Code

Add support for restoring the view position in reloadable views
[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, int *width, size_t max_width, int *trimmed, bool reserve);
72 static bool prompt_yesno(const char *prompt);
73 static int load_refs(void);
75 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
76 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
108 #define AUTHOR_COLS     20
109 #define ID_COLS         8
111 /* The default interval between line numbers. */
112 #define NUMBER_INTERVAL 5
114 #define TAB_SIZE        8
116 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char *name;             /* Ref name; tag or head names are shortened. */
132         char id[SIZEOF_REV];    /* Commit SHA1 ID */
133         unsigned int head:1;    /* Is it the current HEAD? */
134         unsigned int tag:1;     /* Is it a tag? */
135         unsigned int ltag:1;    /* If so, is the tag local? */
136         unsigned int remote:1;  /* Is it a remote ref? */
137         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138         unsigned int next:1;    /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152         const char *name;
153         int namelen;
154         int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159                  int *value, const char *name, int namelen)
162         int i;
164         for (i = 0; i < map_size; i++)
165                 if (namelen == map[i].namelen &&
166                     !strncasecmp(name, map[i].name, namelen)) {
167                         *value = map[i].value;
168                         return OK;
169                 }
171         return ERR;
175 /*
176  * String helpers
177  */
179 static inline void
180 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
182         if (srclen > dstlen - 1)
183                 srclen = dstlen - 1;
185         strncpy(dst, src, srclen);
186         dst[srclen] = 0;
189 /* Shorthands for safely copying into a fixed buffer. */
191 #define string_copy(dst, src) \
192         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
194 #define string_ncopy(dst, src, srclen) \
195         string_ncopy_do(dst, sizeof(dst), src, srclen)
197 #define string_copy_rev(dst, src) \
198         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
200 #define string_add(dst, from, src) \
201         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 static char *
204 chomp_string(char *name)
206         int namelen;
208         while (isspace(*name))
209                 name++;
211         namelen = strlen(name) - 1;
212         while (namelen > 0 && isspace(name[namelen]))
213                 name[namelen--] = 0;
215         return name;
218 static bool
219 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
221         va_list args;
222         size_t pos = bufpos ? *bufpos : 0;
224         va_start(args, fmt);
225         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
226         va_end(args);
228         if (bufpos)
229                 *bufpos = pos;
231         return pos >= bufsize ? FALSE : TRUE;
234 #define string_format(buf, fmt, args...) \
235         string_nformat(buf, sizeof(buf), NULL, fmt, args)
237 #define string_format_from(buf, from, fmt, args...) \
238         string_nformat(buf, sizeof(buf), from, fmt, args)
240 static int
241 string_enum_compare(const char *str1, const char *str2, int len)
243         size_t i;
245 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
247         /* Diff-Header == DIFF_HEADER */
248         for (i = 0; i < len; i++) {
249                 if (toupper(str1[i]) == toupper(str2[i]))
250                         continue;
252                 if (string_enum_sep(str1[i]) &&
253                     string_enum_sep(str2[i]))
254                         continue;
256                 return str1[i] - str2[i];
257         }
259         return 0;
262 #define prefixcmp(str1, str2) \
263         strncmp(str1, str2, STRING_SIZE(str2))
265 static inline int
266 suffixcmp(const char *str, int slen, const char *suffix)
268         size_t len = slen >= 0 ? slen : strlen(str);
269         size_t suffixlen = strlen(suffix);
271         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
275 static bool
276 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
278         int valuelen;
280         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
281                 bool advance = cmd[valuelen] != 0;
283                 cmd[valuelen] = 0;
284                 argv[(*argc)++] = chomp_string(cmd);
285                 cmd += valuelen + advance;
286         }
288         if (*argc < SIZEOF_ARG)
289                 argv[*argc] = NULL;
290         return *argc < SIZEOF_ARG;
293 static void
294 argv_from_env(const char **argv, const char *name)
296         char *env = argv ? getenv(name) : NULL;
297         int argc = 0;
299         if (env && *env)
300                 env = strdup(env);
301         if (env && !argv_from_string(argv, &argc, env))
302                 die("Too many arguments in the `%s` environment variable", name);
306 /*
307  * Executing external commands.
308  */
310 enum io_type {
311         IO_FD,                  /* File descriptor based IO. */
312         IO_BG,                  /* Execute command in the background. */
313         IO_FG,                  /* Execute command with same std{in,out,err}. */
314         IO_RD,                  /* Read only fork+exec IO. */
315         IO_WR,                  /* Write only fork+exec IO. */
316         IO_AP,                  /* Append fork+exec output to file. */
317 };
319 struct io {
320         enum io_type type;      /* The requested type of pipe. */
321         const char *dir;        /* Directory from which to execute. */
322         pid_t pid;              /* Pipe for reading or writing. */
323         int pipe;               /* Pipe end for reading or writing. */
324         int error;              /* Error status. */
325         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
326         char *buf;              /* Read buffer. */
327         size_t bufalloc;        /* Allocated buffer size. */
328         size_t bufsize;         /* Buffer content size. */
329         char *bufpos;           /* Current buffer position. */
330         unsigned int eof:1;     /* Has end of file been reached. */
331 };
333 static void
334 reset_io(struct io *io)
336         io->pipe = -1;
337         io->pid = 0;
338         io->buf = io->bufpos = NULL;
339         io->bufalloc = io->bufsize = 0;
340         io->error = 0;
341         io->eof = 0;
344 static void
345 init_io(struct io *io, const char *dir, enum io_type type)
347         reset_io(io);
348         io->type = type;
349         io->dir = dir;
352 static bool
353 init_io_rd(struct io *io, const char *argv[], const char *dir,
354                 enum format_flags flags)
356         init_io(io, dir, IO_RD);
357         return format_argv(io->argv, argv, flags);
360 static bool
361 io_open(struct io *io, const char *name)
363         init_io(io, NULL, IO_FD);
364         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
365         return io->pipe != -1;
368 static bool
369 kill_io(struct io *io)
371         return kill(io->pid, SIGKILL) != -1;
374 static bool
375 done_io(struct io *io)
377         pid_t pid = io->pid;
379         if (io->pipe != -1)
380                 close(io->pipe);
381         free(io->buf);
382         reset_io(io);
384         while (pid > 0) {
385                 int status;
386                 pid_t waiting = waitpid(pid, &status, 0);
388                 if (waiting < 0) {
389                         if (errno == EINTR)
390                                 continue;
391                         report("waitpid failed (%s)", strerror(errno));
392                         return FALSE;
393                 }
395                 return waiting == pid &&
396                        !WIFSIGNALED(status) &&
397                        WIFEXITED(status) &&
398                        !WEXITSTATUS(status);
399         }
401         return TRUE;
404 static bool
405 start_io(struct io *io)
407         int pipefds[2] = { -1, -1 };
409         if (io->type == IO_FD)
410                 return TRUE;
412         if ((io->type == IO_RD || io->type == IO_WR) &&
413             pipe(pipefds) < 0)
414                 return FALSE;
415         else if (io->type == IO_AP)
416                 pipefds[1] = io->pipe;
418         if ((io->pid = fork())) {
419                 if (pipefds[!(io->type == IO_WR)] != -1)
420                         close(pipefds[!(io->type == IO_WR)]);
421                 if (io->pid != -1) {
422                         io->pipe = pipefds[!!(io->type == IO_WR)];
423                         return TRUE;
424                 }
426         } else {
427                 if (io->type != IO_FG) {
428                         int devnull = open("/dev/null", O_RDWR);
429                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
430                         int writefd = (io->type == IO_RD || io->type == IO_AP)
431                                                         ? pipefds[1] : devnull;
433                         dup2(readfd,  STDIN_FILENO);
434                         dup2(writefd, STDOUT_FILENO);
435                         dup2(devnull, STDERR_FILENO);
437                         close(devnull);
438                         if (pipefds[0] != -1)
439                                 close(pipefds[0]);
440                         if (pipefds[1] != -1)
441                                 close(pipefds[1]);
442                 }
444                 if (io->dir && *io->dir && chdir(io->dir) == -1)
445                         die("Failed to change directory: %s", strerror(errno));
447                 execvp(io->argv[0], (char *const*) io->argv);
448                 die("Failed to execute program: %s", strerror(errno));
449         }
451         if (pipefds[!!(io->type == IO_WR)] != -1)
452                 close(pipefds[!!(io->type == IO_WR)]);
453         return FALSE;
456 static bool
457 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
459         init_io(io, dir, type);
460         if (!format_argv(io->argv, argv, FORMAT_NONE))
461                 return FALSE;
462         return start_io(io);
465 static int
466 run_io_do(struct io *io)
468         return start_io(io) && done_io(io);
471 static int
472 run_io_bg(const char **argv)
474         struct io io = {};
476         init_io(&io, NULL, IO_BG);
477         if (!format_argv(io.argv, argv, FORMAT_NONE))
478                 return FALSE;
479         return run_io_do(&io);
482 static bool
483 run_io_fg(const char **argv, const char *dir)
485         struct io io = {};
487         init_io(&io, dir, IO_FG);
488         if (!format_argv(io.argv, argv, FORMAT_NONE))
489                 return FALSE;
490         return run_io_do(&io);
493 static bool
494 run_io_append(const char **argv, enum format_flags flags, int fd)
496         struct io io = {};
498         init_io(&io, NULL, IO_AP);
499         io.pipe = fd;
500         if (format_argv(io.argv, argv, flags))
501                 return run_io_do(&io);
502         close(fd);
503         return FALSE;
506 static bool
507 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
509         return init_io_rd(io, argv, NULL, flags) && start_io(io);
512 static bool
513 io_eof(struct io *io)
515         return io->eof;
518 static int
519 io_error(struct io *io)
521         return io->error;
524 static bool
525 io_strerror(struct io *io)
527         return strerror(io->error);
530 static bool
531 io_can_read(struct io *io)
533         struct timeval tv = { 0, 500 };
534         fd_set fds;
536         FD_ZERO(&fds);
537         FD_SET(io->pipe, &fds);
539         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
542 static ssize_t
543 io_read(struct io *io, void *buf, size_t bufsize)
545         do {
546                 ssize_t readsize = read(io->pipe, buf, bufsize);
548                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
549                         continue;
550                 else if (readsize == -1)
551                         io->error = errno;
552                 else if (readsize == 0)
553                         io->eof = 1;
554                 return readsize;
555         } while (1);
558 static char *
559 io_get(struct io *io, int c, bool can_read)
561         char *eol;
562         ssize_t readsize;
564         if (!io->buf) {
565                 io->buf = io->bufpos = malloc(BUFSIZ);
566                 if (!io->buf)
567                         return NULL;
568                 io->bufalloc = BUFSIZ;
569                 io->bufsize = 0;
570         }
572         while (TRUE) {
573                 if (io->bufsize > 0) {
574                         eol = memchr(io->bufpos, c, io->bufsize);
575                         if (eol) {
576                                 char *line = io->bufpos;
578                                 *eol = 0;
579                                 io->bufpos = eol + 1;
580                                 io->bufsize -= io->bufpos - line;
581                                 return line;
582                         }
583                 }
585                 if (io_eof(io)) {
586                         if (io->bufsize) {
587                                 io->bufpos[io->bufsize] = 0;
588                                 io->bufsize = 0;
589                                 return io->bufpos;
590                         }
591                         return NULL;
592                 }
594                 if (!can_read)
595                         return NULL;
597                 if (io->bufsize > 0 && io->bufpos > io->buf)
598                         memmove(io->buf, io->bufpos, io->bufsize);
600                 io->bufpos = io->buf;
601                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
602                 if (io_error(io))
603                         return NULL;
604                 io->bufsize += readsize;
605         }
608 static bool
609 io_write(struct io *io, const void *buf, size_t bufsize)
611         size_t written = 0;
613         while (!io_error(io) && written < bufsize) {
614                 ssize_t size;
616                 size = write(io->pipe, buf + written, bufsize - written);
617                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
618                         continue;
619                 else if (size == -1)
620                         io->error = errno;
621                 else
622                         written += size;
623         }
625         return written == bufsize;
628 static bool
629 run_io_buf(const char **argv, char buf[], size_t bufsize)
631         struct io io = {};
632         bool error;
634         if (!run_io_rd(&io, argv, FORMAT_NONE))
635                 return FALSE;
637         io.buf = io.bufpos = buf;
638         io.bufalloc = bufsize;
639         error = !io_get(&io, '\n', TRUE) && io_error(&io);
640         io.buf = NULL;
642         return done_io(&io) || error;
645 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
647 /*
648  * User requests
649  */
651 #define REQ_INFO \
652         /* XXX: Keep the view request first and in sync with views[]. */ \
653         REQ_GROUP("View switching") \
654         REQ_(VIEW_MAIN,         "Show main view"), \
655         REQ_(VIEW_DIFF,         "Show diff view"), \
656         REQ_(VIEW_LOG,          "Show log view"), \
657         REQ_(VIEW_TREE,         "Show tree view"), \
658         REQ_(VIEW_BLOB,         "Show blob view"), \
659         REQ_(VIEW_BLAME,        "Show blame view"), \
660         REQ_(VIEW_HELP,         "Show help page"), \
661         REQ_(VIEW_PAGER,        "Show pager view"), \
662         REQ_(VIEW_STATUS,       "Show status view"), \
663         REQ_(VIEW_STAGE,        "Show stage view"), \
664         \
665         REQ_GROUP("View manipulation") \
666         REQ_(ENTER,             "Enter current line and scroll"), \
667         REQ_(NEXT,              "Move to next"), \
668         REQ_(PREVIOUS,          "Move to previous"), \
669         REQ_(VIEW_NEXT,         "Move focus to next view"), \
670         REQ_(REFRESH,           "Reload and refresh"), \
671         REQ_(MAXIMIZE,          "Maximize the current view"), \
672         REQ_(VIEW_CLOSE,        "Close the current view"), \
673         REQ_(QUIT,              "Close all views and quit"), \
674         \
675         REQ_GROUP("View specific requests") \
676         REQ_(STATUS_UPDATE,     "Update file status"), \
677         REQ_(STATUS_REVERT,     "Revert file changes"), \
678         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
679         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
680         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
681         \
682         REQ_GROUP("Cursor navigation") \
683         REQ_(MOVE_UP,           "Move cursor one line up"), \
684         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
685         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
686         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
687         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
688         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
689         \
690         REQ_GROUP("Scrolling") \
691         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
692         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
693         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
694         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
695         \
696         REQ_GROUP("Searching") \
697         REQ_(SEARCH,            "Search the view"), \
698         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
699         REQ_(FIND_NEXT,         "Find next search match"), \
700         REQ_(FIND_PREV,         "Find previous search match"), \
701         \
702         REQ_GROUP("Option manipulation") \
703         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
704         REQ_(TOGGLE_DATE,       "Toggle date display"), \
705         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
706         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
707         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
708         \
709         REQ_GROUP("Misc") \
710         REQ_(PROMPT,            "Bring up the prompt"), \
711         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
712         REQ_(SHOW_VERSION,      "Show version information"), \
713         REQ_(STOP_LOADING,      "Stop all loading views"), \
714         REQ_(EDIT,              "Open in editor"), \
715         REQ_(NONE,              "Do nothing")
718 /* User action requests. */
719 enum request {
720 #define REQ_GROUP(help)
721 #define REQ_(req, help) REQ_##req
723         /* Offset all requests to avoid conflicts with ncurses getch values. */
724         REQ_OFFSET = KEY_MAX + 1,
725         REQ_INFO
727 #undef  REQ_GROUP
728 #undef  REQ_
729 };
731 struct request_info {
732         enum request request;
733         const char *name;
734         int namelen;
735         const char *help;
736 };
738 static struct request_info req_info[] = {
739 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
740 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
741         REQ_INFO
742 #undef  REQ_GROUP
743 #undef  REQ_
744 };
746 static enum request
747 get_request(const char *name)
749         int namelen = strlen(name);
750         int i;
752         for (i = 0; i < ARRAY_SIZE(req_info); i++)
753                 if (req_info[i].namelen == namelen &&
754                     !string_enum_compare(req_info[i].name, name, namelen))
755                         return req_info[i].request;
757         return REQ_NONE;
761 /*
762  * Options
763  */
765 static const char usage[] =
766 "tig " TIG_VERSION " (" __DATE__ ")\n"
767 "\n"
768 "Usage: tig        [options] [revs] [--] [paths]\n"
769 "   or: tig show   [options] [revs] [--] [paths]\n"
770 "   or: tig blame  [rev] path\n"
771 "   or: tig status\n"
772 "   or: tig <      [git command output]\n"
773 "\n"
774 "Options:\n"
775 "  -v, --version   Show version and exit\n"
776 "  -h, --help      Show help message and exit";
778 /* Option and state variables. */
779 static bool opt_date                    = TRUE;
780 static bool opt_author                  = TRUE;
781 static bool opt_line_number             = FALSE;
782 static bool opt_line_graphics           = TRUE;
783 static bool opt_rev_graph               = FALSE;
784 static bool opt_show_refs               = TRUE;
785 static int opt_num_interval             = NUMBER_INTERVAL;
786 static int opt_tab_size                 = TAB_SIZE;
787 static int opt_author_cols              = AUTHOR_COLS-1;
788 static char opt_path[SIZEOF_STR]        = "";
789 static char opt_file[SIZEOF_STR]        = "";
790 static char opt_ref[SIZEOF_REF]         = "";
791 static char opt_head[SIZEOF_REF]        = "";
792 static char opt_head_rev[SIZEOF_REV]    = "";
793 static char opt_remote[SIZEOF_REF]      = "";
794 static char opt_encoding[20]            = "UTF-8";
795 static bool opt_utf8                    = TRUE;
796 static char opt_codeset[20]             = "UTF-8";
797 static iconv_t opt_iconv                = ICONV_NONE;
798 static char opt_search[SIZEOF_STR]      = "";
799 static char opt_cdup[SIZEOF_STR]        = "";
800 static char opt_git_dir[SIZEOF_STR]     = "";
801 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
802 static char opt_editor[SIZEOF_STR]      = "";
803 static FILE *opt_tty                    = NULL;
805 #define is_initial_commit()     (!*opt_head_rev)
806 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
808 static enum request
809 parse_options(int argc, const char *argv[], const char ***run_argv)
811         enum request request = REQ_VIEW_MAIN;
812         const char *subcommand;
813         bool seen_dashdash = FALSE;
814         /* XXX: This is vulnerable to the user overriding options
815          * required for the main view parser. */
816         static const char *custom_argv[SIZEOF_ARG] = {
817                 "git", "log", "--no-color", "--pretty=raw", "--parents",
818                         "--topo-order", NULL
819         };
820         int i, j = 6;
822         if (!isatty(STDIN_FILENO))
823                 return REQ_VIEW_PAGER;
825         if (argc <= 1)
826                 return REQ_VIEW_MAIN;
828         subcommand = argv[1];
829         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
830                 if (!strcmp(subcommand, "-S"))
831                         warn("`-S' has been deprecated; use `tig status' instead");
832                 if (argc > 2)
833                         warn("ignoring arguments after `%s'", subcommand);
834                 return REQ_VIEW_STATUS;
836         } else if (!strcmp(subcommand, "blame")) {
837                 if (argc <= 2 || argc > 4)
838                         die("invalid number of options to blame\n\n%s", usage);
840                 i = 2;
841                 if (argc == 4) {
842                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
843                         i++;
844                 }
846                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
847                 return REQ_VIEW_BLAME;
849         } else if (!strcmp(subcommand, "show")) {
850                 request = REQ_VIEW_DIFF;
852         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
853                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
854                 warn("`tig %s' has been deprecated", subcommand);
856         } else {
857                 subcommand = NULL;
858         }
860         if (subcommand) {
861                 custom_argv[1] = subcommand;
862                 j = 2;
863         }
865         for (i = 1 + !!subcommand; i < argc; i++) {
866                 const char *opt = argv[i];
868                 if (seen_dashdash || !strcmp(opt, "--")) {
869                         seen_dashdash = TRUE;
871                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
872                         printf("tig version %s\n", TIG_VERSION);
873                         return REQ_NONE;
875                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
876                         printf("%s\n", usage);
877                         return REQ_NONE;
878                 }
880                 custom_argv[j++] = opt;
881                 if (j >= ARRAY_SIZE(custom_argv))
882                         die("command too long");
883         }
885         custom_argv[j] = NULL;
886         *run_argv = custom_argv;
888         return request;
892 /*
893  * Line-oriented content detection.
894  */
896 #define LINE_INFO \
897 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
898 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
899 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
900 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
901 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
902 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
903 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
905 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
906 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
907 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
908 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
909 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
910 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
911 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
912 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
913 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
914 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
915 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
916 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
917 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
918 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
919 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
920 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
921 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
922 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
923 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
924 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
926 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
927 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
928 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
929 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
930 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
931 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
932 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
933 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
934 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
935 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
936 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
937 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
938 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
939 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
940 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
941 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
942 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
943 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
944 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
945 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
946 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
947 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
949 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
950 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
952 enum line_type {
953 #define LINE(type, line, fg, bg, attr) \
954         LINE_##type
955         LINE_INFO,
956         LINE_NONE
957 #undef  LINE
958 };
960 struct line_info {
961         const char *name;       /* Option name. */
962         int namelen;            /* Size of option name. */
963         const char *line;       /* The start of line to match. */
964         int linelen;            /* Size of string to match. */
965         int fg, bg, attr;       /* Color and text attributes for the lines. */
966 };
968 static struct line_info line_info[] = {
969 #define LINE(type, line, fg, bg, attr) \
970         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
971         LINE_INFO
972 #undef  LINE
973 };
975 static enum line_type
976 get_line_type(const char *line)
978         int linelen = strlen(line);
979         enum line_type type;
981         for (type = 0; type < ARRAY_SIZE(line_info); type++)
982                 /* Case insensitive search matches Signed-off-by lines better. */
983                 if (linelen >= line_info[type].linelen &&
984                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
985                         return type;
987         return LINE_DEFAULT;
990 static inline int
991 get_line_attr(enum line_type type)
993         assert(type < ARRAY_SIZE(line_info));
994         return COLOR_PAIR(type) | line_info[type].attr;
997 static struct line_info *
998 get_line_info(const char *name)
1000         size_t namelen = strlen(name);
1001         enum line_type type;
1003         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1004                 if (namelen == line_info[type].namelen &&
1005                     !string_enum_compare(line_info[type].name, name, namelen))
1006                         return &line_info[type];
1008         return NULL;
1011 static void
1012 init_colors(void)
1014         int default_bg = line_info[LINE_DEFAULT].bg;
1015         int default_fg = line_info[LINE_DEFAULT].fg;
1016         enum line_type type;
1018         start_color();
1020         if (assume_default_colors(default_fg, default_bg) == ERR) {
1021                 default_bg = COLOR_BLACK;
1022                 default_fg = COLOR_WHITE;
1023         }
1025         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1026                 struct line_info *info = &line_info[type];
1027                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1028                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1030                 init_pair(type, fg, bg);
1031         }
1034 struct line {
1035         enum line_type type;
1037         /* State flags */
1038         unsigned int selected:1;
1039         unsigned int dirty:1;
1040         unsigned int cleareol:1;
1042         void *data;             /* User data */
1043 };
1046 /*
1047  * Keys
1048  */
1050 struct keybinding {
1051         int alias;
1052         enum request request;
1053 };
1055 static struct keybinding default_keybindings[] = {
1056         /* View switching */
1057         { 'm',          REQ_VIEW_MAIN },
1058         { 'd',          REQ_VIEW_DIFF },
1059         { 'l',          REQ_VIEW_LOG },
1060         { 't',          REQ_VIEW_TREE },
1061         { 'f',          REQ_VIEW_BLOB },
1062         { 'B',          REQ_VIEW_BLAME },
1063         { 'p',          REQ_VIEW_PAGER },
1064         { 'h',          REQ_VIEW_HELP },
1065         { 'S',          REQ_VIEW_STATUS },
1066         { 'c',          REQ_VIEW_STAGE },
1068         /* View manipulation */
1069         { 'q',          REQ_VIEW_CLOSE },
1070         { KEY_TAB,      REQ_VIEW_NEXT },
1071         { KEY_RETURN,   REQ_ENTER },
1072         { KEY_UP,       REQ_PREVIOUS },
1073         { KEY_DOWN,     REQ_NEXT },
1074         { 'R',          REQ_REFRESH },
1075         { KEY_F(5),     REQ_REFRESH },
1076         { 'O',          REQ_MAXIMIZE },
1078         /* Cursor navigation */
1079         { 'k',          REQ_MOVE_UP },
1080         { 'j',          REQ_MOVE_DOWN },
1081         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1082         { KEY_END,      REQ_MOVE_LAST_LINE },
1083         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1084         { ' ',          REQ_MOVE_PAGE_DOWN },
1085         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1086         { 'b',          REQ_MOVE_PAGE_UP },
1087         { '-',          REQ_MOVE_PAGE_UP },
1089         /* Scrolling */
1090         { KEY_IC,       REQ_SCROLL_LINE_UP },
1091         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1092         { 'w',          REQ_SCROLL_PAGE_UP },
1093         { 's',          REQ_SCROLL_PAGE_DOWN },
1095         /* Searching */
1096         { '/',          REQ_SEARCH },
1097         { '?',          REQ_SEARCH_BACK },
1098         { 'n',          REQ_FIND_NEXT },
1099         { 'N',          REQ_FIND_PREV },
1101         /* Misc */
1102         { 'Q',          REQ_QUIT },
1103         { 'z',          REQ_STOP_LOADING },
1104         { 'v',          REQ_SHOW_VERSION },
1105         { 'r',          REQ_SCREEN_REDRAW },
1106         { '.',          REQ_TOGGLE_LINENO },
1107         { 'D',          REQ_TOGGLE_DATE },
1108         { 'A',          REQ_TOGGLE_AUTHOR },
1109         { 'g',          REQ_TOGGLE_REV_GRAPH },
1110         { 'F',          REQ_TOGGLE_REFS },
1111         { ':',          REQ_PROMPT },
1112         { 'u',          REQ_STATUS_UPDATE },
1113         { '!',          REQ_STATUS_REVERT },
1114         { 'M',          REQ_STATUS_MERGE },
1115         { '@',          REQ_STAGE_NEXT },
1116         { ',',          REQ_TREE_PARENT },
1117         { 'e',          REQ_EDIT },
1118 };
1120 #define KEYMAP_INFO \
1121         KEYMAP_(GENERIC), \
1122         KEYMAP_(MAIN), \
1123         KEYMAP_(DIFF), \
1124         KEYMAP_(LOG), \
1125         KEYMAP_(TREE), \
1126         KEYMAP_(BLOB), \
1127         KEYMAP_(BLAME), \
1128         KEYMAP_(PAGER), \
1129         KEYMAP_(HELP), \
1130         KEYMAP_(STATUS), \
1131         KEYMAP_(STAGE)
1133 enum keymap {
1134 #define KEYMAP_(name) KEYMAP_##name
1135         KEYMAP_INFO
1136 #undef  KEYMAP_
1137 };
1139 static struct int_map keymap_table[] = {
1140 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1141         KEYMAP_INFO
1142 #undef  KEYMAP_
1143 };
1145 #define set_keymap(map, name) \
1146         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1148 struct keybinding_table {
1149         struct keybinding *data;
1150         size_t size;
1151 };
1153 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1155 static void
1156 add_keybinding(enum keymap keymap, enum request request, int key)
1158         struct keybinding_table *table = &keybindings[keymap];
1160         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1161         if (!table->data)
1162                 die("Failed to allocate keybinding");
1163         table->data[table->size].alias = key;
1164         table->data[table->size++].request = request;
1167 /* Looks for a key binding first in the given map, then in the generic map, and
1168  * lastly in the default keybindings. */
1169 static enum request
1170 get_keybinding(enum keymap keymap, int key)
1172         size_t i;
1174         for (i = 0; i < keybindings[keymap].size; i++)
1175                 if (keybindings[keymap].data[i].alias == key)
1176                         return keybindings[keymap].data[i].request;
1178         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1179                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1180                         return keybindings[KEYMAP_GENERIC].data[i].request;
1182         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1183                 if (default_keybindings[i].alias == key)
1184                         return default_keybindings[i].request;
1186         return (enum request) key;
1190 struct key {
1191         const char *name;
1192         int value;
1193 };
1195 static struct key key_table[] = {
1196         { "Enter",      KEY_RETURN },
1197         { "Space",      ' ' },
1198         { "Backspace",  KEY_BACKSPACE },
1199         { "Tab",        KEY_TAB },
1200         { "Escape",     KEY_ESC },
1201         { "Left",       KEY_LEFT },
1202         { "Right",      KEY_RIGHT },
1203         { "Up",         KEY_UP },
1204         { "Down",       KEY_DOWN },
1205         { "Insert",     KEY_IC },
1206         { "Delete",     KEY_DC },
1207         { "Hash",       '#' },
1208         { "Home",       KEY_HOME },
1209         { "End",        KEY_END },
1210         { "PageUp",     KEY_PPAGE },
1211         { "PageDown",   KEY_NPAGE },
1212         { "F1",         KEY_F(1) },
1213         { "F2",         KEY_F(2) },
1214         { "F3",         KEY_F(3) },
1215         { "F4",         KEY_F(4) },
1216         { "F5",         KEY_F(5) },
1217         { "F6",         KEY_F(6) },
1218         { "F7",         KEY_F(7) },
1219         { "F8",         KEY_F(8) },
1220         { "F9",         KEY_F(9) },
1221         { "F10",        KEY_F(10) },
1222         { "F11",        KEY_F(11) },
1223         { "F12",        KEY_F(12) },
1224 };
1226 static int
1227 get_key_value(const char *name)
1229         int i;
1231         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1232                 if (!strcasecmp(key_table[i].name, name))
1233                         return key_table[i].value;
1235         if (strlen(name) == 1 && isprint(*name))
1236                 return (int) *name;
1238         return ERR;
1241 static const char *
1242 get_key_name(int key_value)
1244         static char key_char[] = "'X'";
1245         const char *seq = NULL;
1246         int key;
1248         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1249                 if (key_table[key].value == key_value)
1250                         seq = key_table[key].name;
1252         if (seq == NULL &&
1253             key_value < 127 &&
1254             isprint(key_value)) {
1255                 key_char[1] = (char) key_value;
1256                 seq = key_char;
1257         }
1259         return seq ? seq : "(no key)";
1262 static const char *
1263 get_key(enum request request)
1265         static char buf[BUFSIZ];
1266         size_t pos = 0;
1267         char *sep = "";
1268         int i;
1270         buf[pos] = 0;
1272         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1273                 struct keybinding *keybinding = &default_keybindings[i];
1275                 if (keybinding->request != request)
1276                         continue;
1278                 if (!string_format_from(buf, &pos, "%s%s", sep,
1279                                         get_key_name(keybinding->alias)))
1280                         return "Too many keybindings!";
1281                 sep = ", ";
1282         }
1284         return buf;
1287 struct run_request {
1288         enum keymap keymap;
1289         int key;
1290         const char *argv[SIZEOF_ARG];
1291 };
1293 static struct run_request *run_request;
1294 static size_t run_requests;
1296 static enum request
1297 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1299         struct run_request *req;
1301         if (argc >= ARRAY_SIZE(req->argv) - 1)
1302                 return REQ_NONE;
1304         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1305         if (!req)
1306                 return REQ_NONE;
1308         run_request = req;
1309         req = &run_request[run_requests];
1310         req->keymap = keymap;
1311         req->key = key;
1312         req->argv[0] = NULL;
1314         if (!format_argv(req->argv, argv, FORMAT_NONE))
1315                 return REQ_NONE;
1317         return REQ_NONE + ++run_requests;
1320 static struct run_request *
1321 get_run_request(enum request request)
1323         if (request <= REQ_NONE)
1324                 return NULL;
1325         return &run_request[request - REQ_NONE - 1];
1328 static void
1329 add_builtin_run_requests(void)
1331         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1332         const char *gc[] = { "git", "gc", NULL };
1333         struct {
1334                 enum keymap keymap;
1335                 int key;
1336                 int argc;
1337                 const char **argv;
1338         } reqs[] = {
1339                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1340                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1341         };
1342         int i;
1344         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1345                 enum request req;
1347                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1348                 if (req != REQ_NONE)
1349                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1350         }
1353 /*
1354  * User config file handling.
1355  */
1357 static struct int_map color_map[] = {
1358 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1359         COLOR_MAP(DEFAULT),
1360         COLOR_MAP(BLACK),
1361         COLOR_MAP(BLUE),
1362         COLOR_MAP(CYAN),
1363         COLOR_MAP(GREEN),
1364         COLOR_MAP(MAGENTA),
1365         COLOR_MAP(RED),
1366         COLOR_MAP(WHITE),
1367         COLOR_MAP(YELLOW),
1368 };
1370 #define set_color(color, name) \
1371         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1373 static struct int_map attr_map[] = {
1374 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1375         ATTR_MAP(NORMAL),
1376         ATTR_MAP(BLINK),
1377         ATTR_MAP(BOLD),
1378         ATTR_MAP(DIM),
1379         ATTR_MAP(REVERSE),
1380         ATTR_MAP(STANDOUT),
1381         ATTR_MAP(UNDERLINE),
1382 };
1384 #define set_attribute(attr, name) \
1385         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1387 static int   config_lineno;
1388 static bool  config_errors;
1389 static const char *config_msg;
1391 /* Wants: object fgcolor bgcolor [attr] */
1392 static int
1393 option_color_command(int argc, const char *argv[])
1395         struct line_info *info;
1397         if (argc != 3 && argc != 4) {
1398                 config_msg = "Wrong number of arguments given to color command";
1399                 return ERR;
1400         }
1402         info = get_line_info(argv[0]);
1403         if (!info) {
1404                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1405                         info = get_line_info("delimiter");
1407                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1408                         info = get_line_info("date");
1410                 } else {
1411                         config_msg = "Unknown color name";
1412                         return ERR;
1413                 }
1414         }
1416         if (set_color(&info->fg, argv[1]) == ERR ||
1417             set_color(&info->bg, argv[2]) == ERR) {
1418                 config_msg = "Unknown color";
1419                 return ERR;
1420         }
1422         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1423                 config_msg = "Unknown attribute";
1424                 return ERR;
1425         }
1427         return OK;
1430 static bool parse_bool(const char *s)
1432         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1433                 !strcmp(s, "yes")) ? TRUE : FALSE;
1436 static int
1437 parse_int(const char *s, int default_value, int min, int max)
1439         int value = atoi(s);
1441         return (value < min || value > max) ? default_value : value;
1444 /* Wants: name = value */
1445 static int
1446 option_set_command(int argc, const char *argv[])
1448         if (argc != 3) {
1449                 config_msg = "Wrong number of arguments given to set command";
1450                 return ERR;
1451         }
1453         if (strcmp(argv[1], "=")) {
1454                 config_msg = "No value assigned";
1455                 return ERR;
1456         }
1458         if (!strcmp(argv[0], "show-author")) {
1459                 opt_author = parse_bool(argv[2]);
1460                 return OK;
1461         }
1463         if (!strcmp(argv[0], "show-date")) {
1464                 opt_date = parse_bool(argv[2]);
1465                 return OK;
1466         }
1468         if (!strcmp(argv[0], "show-rev-graph")) {
1469                 opt_rev_graph = parse_bool(argv[2]);
1470                 return OK;
1471         }
1473         if (!strcmp(argv[0], "show-refs")) {
1474                 opt_show_refs = parse_bool(argv[2]);
1475                 return OK;
1476         }
1478         if (!strcmp(argv[0], "show-line-numbers")) {
1479                 opt_line_number = parse_bool(argv[2]);
1480                 return OK;
1481         }
1483         if (!strcmp(argv[0], "line-graphics")) {
1484                 opt_line_graphics = parse_bool(argv[2]);
1485                 return OK;
1486         }
1488         if (!strcmp(argv[0], "line-number-interval")) {
1489                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1490                 return OK;
1491         }
1493         if (!strcmp(argv[0], "author-width")) {
1494                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1495                 return OK;
1496         }
1498         if (!strcmp(argv[0], "tab-size")) {
1499                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1500                 return OK;
1501         }
1503         if (!strcmp(argv[0], "commit-encoding")) {
1504                 const char *arg = argv[2];
1505                 int arglen = strlen(arg);
1507                 switch (arg[0]) {
1508                 case '"':
1509                 case '\'':
1510                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1511                                 config_msg = "Unmatched quotation";
1512                                 return ERR;
1513                         }
1514                         arg += 1; arglen -= 2;
1515                 default:
1516                         string_ncopy(opt_encoding, arg, strlen(arg));
1517                         return OK;
1518                 }
1519         }
1521         config_msg = "Unknown variable name";
1522         return ERR;
1525 /* Wants: mode request key */
1526 static int
1527 option_bind_command(int argc, const char *argv[])
1529         enum request request;
1530         int keymap;
1531         int key;
1533         if (argc < 3) {
1534                 config_msg = "Wrong number of arguments given to bind command";
1535                 return ERR;
1536         }
1538         if (set_keymap(&keymap, argv[0]) == ERR) {
1539                 config_msg = "Unknown key map";
1540                 return ERR;
1541         }
1543         key = get_key_value(argv[1]);
1544         if (key == ERR) {
1545                 config_msg = "Unknown key";
1546                 return ERR;
1547         }
1549         request = get_request(argv[2]);
1550         if (request == REQ_NONE) {
1551                 const char *obsolete[] = { "cherry-pick", "screen-resize" };
1552                 size_t namelen = strlen(argv[2]);
1553                 int i;
1555                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1556                         if (namelen == strlen(obsolete[i]) &&
1557                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1558                                 config_msg = "Obsolete request name";
1559                                 return ERR;
1560                         }
1561                 }
1562         }
1563         if (request == REQ_NONE && *argv[2]++ == '!')
1564                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1565         if (request == REQ_NONE) {
1566                 config_msg = "Unknown request name";
1567                 return ERR;
1568         }
1570         add_keybinding(keymap, request, key);
1572         return OK;
1575 static int
1576 set_option(const char *opt, char *value)
1578         const char *argv[SIZEOF_ARG];
1579         int argc = 0;
1581         if (!argv_from_string(argv, &argc, value)) {
1582                 config_msg = "Too many option arguments";
1583                 return ERR;
1584         }
1586         if (!strcmp(opt, "color"))
1587                 return option_color_command(argc, argv);
1589         if (!strcmp(opt, "set"))
1590                 return option_set_command(argc, argv);
1592         if (!strcmp(opt, "bind"))
1593                 return option_bind_command(argc, argv);
1595         config_msg = "Unknown option command";
1596         return ERR;
1599 static int
1600 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1602         int status = OK;
1604         config_lineno++;
1605         config_msg = "Internal error";
1607         /* Check for comment markers, since read_properties() will
1608          * only ensure opt and value are split at first " \t". */
1609         optlen = strcspn(opt, "#");
1610         if (optlen == 0)
1611                 return OK;
1613         if (opt[optlen] != 0) {
1614                 config_msg = "No option value";
1615                 status = ERR;
1617         }  else {
1618                 /* Look for comment endings in the value. */
1619                 size_t len = strcspn(value, "#");
1621                 if (len < valuelen) {
1622                         valuelen = len;
1623                         value[valuelen] = 0;
1624                 }
1626                 status = set_option(opt, value);
1627         }
1629         if (status == ERR) {
1630                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1631                         config_lineno, (int) optlen, opt, config_msg);
1632                 config_errors = TRUE;
1633         }
1635         /* Always keep going if errors are encountered. */
1636         return OK;
1639 static void
1640 load_option_file(const char *path)
1642         struct io io = {};
1644         /* It's ok that the file doesn't exist. */
1645         if (!io_open(&io, path))
1646                 return;
1648         config_lineno = 0;
1649         config_errors = FALSE;
1651         if (read_properties(&io, " \t", read_option) == ERR ||
1652             config_errors == TRUE)
1653                 fprintf(stderr, "Errors while loading %s.\n", path);
1656 static int
1657 load_options(void)
1659         const char *home = getenv("HOME");
1660         const char *tigrc_user = getenv("TIGRC_USER");
1661         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1662         char buf[SIZEOF_STR];
1664         add_builtin_run_requests();
1666         if (!tigrc_system) {
1667                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1668                         return ERR;
1669                 tigrc_system = buf;
1670         }
1671         load_option_file(tigrc_system);
1673         if (!tigrc_user) {
1674                 if (!home || !string_format(buf, "%s/.tigrc", home))
1675                         return ERR;
1676                 tigrc_user = buf;
1677         }
1678         load_option_file(tigrc_user);
1680         return OK;
1684 /*
1685  * The viewer
1686  */
1688 struct view;
1689 struct view_ops;
1691 /* The display array of active views and the index of the current view. */
1692 static struct view *display[2];
1693 static unsigned int current_view;
1695 /* Reading from the prompt? */
1696 static bool input_mode = FALSE;
1698 #define foreach_displayed_view(view, i) \
1699         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1701 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1703 /* Current head and commit ID */
1704 static char ref_blob[SIZEOF_REF]        = "";
1705 static char ref_commit[SIZEOF_REF]      = "HEAD";
1706 static char ref_head[SIZEOF_REF]        = "HEAD";
1708 struct view {
1709         const char *name;       /* View name */
1710         const char *cmd_env;    /* Command line set via environment */
1711         const char *id;         /* Points to either of ref_{head,commit,blob} */
1713         struct view_ops *ops;   /* View operations */
1715         enum keymap keymap;     /* What keymap does this view have */
1716         bool git_dir;           /* Whether the view requires a git directory. */
1718         char ref[SIZEOF_REF];   /* Hovered commit reference */
1719         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1721         int height, width;      /* The width and height of the main window */
1722         WINDOW *win;            /* The main window */
1723         WINDOW *title;          /* The title window living below the main window */
1725         /* Navigation */
1726         unsigned long offset;   /* Offset of the window top */
1727         unsigned long lineno;   /* Current line number */
1728         unsigned long p_offset; /* Previous offset of the window top */
1729         unsigned long p_lineno; /* Previous current line number */
1730         bool p_restore;         /* Should the previous position be restored. */
1732         /* Searching */
1733         char grep[SIZEOF_STR];  /* Search string */
1734         regex_t *regex;         /* Pre-compiled regex */
1736         /* If non-NULL, points to the view that opened this view. If this view
1737          * is closed tig will switch back to the parent view. */
1738         struct view *parent;
1740         /* Buffering */
1741         size_t lines;           /* Total number of lines */
1742         struct line *line;      /* Line index */
1743         size_t line_alloc;      /* Total number of allocated lines */
1744         unsigned int digits;    /* Number of digits in the lines member. */
1746         /* Drawing */
1747         struct line *curline;   /* Line currently being drawn. */
1748         enum line_type curtype; /* Attribute currently used for drawing. */
1749         unsigned long col;      /* Column when drawing. */
1751         /* Loading */
1752         struct io io;
1753         struct io *pipe;
1754         time_t start_time;
1755         time_t update_secs;
1756 };
1758 struct view_ops {
1759         /* What type of content being displayed. Used in the title bar. */
1760         const char *type;
1761         /* Default command arguments. */
1762         const char **argv;
1763         /* Open and reads in all view content. */
1764         bool (*open)(struct view *view);
1765         /* Read one line; updates view->line. */
1766         bool (*read)(struct view *view, char *data);
1767         /* Draw one line; @lineno must be < view->height. */
1768         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1769         /* Depending on view handle a special requests. */
1770         enum request (*request)(struct view *view, enum request request, struct line *line);
1771         /* Search for regex in a line. */
1772         bool (*grep)(struct view *view, struct line *line);
1773         /* Select line */
1774         void (*select)(struct view *view, struct line *line);
1775 };
1777 static struct view_ops blame_ops;
1778 static struct view_ops blob_ops;
1779 static struct view_ops diff_ops;
1780 static struct view_ops help_ops;
1781 static struct view_ops log_ops;
1782 static struct view_ops main_ops;
1783 static struct view_ops pager_ops;
1784 static struct view_ops stage_ops;
1785 static struct view_ops status_ops;
1786 static struct view_ops tree_ops;
1788 #define VIEW_STR(name, env, ref, ops, map, git) \
1789         { name, #env, ref, ops, map, git }
1791 #define VIEW_(id, name, ops, git, ref) \
1792         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1795 static struct view views[] = {
1796         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1797         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1798         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1799         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1800         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1801         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1802         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1803         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1804         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1805         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1806 };
1808 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1809 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1811 #define foreach_view(view, i) \
1812         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1814 #define view_is_displayed(view) \
1815         (view == display[0] || view == display[1])
1818 enum line_graphic {
1819         LINE_GRAPHIC_VLINE
1820 };
1822 static int line_graphics[] = {
1823         /* LINE_GRAPHIC_VLINE: */ '|'
1824 };
1826 static inline void
1827 set_view_attr(struct view *view, enum line_type type)
1829         if (!view->curline->selected && view->curtype != type) {
1830                 wattrset(view->win, get_line_attr(type));
1831                 wchgat(view->win, -1, 0, type, NULL);
1832                 view->curtype = type;
1833         }
1836 static int
1837 draw_chars(struct view *view, enum line_type type, const char *string,
1838            int max_len, bool use_tilde)
1840         int len = 0;
1841         int col = 0;
1842         int trimmed = FALSE;
1844         if (max_len <= 0)
1845                 return 0;
1847         if (opt_utf8) {
1848                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1849         } else {
1850                 col = len = strlen(string);
1851                 if (len > max_len) {
1852                         if (use_tilde) {
1853                                 max_len -= 1;
1854                         }
1855                         col = len = max_len;
1856                         trimmed = TRUE;
1857                 }
1858         }
1860         set_view_attr(view, type);
1861         waddnstr(view->win, string, len);
1862         if (trimmed && use_tilde) {
1863                 set_view_attr(view, LINE_DELIMITER);
1864                 waddch(view->win, '~');
1865                 col++;
1866         }
1868         return col;
1871 static int
1872 draw_space(struct view *view, enum line_type type, int max, int spaces)
1874         static char space[] = "                    ";
1875         int col = 0;
1877         spaces = MIN(max, spaces);
1879         while (spaces > 0) {
1880                 int len = MIN(spaces, sizeof(space) - 1);
1882                 col += draw_chars(view, type, space, spaces, FALSE);
1883                 spaces -= len;
1884         }
1886         return col;
1889 static bool
1890 draw_lineno(struct view *view, unsigned int lineno)
1892         char number[10];
1893         int digits3 = view->digits < 3 ? 3 : view->digits;
1894         int max_number = MIN(digits3, STRING_SIZE(number));
1895         int max = view->width - view->col;
1896         int col;
1898         if (max < max_number)
1899                 max_number = max;
1901         lineno += view->offset + 1;
1902         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1903                 static char fmt[] = "%1ld";
1905                 if (view->digits <= 9)
1906                         fmt[1] = '0' + digits3;
1908                 if (!string_format(number, fmt, lineno))
1909                         number[0] = 0;
1910                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1911         } else {
1912                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1913         }
1915         if (col < max) {
1916                 set_view_attr(view, LINE_DEFAULT);
1917                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1918                 col++;
1919         }
1921         if (col < max)
1922                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1923         view->col += col;
1925         return view->width - view->col <= 0;
1928 static bool
1929 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1931         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1932         return view->width - view->col <= 0;
1935 static bool
1936 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1938         int max = view->width - view->col;
1939         int i;
1941         if (max < size)
1942                 size = max;
1944         set_view_attr(view, type);
1945         /* Using waddch() instead of waddnstr() ensures that
1946          * they'll be rendered correctly for the cursor line. */
1947         for (i = 0; i < size; i++)
1948                 waddch(view->win, graphic[i]);
1950         view->col += size;
1951         if (size < max) {
1952                 waddch(view->win, ' ');
1953                 view->col++;
1954         }
1956         return view->width - view->col <= 0;
1959 static bool
1960 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1962         int max = MIN(view->width - view->col, len);
1963         int col;
1965         if (text)
1966                 col = draw_chars(view, type, text, max - 1, trim);
1967         else
1968                 col = draw_space(view, type, max - 1, max - 1);
1970         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1971         return view->width - view->col <= 0;
1974 static bool
1975 draw_date(struct view *view, struct tm *time)
1977         char buf[DATE_COLS];
1978         char *date;
1979         int timelen = 0;
1981         if (time)
1982                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1983         date = timelen ? buf : NULL;
1985         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1988 static bool
1989 draw_view_line(struct view *view, unsigned int lineno)
1991         struct line *line;
1992         bool selected = (view->offset + lineno == view->lineno);
1993         bool draw_ok;
1995         assert(view_is_displayed(view));
1997         if (view->offset + lineno >= view->lines)
1998                 return FALSE;
2000         line = &view->line[view->offset + lineno];
2002         wmove(view->win, lineno, 0);
2003         if (line->cleareol)
2004                 wclrtoeol(view->win);
2005         view->col = 0;
2006         view->curline = line;
2007         view->curtype = LINE_NONE;
2008         line->selected = FALSE;
2009         line->dirty = line->cleareol = 0;
2011         if (selected) {
2012                 set_view_attr(view, LINE_CURSOR);
2013                 line->selected = TRUE;
2014                 view->ops->select(view, line);
2015         }
2017         scrollok(view->win, FALSE);
2018         draw_ok = view->ops->draw(view, line, lineno);
2019         scrollok(view->win, TRUE);
2021         return draw_ok;
2024 static void
2025 redraw_view_dirty(struct view *view)
2027         bool dirty = FALSE;
2028         int lineno;
2030         for (lineno = 0; lineno < view->height; lineno++) {
2031                 if (view->offset + lineno >= view->lines)
2032                         break;
2033                 if (!view->line[view->offset + lineno].dirty)
2034                         continue;
2035                 dirty = TRUE;
2036                 if (!draw_view_line(view, lineno))
2037                         break;
2038         }
2040         if (!dirty)
2041                 return;
2042         redrawwin(view->win);
2043         if (input_mode)
2044                 wnoutrefresh(view->win);
2045         else
2046                 wrefresh(view->win);
2049 static void
2050 redraw_view_from(struct view *view, int lineno)
2052         assert(0 <= lineno && lineno < view->height);
2054         for (; lineno < view->height; lineno++) {
2055                 if (!draw_view_line(view, lineno))
2056                         break;
2057         }
2059         redrawwin(view->win);
2060         if (input_mode)
2061                 wnoutrefresh(view->win);
2062         else
2063                 wrefresh(view->win);
2066 static void
2067 redraw_view(struct view *view)
2069         werase(view->win);
2070         redraw_view_from(view, 0);
2074 static void
2075 update_view_title(struct view *view)
2077         char buf[SIZEOF_STR];
2078         char state[SIZEOF_STR];
2079         size_t bufpos = 0, statelen = 0;
2081         assert(view_is_displayed(view));
2083         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2084                 unsigned int view_lines = view->offset + view->height;
2085                 unsigned int lines = view->lines
2086                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2087                                    : 0;
2089                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2090                                    view->ops->type,
2091                                    view->lineno + 1,
2092                                    view->lines,
2093                                    lines);
2095         }
2097         if (view->pipe) {
2098                 time_t secs = time(NULL) - view->start_time;
2100                 /* Three git seconds are a long time ... */
2101                 if (secs > 2)
2102                         string_format_from(state, &statelen, " loading %lds", secs);
2103         }
2105         string_format_from(buf, &bufpos, "[%s]", view->name);
2106         if (*view->ref && bufpos < view->width) {
2107                 size_t refsize = strlen(view->ref);
2108                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2110                 if (minsize < view->width)
2111                         refsize = view->width - minsize + 7;
2112                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2113         }
2115         if (statelen && bufpos < view->width) {
2116                 string_format_from(buf, &bufpos, "%s", state);
2117         }
2119         if (view == display[current_view])
2120                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2121         else
2122                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2124         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2125         wclrtoeol(view->title);
2126         wmove(view->title, 0, view->width - 1);
2128         if (input_mode)
2129                 wnoutrefresh(view->title);
2130         else
2131                 wrefresh(view->title);
2134 static void
2135 resize_display(void)
2137         int offset, i;
2138         struct view *base = display[0];
2139         struct view *view = display[1] ? display[1] : display[0];
2141         /* Setup window dimensions */
2143         getmaxyx(stdscr, base->height, base->width);
2145         /* Make room for the status window. */
2146         base->height -= 1;
2148         if (view != base) {
2149                 /* Horizontal split. */
2150                 view->width   = base->width;
2151                 view->height  = SCALE_SPLIT_VIEW(base->height);
2152                 base->height -= view->height;
2154                 /* Make room for the title bar. */
2155                 view->height -= 1;
2156         }
2158         /* Make room for the title bar. */
2159         base->height -= 1;
2161         offset = 0;
2163         foreach_displayed_view (view, i) {
2164                 if (!view->win) {
2165                         view->win = newwin(view->height, 0, offset, 0);
2166                         if (!view->win)
2167                                 die("Failed to create %s view", view->name);
2169                         scrollok(view->win, TRUE);
2171                         view->title = newwin(1, 0, offset + view->height, 0);
2172                         if (!view->title)
2173                                 die("Failed to create title window");
2175                 } else {
2176                         wresize(view->win, view->height, view->width);
2177                         mvwin(view->win,   offset, 0);
2178                         mvwin(view->title, offset + view->height, 0);
2179                 }
2181                 offset += view->height + 1;
2182         }
2185 static void
2186 redraw_display(bool clear)
2188         struct view *view;
2189         int i;
2191         foreach_displayed_view (view, i) {
2192                 if (clear)
2193                         wclear(view->win);
2194                 redraw_view(view);
2195                 update_view_title(view);
2196         }
2199 static void
2200 update_display_cursor(struct view *view)
2202         /* Move the cursor to the right-most column of the cursor line.
2203          *
2204          * XXX: This could turn out to be a bit expensive, but it ensures that
2205          * the cursor does not jump around. */
2206         if (view->lines) {
2207                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2208                 wrefresh(view->win);
2209         }
2212 static void
2213 toggle_view_option(bool *option, const char *help)
2215         *option = !*option;
2216         redraw_display(FALSE);
2217         report("%sabling %s", *option ? "En" : "Dis", help);
2220 /*
2221  * Navigation
2222  */
2224 /* Scrolling backend */
2225 static void
2226 do_scroll_view(struct view *view, int lines)
2228         bool redraw_current_line = FALSE;
2230         /* The rendering expects the new offset. */
2231         view->offset += lines;
2233         assert(0 <= view->offset && view->offset < view->lines);
2234         assert(lines);
2236         /* Move current line into the view. */
2237         if (view->lineno < view->offset) {
2238                 view->lineno = view->offset;
2239                 redraw_current_line = TRUE;
2240         } else if (view->lineno >= view->offset + view->height) {
2241                 view->lineno = view->offset + view->height - 1;
2242                 redraw_current_line = TRUE;
2243         }
2245         assert(view->offset <= view->lineno && view->lineno < view->lines);
2247         /* Redraw the whole screen if scrolling is pointless. */
2248         if (view->height < ABS(lines)) {
2249                 redraw_view(view);
2251         } else {
2252                 int line = lines > 0 ? view->height - lines : 0;
2253                 int end = line + ABS(lines);
2255                 wscrl(view->win, lines);
2257                 for (; line < end; line++) {
2258                         if (!draw_view_line(view, line))
2259                                 break;
2260                 }
2262                 if (redraw_current_line)
2263                         draw_view_line(view, view->lineno - view->offset);
2264         }
2266         redrawwin(view->win);
2267         wrefresh(view->win);
2268         report("");
2271 /* Scroll frontend */
2272 static void
2273 scroll_view(struct view *view, enum request request)
2275         int lines = 1;
2277         assert(view_is_displayed(view));
2279         switch (request) {
2280         case REQ_SCROLL_PAGE_DOWN:
2281                 lines = view->height;
2282         case REQ_SCROLL_LINE_DOWN:
2283                 if (view->offset + lines > view->lines)
2284                         lines = view->lines - view->offset;
2286                 if (lines == 0 || view->offset + view->height >= view->lines) {
2287                         report("Cannot scroll beyond the last line");
2288                         return;
2289                 }
2290                 break;
2292         case REQ_SCROLL_PAGE_UP:
2293                 lines = view->height;
2294         case REQ_SCROLL_LINE_UP:
2295                 if (lines > view->offset)
2296                         lines = view->offset;
2298                 if (lines == 0) {
2299                         report("Cannot scroll beyond the first line");
2300                         return;
2301                 }
2303                 lines = -lines;
2304                 break;
2306         default:
2307                 die("request %d not handled in switch", request);
2308         }
2310         do_scroll_view(view, lines);
2313 /* Cursor moving */
2314 static void
2315 move_view(struct view *view, enum request request)
2317         int scroll_steps = 0;
2318         int steps;
2320         switch (request) {
2321         case REQ_MOVE_FIRST_LINE:
2322                 steps = -view->lineno;
2323                 break;
2325         case REQ_MOVE_LAST_LINE:
2326                 steps = view->lines - view->lineno - 1;
2327                 break;
2329         case REQ_MOVE_PAGE_UP:
2330                 steps = view->height > view->lineno
2331                       ? -view->lineno : -view->height;
2332                 break;
2334         case REQ_MOVE_PAGE_DOWN:
2335                 steps = view->lineno + view->height >= view->lines
2336                       ? view->lines - view->lineno - 1 : view->height;
2337                 break;
2339         case REQ_MOVE_UP:
2340                 steps = -1;
2341                 break;
2343         case REQ_MOVE_DOWN:
2344                 steps = 1;
2345                 break;
2347         default:
2348                 die("request %d not handled in switch", request);
2349         }
2351         if (steps <= 0 && view->lineno == 0) {
2352                 report("Cannot move beyond the first line");
2353                 return;
2355         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2356                 report("Cannot move beyond the last line");
2357                 return;
2358         }
2360         /* Move the current line */
2361         view->lineno += steps;
2362         assert(0 <= view->lineno && view->lineno < view->lines);
2364         /* Check whether the view needs to be scrolled */
2365         if (view->lineno < view->offset ||
2366             view->lineno >= view->offset + view->height) {
2367                 scroll_steps = steps;
2368                 if (steps < 0 && -steps > view->offset) {
2369                         scroll_steps = -view->offset;
2371                 } else if (steps > 0) {
2372                         if (view->lineno == view->lines - 1 &&
2373                             view->lines > view->height) {
2374                                 scroll_steps = view->lines - view->offset - 1;
2375                                 if (scroll_steps >= view->height)
2376                                         scroll_steps -= view->height - 1;
2377                         }
2378                 }
2379         }
2381         if (!view_is_displayed(view)) {
2382                 view->offset += scroll_steps;
2383                 assert(0 <= view->offset && view->offset < view->lines);
2384                 view->ops->select(view, &view->line[view->lineno]);
2385                 return;
2386         }
2388         /* Repaint the old "current" line if we be scrolling */
2389         if (ABS(steps) < view->height)
2390                 draw_view_line(view, view->lineno - steps - view->offset);
2392         if (scroll_steps) {
2393                 do_scroll_view(view, scroll_steps);
2394                 return;
2395         }
2397         /* Draw the current line */
2398         draw_view_line(view, view->lineno - view->offset);
2400         redrawwin(view->win);
2401         wrefresh(view->win);
2402         report("");
2406 /*
2407  * Searching
2408  */
2410 static void search_view(struct view *view, enum request request);
2412 static void
2413 select_view_line(struct view *view, unsigned long lineno)
2415         if (lineno - view->offset >= view->height) {
2416                 view->offset = lineno;
2417                 view->lineno = lineno;
2418                 if (view_is_displayed(view))
2419                         redraw_view(view);
2421         } else {
2422                 unsigned long old_lineno = view->lineno - view->offset;
2424                 view->lineno = lineno;
2425                 if (view_is_displayed(view)) {
2426                         draw_view_line(view, old_lineno);
2427                         draw_view_line(view, view->lineno - view->offset);
2428                         redrawwin(view->win);
2429                         wrefresh(view->win);
2430                 } else {
2431                         view->ops->select(view, &view->line[view->lineno]);
2432                 }
2433         }
2436 static void
2437 find_next(struct view *view, enum request request)
2439         unsigned long lineno = view->lineno;
2440         int direction;
2442         if (!*view->grep) {
2443                 if (!*opt_search)
2444                         report("No previous search");
2445                 else
2446                         search_view(view, request);
2447                 return;
2448         }
2450         switch (request) {
2451         case REQ_SEARCH:
2452         case REQ_FIND_NEXT:
2453                 direction = 1;
2454                 break;
2456         case REQ_SEARCH_BACK:
2457         case REQ_FIND_PREV:
2458                 direction = -1;
2459                 break;
2461         default:
2462                 return;
2463         }
2465         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2466                 lineno += direction;
2468         /* Note, lineno is unsigned long so will wrap around in which case it
2469          * will become bigger than view->lines. */
2470         for (; lineno < view->lines; lineno += direction) {
2471                 if (view->ops->grep(view, &view->line[lineno])) {
2472                         select_view_line(view, lineno);
2473                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2474                         return;
2475                 }
2476         }
2478         report("No match found for '%s'", view->grep);
2481 static void
2482 search_view(struct view *view, enum request request)
2484         int regex_err;
2486         if (view->regex) {
2487                 regfree(view->regex);
2488                 *view->grep = 0;
2489         } else {
2490                 view->regex = calloc(1, sizeof(*view->regex));
2491                 if (!view->regex)
2492                         return;
2493         }
2495         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2496         if (regex_err != 0) {
2497                 char buf[SIZEOF_STR] = "unknown error";
2499                 regerror(regex_err, view->regex, buf, sizeof(buf));
2500                 report("Search failed: %s", buf);
2501                 return;
2502         }
2504         string_copy(view->grep, opt_search);
2506         find_next(view, request);
2509 /*
2510  * Incremental updating
2511  */
2513 static void
2514 reset_view(struct view *view)
2516         int i;
2518         for (i = 0; i < view->lines; i++)
2519                 free(view->line[i].data);
2520         free(view->line);
2522         view->p_offset = view->offset;
2523         view->p_lineno = view->lineno;
2525         view->line = NULL;
2526         view->offset = 0;
2527         view->lines  = 0;
2528         view->lineno = 0;
2529         view->line_alloc = 0;
2530         view->vid[0] = 0;
2531         view->update_secs = 0;
2534 static void
2535 free_argv(const char *argv[])
2537         int argc;
2539         for (argc = 0; argv[argc]; argc++)
2540                 free((void *) argv[argc]);
2543 static bool
2544 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2546         char buf[SIZEOF_STR];
2547         int argc;
2548         bool noreplace = flags == FORMAT_NONE;
2550         free_argv(dst_argv);
2552         for (argc = 0; src_argv[argc]; argc++) {
2553                 const char *arg = src_argv[argc];
2554                 size_t bufpos = 0;
2556                 while (arg) {
2557                         char *next = strstr(arg, "%(");
2558                         int len = next - arg;
2559                         const char *value;
2561                         if (!next || noreplace) {
2562                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2563                                         noreplace = TRUE;
2564                                 len = strlen(arg);
2565                                 value = "";
2567                         } else if (!prefixcmp(next, "%(directory)")) {
2568                                 value = opt_path;
2570                         } else if (!prefixcmp(next, "%(file)")) {
2571                                 value = opt_file;
2573                         } else if (!prefixcmp(next, "%(ref)")) {
2574                                 value = *opt_ref ? opt_ref : "HEAD";
2576                         } else if (!prefixcmp(next, "%(head)")) {
2577                                 value = ref_head;
2579                         } else if (!prefixcmp(next, "%(commit)")) {
2580                                 value = ref_commit;
2582                         } else if (!prefixcmp(next, "%(blob)")) {
2583                                 value = ref_blob;
2585                         } else {
2586                                 report("Unknown replacement: `%s`", next);
2587                                 return FALSE;
2588                         }
2590                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2591                                 return FALSE;
2593                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2594                 }
2596                 dst_argv[argc] = strdup(buf);
2597                 if (!dst_argv[argc])
2598                         break;
2599         }
2601         dst_argv[argc] = NULL;
2603         return src_argv[argc] == NULL;
2606 static bool
2607 restore_view_position(struct view *view)
2609         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2610                 return FALSE;
2612         /* Changing the view position cancels the restoring. */
2613         /* FIXME: Changing back to the first line is not detected. */
2614         if (view->offset != 0 || view->lineno != 0) {
2615                 view->p_restore = FALSE;
2616                 return FALSE;
2617         }
2619         if (view->p_lineno >= view->lines) {
2620                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2621                 if (view->p_offset >= view->p_lineno) {
2622                         unsigned long half = view->height / 2;
2624                         if (view->p_lineno > half)
2625                                 view->p_offset = view->p_lineno - half;
2626                         else
2627                                 view->p_offset = 0;
2628                 }
2629         }
2631         if (view_is_displayed(view) &&
2632             view->offset != view->p_offset &&
2633             view->lineno != view->p_lineno)
2634                 werase(view->win);
2636         view->offset = view->p_offset;
2637         view->lineno = view->p_lineno;
2638         view->p_restore = FALSE;
2640         return TRUE;
2643 static void
2644 end_update(struct view *view, bool force)
2646         if (!view->pipe)
2647                 return;
2648         while (!view->ops->read(view, NULL))
2649                 if (!force)
2650                         return;
2651         set_nonblocking_input(FALSE);
2652         if (force)
2653                 kill_io(view->pipe);
2654         done_io(view->pipe);
2655         view->pipe = NULL;
2658 static void
2659 setup_update(struct view *view, const char *vid)
2661         set_nonblocking_input(TRUE);
2662         reset_view(view);
2663         string_copy_rev(view->vid, vid);
2664         view->pipe = &view->io;
2665         view->start_time = time(NULL);
2668 static bool
2669 prepare_update(struct view *view, const char *argv[], const char *dir,
2670                enum format_flags flags)
2672         if (view->pipe)
2673                 end_update(view, TRUE);
2674         return init_io_rd(&view->io, argv, dir, flags);
2677 static bool
2678 prepare_update_file(struct view *view, const char *name)
2680         if (view->pipe)
2681                 end_update(view, TRUE);
2682         return io_open(&view->io, name);
2685 static bool
2686 begin_update(struct view *view, bool refresh)
2688         if (view->pipe)
2689                 end_update(view, TRUE);
2691         if (refresh) {
2692                 if (!start_io(&view->io))
2693                         return FALSE;
2695         } else {
2696                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2697                         opt_path[0] = 0;
2699                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2700                         return FALSE;
2702                 /* Put the current ref_* value to the view title ref
2703                  * member. This is needed by the blob view. Most other
2704                  * views sets it automatically after loading because the
2705                  * first line is a commit line. */
2706                 string_copy_rev(view->ref, view->id);
2707         }
2709         setup_update(view, view->id);
2711         return TRUE;
2714 #define ITEM_CHUNK_SIZE 256
2715 static void *
2716 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2718         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2719         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2721         if (mem == NULL || num_chunks != num_chunks_new) {
2722                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2723                 mem = realloc(mem, *size * item_size);
2724         }
2726         return mem;
2729 static struct line *
2730 realloc_lines(struct view *view, size_t line_size)
2732         size_t alloc = view->line_alloc;
2733         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2734                                          sizeof(*view->line));
2736         if (!tmp)
2737                 return NULL;
2739         view->line = tmp;
2740         view->line_alloc = alloc;
2741         return view->line;
2744 static bool
2745 update_view(struct view *view)
2747         char out_buffer[BUFSIZ * 2];
2748         char *line;
2749         /* Clear the view and redraw everything since the tree sorting
2750          * might have rearranged things. */
2751         bool redraw = view->lines == 0;
2752         bool can_read = TRUE;
2754         if (!view->pipe)
2755                 return TRUE;
2757         if (!io_can_read(view->pipe)) {
2758                 if (view->lines == 0) {
2759                         time_t secs = time(NULL) - view->start_time;
2761                         if (secs > view->update_secs) {
2762                                 if (view->update_secs == 0)
2763                                         redraw_view(view);
2764                                 update_view_title(view);
2765                                 view->update_secs = secs;
2766                         }
2767                 }
2768                 return TRUE;
2769         }
2771         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2772                 if (opt_iconv != ICONV_NONE) {
2773                         ICONV_CONST char *inbuf = line;
2774                         size_t inlen = strlen(line) + 1;
2776                         char *outbuf = out_buffer;
2777                         size_t outlen = sizeof(out_buffer);
2779                         size_t ret;
2781                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2782                         if (ret != (size_t) -1)
2783                                 line = out_buffer;
2784                 }
2786                 if (!view->ops->read(view, line))
2787                         goto alloc_error;
2788         }
2790         {
2791                 unsigned long lines = view->lines;
2792                 int digits;
2794                 for (digits = 0; lines; digits++)
2795                         lines /= 10;
2797                 /* Keep the displayed view in sync with line number scaling. */
2798                 if (digits != view->digits) {
2799                         view->digits = digits;
2800                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2801                                 redraw = TRUE;
2802                 }
2803         }
2805         if (io_error(view->pipe)) {
2806                 report("Failed to read: %s", io_strerror(view->pipe));
2807                 end_update(view, TRUE);
2809         } else if (io_eof(view->pipe)) {
2810                 report("");
2811                 end_update(view, FALSE);
2812         }
2814         if (restore_view_position(view))
2815                 redraw = TRUE;
2817         if (!view_is_displayed(view))
2818                 return TRUE;
2820         if (redraw)
2821                 redraw_view_from(view, 0);
2822         else
2823                 redraw_view_dirty(view);
2825         /* Update the title _after_ the redraw so that if the redraw picks up a
2826          * commit reference in view->ref it'll be available here. */
2827         update_view_title(view);
2828         return TRUE;
2830 alloc_error:
2831         report("Allocation failure");
2832         end_update(view, TRUE);
2833         return FALSE;
2836 static struct line *
2837 add_line_data(struct view *view, void *data, enum line_type type)
2839         struct line *line;
2841         if (!realloc_lines(view, view->lines + 1))
2842                 return NULL;
2844         line = &view->line[view->lines++];
2845         memset(line, 0, sizeof(*line));
2846         line->type = type;
2847         line->data = data;
2848         line->dirty = 1;
2850         return line;
2853 static struct line *
2854 add_line_text(struct view *view, const char *text, enum line_type type)
2856         char *data = text ? strdup(text) : NULL;
2858         return data ? add_line_data(view, data, type) : NULL;
2861 static struct line *
2862 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2864         char buf[SIZEOF_STR];
2865         va_list args;
2867         va_start(args, fmt);
2868         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2869                 buf[0] = 0;
2870         va_end(args);
2872         return buf[0] ? add_line_text(view, buf, type) : NULL;
2875 /*
2876  * View opening
2877  */
2879 enum open_flags {
2880         OPEN_DEFAULT = 0,       /* Use default view switching. */
2881         OPEN_SPLIT = 1,         /* Split current view. */
2882         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2883         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2884         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2885         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2886         OPEN_PREPARED = 32,     /* Open already prepared command. */
2887 };
2889 static void
2890 open_view(struct view *prev, enum request request, enum open_flags flags)
2892         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2893         bool split = !!(flags & OPEN_SPLIT);
2894         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2895         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2896         struct view *view = VIEW(request);
2897         int nviews = displayed_views();
2898         struct view *base_view = display[0];
2900         if (view == prev && nviews == 1 && !reload) {
2901                 report("Already in %s view", view->name);
2902                 return;
2903         }
2905         if (view->git_dir && !opt_git_dir[0]) {
2906                 report("The %s view is disabled in pager view", view->name);
2907                 return;
2908         }
2910         if (split) {
2911                 display[1] = view;
2912                 if (!backgrounded)
2913                         current_view = 1;
2914         } else if (!nomaximize) {
2915                 /* Maximize the current view. */
2916                 memset(display, 0, sizeof(display));
2917                 current_view = 0;
2918                 display[current_view] = view;
2919         }
2921         /* Resize the view when switching between split- and full-screen,
2922          * or when switching between two different full-screen views. */
2923         if (nviews != displayed_views() ||
2924             (nviews == 1 && base_view != display[0]))
2925                 resize_display();
2927         if (view->ops->open) {
2928                 if (!view->ops->open(view)) {
2929                         report("Failed to load %s view", view->name);
2930                         return;
2931                 }
2932                 restore_view_position(view);
2934         } else if ((reload || strcmp(view->vid, view->id)) &&
2935                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2936                 report("Failed to load %s view", view->name);
2937                 return;
2938         }
2940         if (split && prev->lineno - prev->offset >= prev->height) {
2941                 /* Take the title line into account. */
2942                 int lines = prev->lineno - prev->offset - prev->height + 1;
2944                 /* Scroll the view that was split if the current line is
2945                  * outside the new limited view. */
2946                 do_scroll_view(prev, lines);
2947         }
2949         if (prev && view != prev) {
2950                 if (split && !backgrounded) {
2951                         /* "Blur" the previous view. */
2952                         update_view_title(prev);
2953                 }
2955                 view->parent = prev;
2956         }
2958         if (view->pipe && view->lines == 0) {
2959                 /* Clear the old view and let the incremental updating refill
2960                  * the screen. */
2961                 werase(view->win);
2962                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2963                 report("");
2964         } else if (view_is_displayed(view)) {
2965                 redraw_view(view);
2966                 report("");
2967         }
2969         /* If the view is backgrounded the above calls to report()
2970          * won't redraw the view title. */
2971         if (backgrounded)
2972                 update_view_title(view);
2975 static void
2976 open_external_viewer(const char *argv[], const char *dir)
2978         def_prog_mode();           /* save current tty modes */
2979         endwin();                  /* restore original tty modes */
2980         run_io_fg(argv, dir);
2981         fprintf(stderr, "Press Enter to continue");
2982         getc(opt_tty);
2983         reset_prog_mode();
2984         redraw_display(TRUE);
2987 static void
2988 open_mergetool(const char *file)
2990         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2992         open_external_viewer(mergetool_argv, opt_cdup);
2995 static void
2996 open_editor(bool from_root, const char *file)
2998         const char *editor_argv[] = { "vi", file, NULL };
2999         const char *editor;
3001         editor = getenv("GIT_EDITOR");
3002         if (!editor && *opt_editor)
3003                 editor = opt_editor;
3004         if (!editor)
3005                 editor = getenv("VISUAL");
3006         if (!editor)
3007                 editor = getenv("EDITOR");
3008         if (!editor)
3009                 editor = "vi";
3011         editor_argv[0] = editor;
3012         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3015 static void
3016 open_run_request(enum request request)
3018         struct run_request *req = get_run_request(request);
3019         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3021         if (!req) {
3022                 report("Unknown run request");
3023                 return;
3024         }
3026         if (format_argv(argv, req->argv, FORMAT_ALL))
3027                 open_external_viewer(argv, NULL);
3028         free_argv(argv);
3031 /*
3032  * User request switch noodle
3033  */
3035 static int
3036 view_driver(struct view *view, enum request request)
3038         int i;
3040         if (request == REQ_NONE) {
3041                 doupdate();
3042                 return TRUE;
3043         }
3045         if (request > REQ_NONE) {
3046                 open_run_request(request);
3047                 /* FIXME: When all views can refresh always do this. */
3048                 if (view == VIEW(REQ_VIEW_STATUS) ||
3049                     view == VIEW(REQ_VIEW_MAIN) ||
3050                     view == VIEW(REQ_VIEW_LOG) ||
3051                     view == VIEW(REQ_VIEW_STAGE))
3052                         request = REQ_REFRESH;
3053                 else
3054                         return TRUE;
3055         }
3057         if (view && view->lines) {
3058                 request = view->ops->request(view, request, &view->line[view->lineno]);
3059                 if (request == REQ_NONE)
3060                         return TRUE;
3061         }
3063         switch (request) {
3064         case REQ_MOVE_UP:
3065         case REQ_MOVE_DOWN:
3066         case REQ_MOVE_PAGE_UP:
3067         case REQ_MOVE_PAGE_DOWN:
3068         case REQ_MOVE_FIRST_LINE:
3069         case REQ_MOVE_LAST_LINE:
3070                 move_view(view, request);
3071                 break;
3073         case REQ_SCROLL_LINE_DOWN:
3074         case REQ_SCROLL_LINE_UP:
3075         case REQ_SCROLL_PAGE_DOWN:
3076         case REQ_SCROLL_PAGE_UP:
3077                 scroll_view(view, request);
3078                 break;
3080         case REQ_VIEW_BLAME:
3081                 if (!opt_file[0]) {
3082                         report("No file chosen, press %s to open tree view",
3083                                get_key(REQ_VIEW_TREE));
3084                         break;
3085                 }
3086                 open_view(view, request, OPEN_DEFAULT);
3087                 break;
3089         case REQ_VIEW_BLOB:
3090                 if (!ref_blob[0]) {
3091                         report("No file chosen, press %s to open tree view",
3092                                get_key(REQ_VIEW_TREE));
3093                         break;
3094                 }
3095                 open_view(view, request, OPEN_DEFAULT);
3096                 break;
3098         case REQ_VIEW_PAGER:
3099                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3100                         report("No pager content, press %s to run command from prompt",
3101                                get_key(REQ_PROMPT));
3102                         break;
3103                 }
3104                 open_view(view, request, OPEN_DEFAULT);
3105                 break;
3107         case REQ_VIEW_STAGE:
3108                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3109                         report("No stage content, press %s to open the status view and choose file",
3110                                get_key(REQ_VIEW_STATUS));
3111                         break;
3112                 }
3113                 open_view(view, request, OPEN_DEFAULT);
3114                 break;
3116         case REQ_VIEW_STATUS:
3117                 if (opt_is_inside_work_tree == FALSE) {
3118                         report("The status view requires a working tree");
3119                         break;
3120                 }
3121                 open_view(view, request, OPEN_DEFAULT);
3122                 break;
3124         case REQ_VIEW_MAIN:
3125         case REQ_VIEW_DIFF:
3126         case REQ_VIEW_LOG:
3127         case REQ_VIEW_TREE:
3128         case REQ_VIEW_HELP:
3129                 open_view(view, request, OPEN_DEFAULT);
3130                 break;
3132         case REQ_NEXT:
3133         case REQ_PREVIOUS:
3134                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3136                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3137                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3138                    (view == VIEW(REQ_VIEW_DIFF) &&
3139                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3140                    (view == VIEW(REQ_VIEW_STAGE) &&
3141                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3142                    (view == VIEW(REQ_VIEW_BLOB) &&
3143                      view->parent == VIEW(REQ_VIEW_TREE))) {
3144                         int line;
3146                         view = view->parent;
3147                         line = view->lineno;
3148                         move_view(view, request);
3149                         if (view_is_displayed(view))
3150                                 update_view_title(view);
3151                         if (line != view->lineno)
3152                                 view->ops->request(view, REQ_ENTER,
3153                                                    &view->line[view->lineno]);
3155                 } else {
3156                         move_view(view, request);
3157                 }
3158                 break;
3160         case REQ_VIEW_NEXT:
3161         {
3162                 int nviews = displayed_views();
3163                 int next_view = (current_view + 1) % nviews;
3165                 if (next_view == current_view) {
3166                         report("Only one view is displayed");
3167                         break;
3168                 }
3170                 current_view = next_view;
3171                 /* Blur out the title of the previous view. */
3172                 update_view_title(view);
3173                 report("");
3174                 break;
3175         }
3176         case REQ_REFRESH:
3177                 report("Refreshing is not yet supported for the %s view", view->name);
3178                 break;
3180         case REQ_MAXIMIZE:
3181                 if (displayed_views() == 2)
3182                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3183                 break;
3185         case REQ_TOGGLE_LINENO:
3186                 toggle_view_option(&opt_line_number, "line numbers");
3187                 break;
3189         case REQ_TOGGLE_DATE:
3190                 toggle_view_option(&opt_date, "date display");
3191                 break;
3193         case REQ_TOGGLE_AUTHOR:
3194                 toggle_view_option(&opt_author, "author display");
3195                 break;
3197         case REQ_TOGGLE_REV_GRAPH:
3198                 toggle_view_option(&opt_rev_graph, "revision graph display");
3199                 break;
3201         case REQ_TOGGLE_REFS:
3202                 toggle_view_option(&opt_show_refs, "reference display");
3203                 break;
3205         case REQ_SEARCH:
3206         case REQ_SEARCH_BACK:
3207                 search_view(view, request);
3208                 break;
3210         case REQ_FIND_NEXT:
3211         case REQ_FIND_PREV:
3212                 find_next(view, request);
3213                 break;
3215         case REQ_STOP_LOADING:
3216                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3217                         view = &views[i];
3218                         if (view->pipe)
3219                                 report("Stopped loading the %s view", view->name),
3220                         end_update(view, TRUE);
3221                 }
3222                 break;
3224         case REQ_SHOW_VERSION:
3225                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3226                 return TRUE;
3228         case REQ_SCREEN_REDRAW:
3229                 redraw_display(TRUE);
3230                 break;
3232         case REQ_EDIT:
3233                 report("Nothing to edit");
3234                 break;
3236         case REQ_ENTER:
3237                 report("Nothing to enter");
3238                 break;
3240         case REQ_VIEW_CLOSE:
3241                 /* XXX: Mark closed views by letting view->parent point to the
3242                  * view itself. Parents to closed view should never be
3243                  * followed. */
3244                 if (view->parent &&
3245                     view->parent->parent != view->parent) {
3246                         memset(display, 0, sizeof(display));
3247                         current_view = 0;
3248                         display[current_view] = view->parent;
3249                         view->parent = view;
3250                         resize_display();
3251                         redraw_display(FALSE);
3252                         report("");
3253                         break;
3254                 }
3255                 /* Fall-through */
3256         case REQ_QUIT:
3257                 return FALSE;
3259         default:
3260                 report("Unknown key, press 'h' for help");
3261                 return TRUE;
3262         }
3264         return TRUE;
3268 /*
3269  * Pager backend
3270  */
3272 static bool
3273 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3275         char *text = line->data;
3277         if (opt_line_number && draw_lineno(view, lineno))
3278                 return TRUE;
3280         draw_text(view, line->type, text, TRUE);
3281         return TRUE;
3284 static bool
3285 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3287         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3288         char refbuf[SIZEOF_STR];
3289         char *ref = NULL;
3291         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3292                 ref = chomp_string(refbuf);
3294         if (!ref || !*ref)
3295                 return TRUE;
3297         /* This is the only fatal call, since it can "corrupt" the buffer. */
3298         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3299                 return FALSE;
3301         return TRUE;
3304 static void
3305 add_pager_refs(struct view *view, struct line *line)
3307         char buf[SIZEOF_STR];
3308         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3309         struct ref **refs;
3310         size_t bufpos = 0, refpos = 0;
3311         const char *sep = "Refs: ";
3312         bool is_tag = FALSE;
3314         assert(line->type == LINE_COMMIT);
3316         refs = get_refs(commit_id);
3317         if (!refs) {
3318                 if (view == VIEW(REQ_VIEW_DIFF))
3319                         goto try_add_describe_ref;
3320                 return;
3321         }
3323         do {
3324                 struct ref *ref = refs[refpos];
3325                 const char *fmt = ref->tag    ? "%s[%s]" :
3326                                   ref->remote ? "%s<%s>" : "%s%s";
3328                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3329                         return;
3330                 sep = ", ";
3331                 if (ref->tag)
3332                         is_tag = TRUE;
3333         } while (refs[refpos++]->next);
3335         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3336 try_add_describe_ref:
3337                 /* Add <tag>-g<commit_id> "fake" reference. */
3338                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3339                         return;
3340         }
3342         if (bufpos == 0)
3343                 return;
3345         add_line_text(view, buf, LINE_PP_REFS);
3348 static bool
3349 pager_read(struct view *view, char *data)
3351         struct line *line;
3353         if (!data)
3354                 return TRUE;
3356         line = add_line_text(view, data, get_line_type(data));
3357         if (!line)
3358                 return FALSE;
3360         if (line->type == LINE_COMMIT &&
3361             (view == VIEW(REQ_VIEW_DIFF) ||
3362              view == VIEW(REQ_VIEW_LOG)))
3363                 add_pager_refs(view, line);
3365         return TRUE;
3368 static enum request
3369 pager_request(struct view *view, enum request request, struct line *line)
3371         int split = 0;
3373         if (request != REQ_ENTER)
3374                 return request;
3376         if (line->type == LINE_COMMIT &&
3377            (view == VIEW(REQ_VIEW_LOG) ||
3378             view == VIEW(REQ_VIEW_PAGER))) {
3379                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3380                 split = 1;
3381         }
3383         /* Always scroll the view even if it was split. That way
3384          * you can use Enter to scroll through the log view and
3385          * split open each commit diff. */
3386         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3388         /* FIXME: A minor workaround. Scrolling the view will call report("")
3389          * but if we are scrolling a non-current view this won't properly
3390          * update the view title. */
3391         if (split)
3392                 update_view_title(view);
3394         return REQ_NONE;
3397 static bool
3398 pager_grep(struct view *view, struct line *line)
3400         regmatch_t pmatch;
3401         char *text = line->data;
3403         if (!*text)
3404                 return FALSE;
3406         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3407                 return FALSE;
3409         return TRUE;
3412 static void
3413 pager_select(struct view *view, struct line *line)
3415         if (line->type == LINE_COMMIT) {
3416                 char *text = (char *)line->data + STRING_SIZE("commit ");
3418                 if (view != VIEW(REQ_VIEW_PAGER))
3419                         string_copy_rev(view->ref, text);
3420                 string_copy_rev(ref_commit, text);
3421         }
3424 static struct view_ops pager_ops = {
3425         "line",
3426         NULL,
3427         NULL,
3428         pager_read,
3429         pager_draw,
3430         pager_request,
3431         pager_grep,
3432         pager_select,
3433 };
3435 static const char *log_argv[SIZEOF_ARG] = {
3436         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3437 };
3439 static enum request
3440 log_request(struct view *view, enum request request, struct line *line)
3442         switch (request) {
3443         case REQ_REFRESH:
3444                 load_refs();
3445                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3446                 return REQ_NONE;
3447         default:
3448                 return pager_request(view, request, line);
3449         }
3452 static struct view_ops log_ops = {
3453         "line",
3454         log_argv,
3455         NULL,
3456         pager_read,
3457         pager_draw,
3458         log_request,
3459         pager_grep,
3460         pager_select,
3461 };
3463 static const char *diff_argv[SIZEOF_ARG] = {
3464         "git", "show", "--pretty=fuller", "--no-color", "--root",
3465                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3466 };
3468 static struct view_ops diff_ops = {
3469         "line",
3470         diff_argv,
3471         NULL,
3472         pager_read,
3473         pager_draw,
3474         pager_request,
3475         pager_grep,
3476         pager_select,
3477 };
3479 /*
3480  * Help backend
3481  */
3483 static bool
3484 help_open(struct view *view)
3486         int lines = ARRAY_SIZE(req_info) + 2;
3487         int i;
3489         if (view->lines > 0)
3490                 return TRUE;
3492         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3493                 if (!req_info[i].request)
3494                         lines++;
3496         lines += run_requests + 1;
3498         view->line = calloc(lines, sizeof(*view->line));
3499         if (!view->line)
3500                 return FALSE;
3502         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3504         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3505                 const char *key;
3507                 if (req_info[i].request == REQ_NONE)
3508                         continue;
3510                 if (!req_info[i].request) {
3511                         add_line_text(view, "", LINE_DEFAULT);
3512                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3513                         continue;
3514                 }
3516                 key = get_key(req_info[i].request);
3517                 if (!*key)
3518                         key = "(no key defined)";
3520                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3521                                 key, req_info[i].help);
3522         }
3524         if (run_requests) {
3525                 add_line_text(view, "", LINE_DEFAULT);
3526                 add_line_text(view, "External commands:", LINE_DEFAULT);
3527         }
3529         for (i = 0; i < run_requests; i++) {
3530                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3531                 const char *key;
3532                 char cmd[SIZEOF_STR];
3533                 size_t bufpos;
3534                 int argc;
3536                 if (!req)
3537                         continue;
3539                 key = get_key_name(req->key);
3540                 if (!*key)
3541                         key = "(no key defined)";
3543                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3544                         if (!string_format_from(cmd, &bufpos, "%s%s",
3545                                                 argc ? " " : "", req->argv[argc]))
3546                                 return REQ_NONE;
3548                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3549                                 keymap_table[req->keymap].name, key, cmd);
3550         }
3552         return TRUE;
3555 static struct view_ops help_ops = {
3556         "line",
3557         NULL,
3558         help_open,
3559         NULL,
3560         pager_draw,
3561         pager_request,
3562         pager_grep,
3563         pager_select,
3564 };
3567 /*
3568  * Tree backend
3569  */
3571 struct tree_stack_entry {
3572         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3573         unsigned long lineno;           /* Line number to restore */
3574         char *name;                     /* Position of name in opt_path */
3575 };
3577 /* The top of the path stack. */
3578 static struct tree_stack_entry *tree_stack = NULL;
3579 unsigned long tree_lineno = 0;
3581 static void
3582 pop_tree_stack_entry(void)
3584         struct tree_stack_entry *entry = tree_stack;
3586         tree_lineno = entry->lineno;
3587         entry->name[0] = 0;
3588         tree_stack = entry->prev;
3589         free(entry);
3592 static void
3593 push_tree_stack_entry(const char *name, unsigned long lineno)
3595         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3596         size_t pathlen = strlen(opt_path);
3598         if (!entry)
3599                 return;
3601         entry->prev = tree_stack;
3602         entry->name = opt_path + pathlen;
3603         tree_stack = entry;
3605         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3606                 pop_tree_stack_entry();
3607                 return;
3608         }
3610         /* Move the current line to the first tree entry. */
3611         tree_lineno = 1;
3612         entry->lineno = lineno;
3615 /* Parse output from git-ls-tree(1):
3616  *
3617  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3618  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3619  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3620  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3621  */
3623 #define SIZEOF_TREE_ATTR \
3624         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3626 #define TREE_UP_FORMAT "040000 tree %s\t.."
3628 static const char *
3629 tree_path(struct line *line)
3631         const char *path = line->data;
3633         return path + SIZEOF_TREE_ATTR;
3636 static int
3637 tree_compare_entry(struct line *line1, struct line *line2)
3639         if (line1->type != line2->type)
3640                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3641         return strcmp(tree_path(line1), tree_path(line2));
3644 static bool
3645 tree_read(struct view *view, char *text)
3647         size_t textlen = text ? strlen(text) : 0;
3648         struct line *entry, *line;
3649         enum line_type type;
3651         if (!text)
3652                 return TRUE;
3653         if (textlen <= SIZEOF_TREE_ATTR)
3654                 return FALSE;
3656         type = text[STRING_SIZE("100644 ")] == 't'
3657              ? LINE_TREE_DIR : LINE_TREE_FILE;
3659         if (view->lines == 0 &&
3660             !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
3661                 return FALSE;
3663         /* Strip the path part ... */
3664         if (*opt_path) {
3665                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3666                 size_t striplen = strlen(opt_path);
3667                 char *path = text + SIZEOF_TREE_ATTR;
3669                 if (pathlen > striplen)
3670                         memmove(path, path + striplen,
3671                                 pathlen - striplen + 1);
3673                 /* Insert "link" to parent directory. */
3674                 if (view->lines == 1 &&
3675                     !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
3676                         return FALSE;
3677         }
3679         entry = add_line_text(view, text, type);
3680         if (!entry)
3681                 return FALSE;
3682         text = entry->data;
3684         /* Skip "Directory ..." and ".." line. */
3685         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3686                 if (tree_compare_entry(line, entry) <= 0)
3687                         continue;
3689                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3691                 line->data = text;
3692                 line->type = type;
3693                 for (; line <= entry; line++)
3694                         line->dirty = line->cleareol = 1;
3695                 return TRUE;
3696         }
3698         if (tree_lineno > view->lineno) {
3699                 view->lineno = tree_lineno;
3700                 tree_lineno = 0;
3701         }
3703         return TRUE;
3706 static void
3707 open_blob_editor()
3709         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3710         int fd = mkstemp(file);
3712         if (fd == -1)
3713                 report("Failed to create temporary file");
3714         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3715                 report("Failed to save blob data to file");
3716         else
3717                 open_editor(FALSE, file);
3718         if (fd != -1)
3719                 unlink(file);
3722 static enum request
3723 tree_request(struct view *view, enum request request, struct line *line)
3725         enum open_flags flags;
3727         switch (request) {
3728         case REQ_VIEW_BLAME:
3729                 if (line->type != LINE_TREE_FILE) {
3730                         report("Blame only supported for files");
3731                         return REQ_NONE;
3732                 }
3734                 string_copy(opt_ref, view->vid);
3735                 return request;
3737         case REQ_EDIT:
3738                 if (line->type != LINE_TREE_FILE) {
3739                         report("Edit only supported for files");
3740                 } else if (!is_head_commit(view->vid)) {
3741                         open_blob_editor();
3742                 } else {
3743                         open_editor(TRUE, opt_file);
3744                 }
3745                 return REQ_NONE;
3747         case REQ_TREE_PARENT:
3748                 if (!*opt_path) {
3749                         /* quit view if at top of tree */
3750                         return REQ_VIEW_CLOSE;
3751                 }
3752                 /* fake 'cd  ..' */
3753                 line = &view->line[1];
3754                 break;
3756         case REQ_ENTER:
3757                 break;
3759         default:
3760                 return request;
3761         }
3763         /* Cleanup the stack if the tree view is at a different tree. */
3764         while (!*opt_path && tree_stack)
3765                 pop_tree_stack_entry();
3767         switch (line->type) {
3768         case LINE_TREE_DIR:
3769                 /* Depending on whether it is a subdir or parent (updir?) link
3770                  * mangle the path buffer. */
3771                 if (line == &view->line[1] && *opt_path) {
3772                         pop_tree_stack_entry();
3774                 } else {
3775                         const char *basename = tree_path(line);
3777                         push_tree_stack_entry(basename, view->lineno);
3778                 }
3780                 /* Trees and subtrees share the same ID, so they are not not
3781                  * unique like blobs. */
3782                 flags = OPEN_RELOAD;
3783                 request = REQ_VIEW_TREE;
3784                 break;
3786         case LINE_TREE_FILE:
3787                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3788                 request = REQ_VIEW_BLOB;
3789                 break;
3791         default:
3792                 return TRUE;
3793         }
3795         open_view(view, request, flags);
3796         if (request == REQ_VIEW_TREE) {
3797                 view->lineno = tree_lineno;
3798         }
3800         return REQ_NONE;
3803 static void
3804 tree_select(struct view *view, struct line *line)
3806         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3808         if (line->type == LINE_TREE_FILE) {
3809                 string_copy_rev(ref_blob, text);
3810                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3812         } else if (line->type != LINE_TREE_DIR) {
3813                 return;
3814         }
3816         string_copy_rev(view->ref, text);
3819 static const char *tree_argv[SIZEOF_ARG] = {
3820         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3821 };
3823 static struct view_ops tree_ops = {
3824         "file",
3825         tree_argv,
3826         NULL,
3827         tree_read,
3828         pager_draw,
3829         tree_request,
3830         pager_grep,
3831         tree_select,
3832 };
3834 static bool
3835 blob_read(struct view *view, char *line)
3837         if (!line)
3838                 return TRUE;
3839         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3842 static enum request
3843 blob_request(struct view *view, enum request request, struct line *line)
3845         switch (request) {
3846         case REQ_EDIT:
3847                 open_blob_editor();
3848                 return REQ_NONE;
3849         default:
3850                 return pager_request(view, request, line);
3851         }
3854 static const char *blob_argv[SIZEOF_ARG] = {
3855         "git", "cat-file", "blob", "%(blob)", NULL
3856 };
3858 static struct view_ops blob_ops = {
3859         "line",
3860         blob_argv,
3861         NULL,
3862         blob_read,
3863         pager_draw,
3864         blob_request,
3865         pager_grep,
3866         pager_select,
3867 };
3869 /*
3870  * Blame backend
3871  *
3872  * Loading the blame view is a two phase job:
3873  *
3874  *  1. File content is read either using opt_file from the
3875  *     filesystem or using git-cat-file.
3876  *  2. Then blame information is incrementally added by
3877  *     reading output from git-blame.
3878  */
3880 static const char *blame_head_argv[] = {
3881         "git", "blame", "--incremental", "--", "%(file)", NULL
3882 };
3884 static const char *blame_ref_argv[] = {
3885         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3886 };
3888 static const char *blame_cat_file_argv[] = {
3889         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3890 };
3892 struct blame_commit {
3893         char id[SIZEOF_REV];            /* SHA1 ID. */
3894         char title[128];                /* First line of the commit message. */
3895         char author[75];                /* Author of the commit. */
3896         struct tm time;                 /* Date from the author ident. */
3897         char filename[128];             /* Name of file. */
3898 };
3900 struct blame {
3901         struct blame_commit *commit;
3902         char text[1];
3903 };
3905 static bool
3906 blame_open(struct view *view)
3908         if (*opt_ref || !io_open(&view->io, opt_file)) {
3909                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3910                         return FALSE;
3911         }
3913         setup_update(view, opt_file);
3914         string_format(view->ref, "%s ...", opt_file);
3916         return TRUE;
3919 static struct blame_commit *
3920 get_blame_commit(struct view *view, const char *id)
3922         size_t i;
3924         for (i = 0; i < view->lines; i++) {
3925                 struct blame *blame = view->line[i].data;
3927                 if (!blame->commit)
3928                         continue;
3930                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3931                         return blame->commit;
3932         }
3934         {
3935                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3937                 if (commit)
3938                         string_ncopy(commit->id, id, SIZEOF_REV);
3939                 return commit;
3940         }
3943 static bool
3944 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3946         const char *pos = *posref;
3948         *posref = NULL;
3949         pos = strchr(pos + 1, ' ');
3950         if (!pos || !isdigit(pos[1]))
3951                 return FALSE;
3952         *number = atoi(pos + 1);
3953         if (*number < min || *number > max)
3954                 return FALSE;
3956         *posref = pos;
3957         return TRUE;
3960 static struct blame_commit *
3961 parse_blame_commit(struct view *view, const char *text, int *blamed)
3963         struct blame_commit *commit;
3964         struct blame *blame;
3965         const char *pos = text + SIZEOF_REV - 1;
3966         size_t lineno;
3967         size_t group;
3969         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3970                 return NULL;
3972         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3973             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3974                 return NULL;
3976         commit = get_blame_commit(view, text);
3977         if (!commit)
3978                 return NULL;
3980         *blamed += group;
3981         while (group--) {
3982                 struct line *line = &view->line[lineno + group - 1];
3984                 blame = line->data;
3985                 blame->commit = commit;
3986                 line->dirty = 1;
3987         }
3989         return commit;
3992 static bool
3993 blame_read_file(struct view *view, const char *line, bool *read_file)
3995         if (!line) {
3996                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3997                 struct io io = {};
3999                 if (view->lines == 0 && !view->parent)
4000                         die("No blame exist for %s", view->vid);
4002                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4003                         report("Failed to load blame data");
4004                         return TRUE;
4005                 }
4007                 done_io(view->pipe);
4008                 view->io = io;
4009                 *read_file = FALSE;
4010                 return FALSE;
4012         } else {
4013                 size_t linelen = strlen(line);
4014                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4016                 blame->commit = NULL;
4017                 strncpy(blame->text, line, linelen);
4018                 blame->text[linelen] = 0;
4019                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4020         }
4023 static bool
4024 match_blame_header(const char *name, char **line)
4026         size_t namelen = strlen(name);
4027         bool matched = !strncmp(name, *line, namelen);
4029         if (matched)
4030                 *line += namelen;
4032         return matched;
4035 static bool
4036 blame_read(struct view *view, char *line)
4038         static struct blame_commit *commit = NULL;
4039         static int blamed = 0;
4040         static time_t author_time;
4041         static bool read_file = TRUE;
4043         if (read_file)
4044                 return blame_read_file(view, line, &read_file);
4046         if (!line) {
4047                 /* Reset all! */
4048                 commit = NULL;
4049                 blamed = 0;
4050                 read_file = TRUE;
4051                 string_format(view->ref, "%s", view->vid);
4052                 if (view_is_displayed(view)) {
4053                         update_view_title(view);
4054                         redraw_view_from(view, 0);
4055                 }
4056                 return TRUE;
4057         }
4059         if (!commit) {
4060                 commit = parse_blame_commit(view, line, &blamed);
4061                 string_format(view->ref, "%s %2d%%", view->vid,
4062                               blamed * 100 / view->lines);
4064         } else if (match_blame_header("author ", &line)) {
4065                 string_ncopy(commit->author, line, strlen(line));
4067         } else if (match_blame_header("author-time ", &line)) {
4068                 author_time = (time_t) atol(line);
4070         } else if (match_blame_header("author-tz ", &line)) {
4071                 long tz;
4073                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4074                 tz += ('0' - line[2]) * 60 * 60;
4075                 tz += ('0' - line[3]) * 60;
4076                 tz += ('0' - line[4]) * 60;
4078                 if (line[0] == '-')
4079                         tz = -tz;
4081                 author_time -= tz;
4082                 gmtime_r(&author_time, &commit->time);
4084         } else if (match_blame_header("summary ", &line)) {
4085                 string_ncopy(commit->title, line, strlen(line));
4087         } else if (match_blame_header("filename ", &line)) {
4088                 string_ncopy(commit->filename, line, strlen(line));
4089                 commit = NULL;
4090         }
4092         return TRUE;
4095 static bool
4096 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4098         struct blame *blame = line->data;
4099         struct tm *time = NULL;
4100         const char *id = NULL, *author = NULL;
4102         if (blame->commit && *blame->commit->filename) {
4103                 id = blame->commit->id;
4104                 author = blame->commit->author;
4105                 time = &blame->commit->time;
4106         }
4108         if (opt_date && draw_date(view, time))
4109                 return TRUE;
4111         if (opt_author &&
4112             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4113                 return TRUE;
4115         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4116                 return TRUE;
4118         if (draw_lineno(view, lineno))
4119                 return TRUE;
4121         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4122         return TRUE;
4125 static bool
4126 check_blame_commit(struct blame *blame)
4128         if (!blame->commit)
4129                 report("Commit data not loaded yet");
4130         else if (!strcmp(blame->commit->id, NULL_ID))
4131                 report("No commit exist for the selected line");
4132         else
4133                 return TRUE;
4134         return FALSE;
4137 static enum request
4138 blame_request(struct view *view, enum request request, struct line *line)
4140         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4141         struct blame *blame = line->data;
4143         switch (request) {
4144         case REQ_VIEW_BLAME:
4145                 if (check_blame_commit(blame)) {
4146                         string_copy(opt_ref, blame->commit->id);
4147                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4148                 }
4149                 break;
4151         case REQ_ENTER:
4152                 if (!blame->commit) {
4153                         report("No commit loaded yet");
4154                         break;
4155                 }
4157                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4158                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4159                         break;
4161                 if (!strcmp(blame->commit->id, NULL_ID)) {
4162                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4163                         const char *diff_index_argv[] = {
4164                                 "git", "diff-index", "--root", "--cached",
4165                                         "--patch-with-stat", "-C", "-M",
4166                                         "HEAD", "--", view->vid, NULL
4167                         };
4169                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4170                                 report("Failed to allocate diff command");
4171                                 break;
4172                         }
4173                         flags |= OPEN_PREPARED;
4174                 }
4176                 open_view(view, REQ_VIEW_DIFF, flags);
4177                 break;
4179         default:
4180                 return request;
4181         }
4183         return REQ_NONE;
4186 static bool
4187 blame_grep(struct view *view, struct line *line)
4189         struct blame *blame = line->data;
4190         struct blame_commit *commit = blame->commit;
4191         regmatch_t pmatch;
4193 #define MATCH(text, on)                                                 \
4194         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4196         if (commit) {
4197                 char buf[DATE_COLS + 1];
4199                 if (MATCH(commit->title, 1) ||
4200                     MATCH(commit->author, opt_author) ||
4201                     MATCH(commit->id, opt_date))
4202                         return TRUE;
4204                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4205                     MATCH(buf, 1))
4206                         return TRUE;
4207         }
4209         return MATCH(blame->text, 1);
4211 #undef MATCH
4214 static void
4215 blame_select(struct view *view, struct line *line)
4217         struct blame *blame = line->data;
4218         struct blame_commit *commit = blame->commit;
4220         if (!commit)
4221                 return;
4223         if (!strcmp(commit->id, NULL_ID))
4224                 string_ncopy(ref_commit, "HEAD", 4);
4225         else
4226                 string_copy_rev(ref_commit, commit->id);
4229 static struct view_ops blame_ops = {
4230         "line",
4231         NULL,
4232         blame_open,
4233         blame_read,
4234         blame_draw,
4235         blame_request,
4236         blame_grep,
4237         blame_select,
4238 };
4240 /*
4241  * Status backend
4242  */
4244 struct status {
4245         char status;
4246         struct {
4247                 mode_t mode;
4248                 char rev[SIZEOF_REV];
4249                 char name[SIZEOF_STR];
4250         } old;
4251         struct {
4252                 mode_t mode;
4253                 char rev[SIZEOF_REV];
4254                 char name[SIZEOF_STR];
4255         } new;
4256 };
4258 static char status_onbranch[SIZEOF_STR];
4259 static struct status stage_status;
4260 static enum line_type stage_line_type;
4261 static size_t stage_chunks;
4262 static int *stage_chunk;
4264 /* This should work even for the "On branch" line. */
4265 static inline bool
4266 status_has_none(struct view *view, struct line *line)
4268         return line < view->line + view->lines && !line[1].data;
4271 /* Get fields from the diff line:
4272  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4273  */
4274 static inline bool
4275 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4277         const char *old_mode = buf +  1;
4278         const char *new_mode = buf +  8;
4279         const char *old_rev  = buf + 15;
4280         const char *new_rev  = buf + 56;
4281         const char *status   = buf + 97;
4283         if (bufsize < 98 ||
4284             old_mode[-1] != ':' ||
4285             new_mode[-1] != ' ' ||
4286             old_rev[-1]  != ' ' ||
4287             new_rev[-1]  != ' ' ||
4288             status[-1]   != ' ')
4289                 return FALSE;
4291         file->status = *status;
4293         string_copy_rev(file->old.rev, old_rev);
4294         string_copy_rev(file->new.rev, new_rev);
4296         file->old.mode = strtoul(old_mode, NULL, 8);
4297         file->new.mode = strtoul(new_mode, NULL, 8);
4299         file->old.name[0] = file->new.name[0] = 0;
4301         return TRUE;
4304 static bool
4305 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4307         struct status *file = NULL;
4308         struct status *unmerged = NULL;
4309         char *buf;
4310         struct io io = {};
4312         if (!run_io(&io, argv, NULL, IO_RD))
4313                 return FALSE;
4315         add_line_data(view, NULL, type);
4317         while ((buf = io_get(&io, 0, TRUE))) {
4318                 if (!file) {
4319                         file = calloc(1, sizeof(*file));
4320                         if (!file || !add_line_data(view, file, type))
4321                                 goto error_out;
4322                 }
4324                 /* Parse diff info part. */
4325                 if (status) {
4326                         file->status = status;
4327                         if (status == 'A')
4328                                 string_copy(file->old.rev, NULL_ID);
4330                 } else if (!file->status) {
4331                         if (!status_get_diff(file, buf, strlen(buf)))
4332                                 goto error_out;
4334                         buf = io_get(&io, 0, TRUE);
4335                         if (!buf)
4336                                 break;
4338                         /* Collapse all 'M'odified entries that follow a
4339                          * associated 'U'nmerged entry. */
4340                         if (file->status == 'U') {
4341                                 unmerged = file;
4343                         } else if (unmerged) {
4344                                 int collapse = !strcmp(buf, unmerged->new.name);
4346                                 unmerged = NULL;
4347                                 if (collapse) {
4348                                         free(file);
4349                                         view->lines--;
4350                                         continue;
4351                                 }
4352                         }
4353                 }
4355                 /* Grab the old name for rename/copy. */
4356                 if (!*file->old.name &&
4357                     (file->status == 'R' || file->status == 'C')) {
4358                         string_ncopy(file->old.name, buf, strlen(buf));
4360                         buf = io_get(&io, 0, TRUE);
4361                         if (!buf)
4362                                 break;
4363                 }
4365                 /* git-ls-files just delivers a NUL separated list of
4366                  * file names similar to the second half of the
4367                  * git-diff-* output. */
4368                 string_ncopy(file->new.name, buf, strlen(buf));
4369                 if (!*file->old.name)
4370                         string_copy(file->old.name, file->new.name);
4371                 file = NULL;
4372         }
4374         if (io_error(&io)) {
4375 error_out:
4376                 done_io(&io);
4377                 return FALSE;
4378         }
4380         if (!view->line[view->lines - 1].data)
4381                 add_line_data(view, NULL, LINE_STAT_NONE);
4383         done_io(&io);
4384         return TRUE;
4387 /* Don't show unmerged entries in the staged section. */
4388 static const char *status_diff_index_argv[] = {
4389         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4390                              "--cached", "-M", "HEAD", NULL
4391 };
4393 static const char *status_diff_files_argv[] = {
4394         "git", "diff-files", "-z", NULL
4395 };
4397 static const char *status_list_other_argv[] = {
4398         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4399 };
4401 static const char *status_list_no_head_argv[] = {
4402         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4403 };
4405 static const char *update_index_argv[] = {
4406         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4407 };
4409 /* Restore the previous line number to stay in the context or select a
4410  * line with something that can be updated. */
4411 static void
4412 status_restore(struct view *view)
4414         if (view->p_lineno >= view->lines)
4415                 view->p_lineno = view->lines - 1;
4416         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4417                 view->p_lineno++;
4418         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4419                 view->p_lineno--;
4421         /* If the above fails, always skip the "On branch" line. */
4422         if (view->p_lineno < view->lines)
4423                 view->lineno = view->p_lineno;
4424         else
4425                 view->lineno = 1;
4427         if (view->lineno < view->offset)
4428                 view->offset = view->lineno;
4429         else if (view->offset + view->height <= view->lineno)
4430                 view->offset = view->lineno - view->height + 1;
4432         view->p_restore = FALSE;
4435 /* First parse staged info using git-diff-index(1), then parse unstaged
4436  * info using git-diff-files(1), and finally untracked files using
4437  * git-ls-files(1). */
4438 static bool
4439 status_open(struct view *view)
4441         reset_view(view);
4443         add_line_data(view, NULL, LINE_STAT_HEAD);
4444         if (is_initial_commit())
4445                 string_copy(status_onbranch, "Initial commit");
4446         else if (!*opt_head)
4447                 string_copy(status_onbranch, "Not currently on any branch");
4448         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4449                 return FALSE;
4451         run_io_bg(update_index_argv);
4453         if (is_initial_commit()) {
4454                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4455                         return FALSE;
4456         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4457                 return FALSE;
4458         }
4460         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4461             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4462                 return FALSE;
4464         /* Restore the exact position or use the specialized restore
4465          * mode? */
4466         if (!view->p_restore)
4467                 status_restore(view);
4468         return TRUE;
4471 static bool
4472 status_draw(struct view *view, struct line *line, unsigned int lineno)
4474         struct status *status = line->data;
4475         enum line_type type;
4476         const char *text;
4478         if (!status) {
4479                 switch (line->type) {
4480                 case LINE_STAT_STAGED:
4481                         type = LINE_STAT_SECTION;
4482                         text = "Changes to be committed:";
4483                         break;
4485                 case LINE_STAT_UNSTAGED:
4486                         type = LINE_STAT_SECTION;
4487                         text = "Changed but not updated:";
4488                         break;
4490                 case LINE_STAT_UNTRACKED:
4491                         type = LINE_STAT_SECTION;
4492                         text = "Untracked files:";
4493                         break;
4495                 case LINE_STAT_NONE:
4496                         type = LINE_DEFAULT;
4497                         text = "    (no files)";
4498                         break;
4500                 case LINE_STAT_HEAD:
4501                         type = LINE_STAT_HEAD;
4502                         text = status_onbranch;
4503                         break;
4505                 default:
4506                         return FALSE;
4507                 }
4508         } else {
4509                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4511                 buf[0] = status->status;
4512                 if (draw_text(view, line->type, buf, TRUE))
4513                         return TRUE;
4514                 type = LINE_DEFAULT;
4515                 text = status->new.name;
4516         }
4518         draw_text(view, type, text, TRUE);
4519         return TRUE;
4522 static enum request
4523 status_enter(struct view *view, struct line *line)
4525         struct status *status = line->data;
4526         const char *oldpath = status ? status->old.name : NULL;
4527         /* Diffs for unmerged entries are empty when passing the new
4528          * path, so leave it empty. */
4529         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4530         const char *info;
4531         enum open_flags split;
4532         struct view *stage = VIEW(REQ_VIEW_STAGE);
4534         if (line->type == LINE_STAT_NONE ||
4535             (!status && line[1].type == LINE_STAT_NONE)) {
4536                 report("No file to diff");
4537                 return REQ_NONE;
4538         }
4540         switch (line->type) {
4541         case LINE_STAT_STAGED:
4542                 if (is_initial_commit()) {
4543                         const char *no_head_diff_argv[] = {
4544                                 "git", "diff", "--no-color", "--patch-with-stat",
4545                                         "--", "/dev/null", newpath, NULL
4546                         };
4548                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4549                                 return REQ_QUIT;
4550                 } else {
4551                         const char *index_show_argv[] = {
4552                                 "git", "diff-index", "--root", "--patch-with-stat",
4553                                         "-C", "-M", "--cached", "HEAD", "--",
4554                                         oldpath, newpath, NULL
4555                         };
4557                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4558                                 return REQ_QUIT;
4559                 }
4561                 if (status)
4562                         info = "Staged changes to %s";
4563                 else
4564                         info = "Staged changes";
4565                 break;
4567         case LINE_STAT_UNSTAGED:
4568         {
4569                 const char *files_show_argv[] = {
4570                         "git", "diff-files", "--root", "--patch-with-stat",
4571                                 "-C", "-M", "--", oldpath, newpath, NULL
4572                 };
4574                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4575                         return REQ_QUIT;
4576                 if (status)
4577                         info = "Unstaged changes to %s";
4578                 else
4579                         info = "Unstaged changes";
4580                 break;
4581         }
4582         case LINE_STAT_UNTRACKED:
4583                 if (!newpath) {
4584                         report("No file to show");
4585                         return REQ_NONE;
4586                 }
4588                 if (!suffixcmp(status->new.name, -1, "/")) {
4589                         report("Cannot display a directory");
4590                         return REQ_NONE;
4591                 }
4593                 if (!prepare_update_file(stage, newpath))
4594                         return REQ_QUIT;
4595                 info = "Untracked file %s";
4596                 break;
4598         case LINE_STAT_HEAD:
4599                 return REQ_NONE;
4601         default:
4602                 die("line type %d not handled in switch", line->type);
4603         }
4605         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4606         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4607         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4608                 if (status) {
4609                         stage_status = *status;
4610                 } else {
4611                         memset(&stage_status, 0, sizeof(stage_status));
4612                 }
4614                 stage_line_type = line->type;
4615                 stage_chunks = 0;
4616                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4617         }
4619         return REQ_NONE;
4622 static bool
4623 status_exists(struct status *status, enum line_type type)
4625         struct view *view = VIEW(REQ_VIEW_STATUS);
4626         unsigned long lineno;
4628         for (lineno = 0; lineno < view->lines; lineno++) {
4629                 struct line *line = &view->line[lineno];
4630                 struct status *pos = line->data;
4632                 if (line->type != type)
4633                         continue;
4634                 if (!pos && (!status || !status->status) && line[1].data) {
4635                         select_view_line(view, lineno);
4636                         return TRUE;
4637                 }
4638                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4639                         select_view_line(view, lineno);
4640                         return TRUE;
4641                 }
4642         }
4644         return FALSE;
4648 static bool
4649 status_update_prepare(struct io *io, enum line_type type)
4651         const char *staged_argv[] = {
4652                 "git", "update-index", "-z", "--index-info", NULL
4653         };
4654         const char *others_argv[] = {
4655                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4656         };
4658         switch (type) {
4659         case LINE_STAT_STAGED:
4660                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4662         case LINE_STAT_UNSTAGED:
4663                 return run_io(io, others_argv, opt_cdup, IO_WR);
4665         case LINE_STAT_UNTRACKED:
4666                 return run_io(io, others_argv, NULL, IO_WR);
4668         default:
4669                 die("line type %d not handled in switch", type);
4670                 return FALSE;
4671         }
4674 static bool
4675 status_update_write(struct io *io, struct status *status, enum line_type type)
4677         char buf[SIZEOF_STR];
4678         size_t bufsize = 0;
4680         switch (type) {
4681         case LINE_STAT_STAGED:
4682                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4683                                         status->old.mode,
4684                                         status->old.rev,
4685                                         status->old.name, 0))
4686                         return FALSE;
4687                 break;
4689         case LINE_STAT_UNSTAGED:
4690         case LINE_STAT_UNTRACKED:
4691                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4692                         return FALSE;
4693                 break;
4695         default:
4696                 die("line type %d not handled in switch", type);
4697         }
4699         return io_write(io, buf, bufsize);
4702 static bool
4703 status_update_file(struct status *status, enum line_type type)
4705         struct io io = {};
4706         bool result;
4708         if (!status_update_prepare(&io, type))
4709                 return FALSE;
4711         result = status_update_write(&io, status, type);
4712         done_io(&io);
4713         return result;
4716 static bool
4717 status_update_files(struct view *view, struct line *line)
4719         struct io io = {};
4720         bool result = TRUE;
4721         struct line *pos = view->line + view->lines;
4722         int files = 0;
4723         int file, done;
4725         if (!status_update_prepare(&io, line->type))
4726                 return FALSE;
4728         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4729                 files++;
4731         for (file = 0, done = 0; result && file < files; line++, file++) {
4732                 int almost_done = file * 100 / files;
4734                 if (almost_done > done) {
4735                         done = almost_done;
4736                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4737                                       file, files, done);
4738                         update_view_title(view);
4739                 }
4740                 result = status_update_write(&io, line->data, line->type);
4741         }
4743         done_io(&io);
4744         return result;
4747 static bool
4748 status_update(struct view *view)
4750         struct line *line = &view->line[view->lineno];
4752         assert(view->lines);
4754         if (!line->data) {
4755                 /* This should work even for the "On branch" line. */
4756                 if (line < view->line + view->lines && !line[1].data) {
4757                         report("Nothing to update");
4758                         return FALSE;
4759                 }
4761                 if (!status_update_files(view, line + 1)) {
4762                         report("Failed to update file status");
4763                         return FALSE;
4764                 }
4766         } else if (!status_update_file(line->data, line->type)) {
4767                 report("Failed to update file status");
4768                 return FALSE;
4769         }
4771         return TRUE;
4774 static bool
4775 status_revert(struct status *status, enum line_type type, bool has_none)
4777         if (!status || type != LINE_STAT_UNSTAGED) {
4778                 if (type == LINE_STAT_STAGED) {
4779                         report("Cannot revert changes to staged files");
4780                 } else if (type == LINE_STAT_UNTRACKED) {
4781                         report("Cannot revert changes to untracked files");
4782                 } else if (has_none) {
4783                         report("Nothing to revert");
4784                 } else {
4785                         report("Cannot revert changes to multiple files");
4786                 }
4787                 return FALSE;
4789         } else {
4790                 const char *checkout_argv[] = {
4791                         "git", "checkout", "--", status->old.name, NULL
4792                 };
4794                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4795                         return FALSE;
4796                 return run_io_fg(checkout_argv, opt_cdup);
4797         }
4800 static enum request
4801 status_request(struct view *view, enum request request, struct line *line)
4803         struct status *status = line->data;
4805         switch (request) {
4806         case REQ_STATUS_UPDATE:
4807                 if (!status_update(view))
4808                         return REQ_NONE;
4809                 break;
4811         case REQ_STATUS_REVERT:
4812                 if (!status_revert(status, line->type, status_has_none(view, line)))
4813                         return REQ_NONE;
4814                 break;
4816         case REQ_STATUS_MERGE:
4817                 if (!status || status->status != 'U') {
4818                         report("Merging only possible for files with unmerged status ('U').");
4819                         return REQ_NONE;
4820                 }
4821                 open_mergetool(status->new.name);
4822                 break;
4824         case REQ_EDIT:
4825                 if (!status)
4826                         return request;
4827                 if (status->status == 'D') {
4828                         report("File has been deleted.");
4829                         return REQ_NONE;
4830                 }
4832                 open_editor(status->status != '?', status->new.name);
4833                 break;
4835         case REQ_VIEW_BLAME:
4836                 if (status) {
4837                         string_copy(opt_file, status->new.name);
4838                         opt_ref[0] = 0;
4839                 }
4840                 return request;
4842         case REQ_ENTER:
4843                 /* After returning the status view has been split to
4844                  * show the stage view. No further reloading is
4845                  * necessary. */
4846                 status_enter(view, line);
4847                 return REQ_NONE;
4849         case REQ_REFRESH:
4850                 /* Simply reload the view. */
4851                 break;
4853         default:
4854                 return request;
4855         }
4857         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4859         return REQ_NONE;
4862 static void
4863 status_select(struct view *view, struct line *line)
4865         struct status *status = line->data;
4866         char file[SIZEOF_STR] = "all files";
4867         const char *text;
4868         const char *key;
4870         if (status && !string_format(file, "'%s'", status->new.name))
4871                 return;
4873         if (!status && line[1].type == LINE_STAT_NONE)
4874                 line++;
4876         switch (line->type) {
4877         case LINE_STAT_STAGED:
4878                 text = "Press %s to unstage %s for commit";
4879                 break;
4881         case LINE_STAT_UNSTAGED:
4882                 text = "Press %s to stage %s for commit";
4883                 break;
4885         case LINE_STAT_UNTRACKED:
4886                 text = "Press %s to stage %s for addition";
4887                 break;
4889         case LINE_STAT_HEAD:
4890         case LINE_STAT_NONE:
4891                 text = "Nothing to update";
4892                 break;
4894         default:
4895                 die("line type %d not handled in switch", line->type);
4896         }
4898         if (status && status->status == 'U') {
4899                 text = "Press %s to resolve conflict in %s";
4900                 key = get_key(REQ_STATUS_MERGE);
4902         } else {
4903                 key = get_key(REQ_STATUS_UPDATE);
4904         }
4906         string_format(view->ref, text, key, file);
4909 static bool
4910 status_grep(struct view *view, struct line *line)
4912         struct status *status = line->data;
4913         enum { S_STATUS, S_NAME, S_END } state;
4914         char buf[2] = "?";
4915         regmatch_t pmatch;
4917         if (!status)
4918                 return FALSE;
4920         for (state = S_STATUS; state < S_END; state++) {
4921                 const char *text;
4923                 switch (state) {
4924                 case S_NAME:    text = status->new.name;        break;
4925                 case S_STATUS:
4926                         buf[0] = status->status;
4927                         text = buf;
4928                         break;
4930                 default:
4931                         return FALSE;
4932                 }
4934                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4935                         return TRUE;
4936         }
4938         return FALSE;
4941 static struct view_ops status_ops = {
4942         "file",
4943         NULL,
4944         status_open,
4945         NULL,
4946         status_draw,
4947         status_request,
4948         status_grep,
4949         status_select,
4950 };
4953 static bool
4954 stage_diff_write(struct io *io, struct line *line, struct line *end)
4956         while (line < end) {
4957                 if (!io_write(io, line->data, strlen(line->data)) ||
4958                     !io_write(io, "\n", 1))
4959                         return FALSE;
4960                 line++;
4961                 if (line->type == LINE_DIFF_CHUNK ||
4962                     line->type == LINE_DIFF_HEADER)
4963                         break;
4964         }
4966         return TRUE;
4969 static struct line *
4970 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4972         for (; view->line < line; line--)
4973                 if (line->type == type)
4974                         return line;
4976         return NULL;
4979 static bool
4980 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4982         const char *apply_argv[SIZEOF_ARG] = {
4983                 "git", "apply", "--whitespace=nowarn", NULL
4984         };
4985         struct line *diff_hdr;
4986         struct io io = {};
4987         int argc = 3;
4989         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4990         if (!diff_hdr)
4991                 return FALSE;
4993         if (!revert)
4994                 apply_argv[argc++] = "--cached";
4995         if (revert || stage_line_type == LINE_STAT_STAGED)
4996                 apply_argv[argc++] = "-R";
4997         apply_argv[argc++] = "-";
4998         apply_argv[argc++] = NULL;
4999         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5000                 return FALSE;
5002         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5003             !stage_diff_write(&io, chunk, view->line + view->lines))
5004                 chunk = NULL;
5006         done_io(&io);
5007         run_io_bg(update_index_argv);
5009         return chunk ? TRUE : FALSE;
5012 static bool
5013 stage_update(struct view *view, struct line *line)
5015         struct line *chunk = NULL;
5017         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5018                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5020         if (chunk) {
5021                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5022                         report("Failed to apply chunk");
5023                         return FALSE;
5024                 }
5026         } else if (!stage_status.status) {
5027                 view = VIEW(REQ_VIEW_STATUS);
5029                 for (line = view->line; line < view->line + view->lines; line++)
5030                         if (line->type == stage_line_type)
5031                                 break;
5033                 if (!status_update_files(view, line + 1)) {
5034                         report("Failed to update files");
5035                         return FALSE;
5036                 }
5038         } else if (!status_update_file(&stage_status, stage_line_type)) {
5039                 report("Failed to update file");
5040                 return FALSE;
5041         }
5043         return TRUE;
5046 static bool
5047 stage_revert(struct view *view, struct line *line)
5049         struct line *chunk = NULL;
5051         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5052                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5054         if (chunk) {
5055                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5056                         return FALSE;
5058                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5059                         report("Failed to revert chunk");
5060                         return FALSE;
5061                 }
5062                 return TRUE;
5064         } else {
5065                 return status_revert(stage_status.status ? &stage_status : NULL,
5066                                      stage_line_type, FALSE);
5067         }
5071 static void
5072 stage_next(struct view *view, struct line *line)
5074         int i;
5076         if (!stage_chunks) {
5077                 static size_t alloc = 0;
5078                 int *tmp;
5080                 for (line = view->line; line < view->line + view->lines; line++) {
5081                         if (line->type != LINE_DIFF_CHUNK)
5082                                 continue;
5084                         tmp = realloc_items(stage_chunk, &alloc,
5085                                             stage_chunks, sizeof(*tmp));
5086                         if (!tmp) {
5087                                 report("Allocation failure");
5088                                 return;
5089                         }
5091                         stage_chunk = tmp;
5092                         stage_chunk[stage_chunks++] = line - view->line;
5093                 }
5094         }
5096         for (i = 0; i < stage_chunks; i++) {
5097                 if (stage_chunk[i] > view->lineno) {
5098                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5099                         report("Chunk %d of %d", i + 1, stage_chunks);
5100                         return;
5101                 }
5102         }
5104         report("No next chunk found");
5107 static enum request
5108 stage_request(struct view *view, enum request request, struct line *line)
5110         switch (request) {
5111         case REQ_STATUS_UPDATE:
5112                 if (!stage_update(view, line))
5113                         return REQ_NONE;
5114                 break;
5116         case REQ_STATUS_REVERT:
5117                 if (!stage_revert(view, line))
5118                         return REQ_NONE;
5119                 break;
5121         case REQ_STAGE_NEXT:
5122                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5123                         report("File is untracked; press %s to add",
5124                                get_key(REQ_STATUS_UPDATE));
5125                         return REQ_NONE;
5126                 }
5127                 stage_next(view, line);
5128                 return REQ_NONE;
5130         case REQ_EDIT:
5131                 if (!stage_status.new.name[0])
5132                         return request;
5133                 if (stage_status.status == 'D') {
5134                         report("File has been deleted.");
5135                         return REQ_NONE;
5136                 }
5138                 open_editor(stage_status.status != '?', stage_status.new.name);
5139                 break;
5141         case REQ_REFRESH:
5142                 /* Reload everything ... */
5143                 break;
5145         case REQ_VIEW_BLAME:
5146                 if (stage_status.new.name[0]) {
5147                         string_copy(opt_file, stage_status.new.name);
5148                         opt_ref[0] = 0;
5149                 }
5150                 return request;
5152         case REQ_ENTER:
5153                 return pager_request(view, request, line);
5155         default:
5156                 return request;
5157         }
5159         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5160         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5162         /* Check whether the staged entry still exists, and close the
5163          * stage view if it doesn't. */
5164         if (!status_exists(&stage_status, stage_line_type)) {
5165                 status_restore(VIEW(REQ_VIEW_STATUS));
5166                 return REQ_VIEW_CLOSE;
5167         }
5169         if (stage_line_type == LINE_STAT_UNTRACKED) {
5170                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5171                         report("Cannot display a directory");
5172                         return REQ_NONE;
5173                 }
5175                 if (!prepare_update_file(view, stage_status.new.name)) {
5176                         report("Failed to open file: %s", strerror(errno));
5177                         return REQ_NONE;
5178                 }
5179         }
5180         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5182         return REQ_NONE;
5185 static struct view_ops stage_ops = {
5186         "line",
5187         NULL,
5188         NULL,
5189         pager_read,
5190         pager_draw,
5191         stage_request,
5192         pager_grep,
5193         pager_select,
5194 };
5197 /*
5198  * Revision graph
5199  */
5201 struct commit {
5202         char id[SIZEOF_REV];            /* SHA1 ID. */
5203         char title[128];                /* First line of the commit message. */
5204         char author[75];                /* Author of the commit. */
5205         struct tm time;                 /* Date from the author ident. */
5206         struct ref **refs;              /* Repository references. */
5207         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5208         size_t graph_size;              /* The width of the graph array. */
5209         bool has_parents;               /* Rewritten --parents seen. */
5210 };
5212 /* Size of rev graph with no  "padding" columns */
5213 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5215 struct rev_graph {
5216         struct rev_graph *prev, *next, *parents;
5217         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5218         size_t size;
5219         struct commit *commit;
5220         size_t pos;
5221         unsigned int boundary:1;
5222 };
5224 /* Parents of the commit being visualized. */
5225 static struct rev_graph graph_parents[4];
5227 /* The current stack of revisions on the graph. */
5228 static struct rev_graph graph_stacks[4] = {
5229         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5230         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5231         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5232         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5233 };
5235 static inline bool
5236 graph_parent_is_merge(struct rev_graph *graph)
5238         return graph->parents->size > 1;
5241 static inline void
5242 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5244         struct commit *commit = graph->commit;
5246         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5247                 commit->graph[commit->graph_size++] = symbol;
5250 static void
5251 clear_rev_graph(struct rev_graph *graph)
5253         graph->boundary = 0;
5254         graph->size = graph->pos = 0;
5255         graph->commit = NULL;
5256         memset(graph->parents, 0, sizeof(*graph->parents));
5259 static void
5260 done_rev_graph(struct rev_graph *graph)
5262         if (graph_parent_is_merge(graph) &&
5263             graph->pos < graph->size - 1 &&
5264             graph->next->size == graph->size + graph->parents->size - 1) {
5265                 size_t i = graph->pos + graph->parents->size - 1;
5267                 graph->commit->graph_size = i * 2;
5268                 while (i < graph->next->size - 1) {
5269                         append_to_rev_graph(graph, ' ');
5270                         append_to_rev_graph(graph, '\\');
5271                         i++;
5272                 }
5273         }
5275         clear_rev_graph(graph);
5278 static void
5279 push_rev_graph(struct rev_graph *graph, const char *parent)
5281         int i;
5283         /* "Collapse" duplicate parents lines.
5284          *
5285          * FIXME: This needs to also update update the drawn graph but
5286          * for now it just serves as a method for pruning graph lines. */
5287         for (i = 0; i < graph->size; i++)
5288                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5289                         return;
5291         if (graph->size < SIZEOF_REVITEMS) {
5292                 string_copy_rev(graph->rev[graph->size++], parent);
5293         }
5296 static chtype
5297 get_rev_graph_symbol(struct rev_graph *graph)
5299         chtype symbol;
5301         if (graph->boundary)
5302                 symbol = REVGRAPH_BOUND;
5303         else if (graph->parents->size == 0)
5304                 symbol = REVGRAPH_INIT;
5305         else if (graph_parent_is_merge(graph))
5306                 symbol = REVGRAPH_MERGE;
5307         else if (graph->pos >= graph->size)
5308                 symbol = REVGRAPH_BRANCH;
5309         else
5310                 symbol = REVGRAPH_COMMIT;
5312         return symbol;
5315 static void
5316 draw_rev_graph(struct rev_graph *graph)
5318         struct rev_filler {
5319                 chtype separator, line;
5320         };
5321         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5322         static struct rev_filler fillers[] = {
5323                 { ' ',  '|' },
5324                 { '`',  '.' },
5325                 { '\'', ' ' },
5326                 { '/',  ' ' },
5327         };
5328         chtype symbol = get_rev_graph_symbol(graph);
5329         struct rev_filler *filler;
5330         size_t i;
5332         if (opt_line_graphics)
5333                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5335         filler = &fillers[DEFAULT];
5337         for (i = 0; i < graph->pos; i++) {
5338                 append_to_rev_graph(graph, filler->line);
5339                 if (graph_parent_is_merge(graph->prev) &&
5340                     graph->prev->pos == i)
5341                         filler = &fillers[RSHARP];
5343                 append_to_rev_graph(graph, filler->separator);
5344         }
5346         /* Place the symbol for this revision. */
5347         append_to_rev_graph(graph, symbol);
5349         if (graph->prev->size > graph->size)
5350                 filler = &fillers[RDIAG];
5351         else
5352                 filler = &fillers[DEFAULT];
5354         i++;
5356         for (; i < graph->size; i++) {
5357                 append_to_rev_graph(graph, filler->separator);
5358                 append_to_rev_graph(graph, filler->line);
5359                 if (graph_parent_is_merge(graph->prev) &&
5360                     i < graph->prev->pos + graph->parents->size)
5361                         filler = &fillers[RSHARP];
5362                 if (graph->prev->size > graph->size)
5363                         filler = &fillers[LDIAG];
5364         }
5366         if (graph->prev->size > graph->size) {
5367                 append_to_rev_graph(graph, filler->separator);
5368                 if (filler->line != ' ')
5369                         append_to_rev_graph(graph, filler->line);
5370         }
5373 /* Prepare the next rev graph */
5374 static void
5375 prepare_rev_graph(struct rev_graph *graph)
5377         size_t i;
5379         /* First, traverse all lines of revisions up to the active one. */
5380         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5381                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5382                         break;
5384                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5385         }
5387         /* Interleave the new revision parent(s). */
5388         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5389                 push_rev_graph(graph->next, graph->parents->rev[i]);
5391         /* Lastly, put any remaining revisions. */
5392         for (i = graph->pos + 1; i < graph->size; i++)
5393                 push_rev_graph(graph->next, graph->rev[i]);
5396 static void
5397 update_rev_graph(struct view *view, struct rev_graph *graph)
5399         /* If this is the finalizing update ... */
5400         if (graph->commit)
5401                 prepare_rev_graph(graph);
5403         /* Graph visualization needs a one rev look-ahead,
5404          * so the first update doesn't visualize anything. */
5405         if (!graph->prev->commit)
5406                 return;
5408         if (view->lines > 2)
5409                 view->line[view->lines - 3].dirty = 1;
5410         if (view->lines > 1)
5411                 view->line[view->lines - 2].dirty = 1;
5412         draw_rev_graph(graph->prev);
5413         done_rev_graph(graph->prev->prev);
5417 /*
5418  * Main view backend
5419  */
5421 static const char *main_argv[SIZEOF_ARG] = {
5422         "git", "log", "--no-color", "--pretty=raw", "--parents",
5423                       "--topo-order", "%(head)", NULL
5424 };
5426 static bool
5427 main_draw(struct view *view, struct line *line, unsigned int lineno)
5429         struct commit *commit = line->data;
5431         if (!*commit->author)
5432                 return FALSE;
5434         if (opt_date && draw_date(view, &commit->time))
5435                 return TRUE;
5437         if (opt_author &&
5438             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5439                 return TRUE;
5441         if (opt_rev_graph && commit->graph_size &&
5442             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5443                 return TRUE;
5445         if (opt_show_refs && commit->refs) {
5446                 size_t i = 0;
5448                 do {
5449                         enum line_type type;
5451                         if (commit->refs[i]->head)
5452                                 type = LINE_MAIN_HEAD;
5453                         else if (commit->refs[i]->ltag)
5454                                 type = LINE_MAIN_LOCAL_TAG;
5455                         else if (commit->refs[i]->tag)
5456                                 type = LINE_MAIN_TAG;
5457                         else if (commit->refs[i]->tracked)
5458                                 type = LINE_MAIN_TRACKED;
5459                         else if (commit->refs[i]->remote)
5460                                 type = LINE_MAIN_REMOTE;
5461                         else
5462                                 type = LINE_MAIN_REF;
5464                         if (draw_text(view, type, "[", TRUE) ||
5465                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5466                             draw_text(view, type, "]", TRUE))
5467                                 return TRUE;
5469                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5470                                 return TRUE;
5471                 } while (commit->refs[i++]->next);
5472         }
5474         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5475         return TRUE;
5478 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5479 static bool
5480 main_read(struct view *view, char *line)
5482         static struct rev_graph *graph = graph_stacks;
5483         enum line_type type;
5484         struct commit *commit;
5486         if (!line) {
5487                 int i;
5489                 if (!view->lines && !view->parent)
5490                         die("No revisions match the given arguments.");
5491                 if (view->lines > 0) {
5492                         commit = view->line[view->lines - 1].data;
5493                         view->line[view->lines - 1].dirty = 1;
5494                         if (!*commit->author) {
5495                                 view->lines--;
5496                                 free(commit);
5497                                 graph->commit = NULL;
5498                         }
5499                 }
5500                 update_rev_graph(view, graph);
5502                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5503                         clear_rev_graph(&graph_stacks[i]);
5504                 return TRUE;
5505         }
5507         type = get_line_type(line);
5508         if (type == LINE_COMMIT) {
5509                 commit = calloc(1, sizeof(struct commit));
5510                 if (!commit)
5511                         return FALSE;
5513                 line += STRING_SIZE("commit ");
5514                 if (*line == '-') {
5515                         graph->boundary = 1;
5516                         line++;
5517                 }
5519                 string_copy_rev(commit->id, line);
5520                 commit->refs = get_refs(commit->id);
5521                 graph->commit = commit;
5522                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5524                 while ((line = strchr(line, ' '))) {
5525                         line++;
5526                         push_rev_graph(graph->parents, line);
5527                         commit->has_parents = TRUE;
5528                 }
5529                 return TRUE;
5530         }
5532         if (!view->lines)
5533                 return TRUE;
5534         commit = view->line[view->lines - 1].data;
5536         switch (type) {
5537         case LINE_PARENT:
5538                 if (commit->has_parents)
5539                         break;
5540                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5541                 break;
5543         case LINE_AUTHOR:
5544         {
5545                 /* Parse author lines where the name may be empty:
5546                  *      author  <email@address.tld> 1138474660 +0100
5547                  */
5548                 char *ident = line + STRING_SIZE("author ");
5549                 char *nameend = strchr(ident, '<');
5550                 char *emailend = strchr(ident, '>');
5552                 if (!nameend || !emailend)
5553                         break;
5555                 update_rev_graph(view, graph);
5556                 graph = graph->next;
5558                 *nameend = *emailend = 0;
5559                 ident = chomp_string(ident);
5560                 if (!*ident) {
5561                         ident = chomp_string(nameend + 1);
5562                         if (!*ident)
5563                                 ident = "Unknown";
5564                 }
5566                 string_ncopy(commit->author, ident, strlen(ident));
5567                 view->line[view->lines - 1].dirty = 1;
5569                 /* Parse epoch and timezone */
5570                 if (emailend[1] == ' ') {
5571                         char *secs = emailend + 2;
5572                         char *zone = strchr(secs, ' ');
5573                         time_t time = (time_t) atol(secs);
5575                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5576                                 long tz;
5578                                 zone++;
5579                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5580                                 tz += ('0' - zone[2]) * 60 * 60;
5581                                 tz += ('0' - zone[3]) * 60;
5582                                 tz += ('0' - zone[4]) * 60;
5584                                 if (zone[0] == '-')
5585                                         tz = -tz;
5587                                 time -= tz;
5588                         }
5590                         gmtime_r(&time, &commit->time);
5591                 }
5592                 break;
5593         }
5594         default:
5595                 /* Fill in the commit title if it has not already been set. */
5596                 if (commit->title[0])
5597                         break;
5599                 /* Require titles to start with a non-space character at the
5600                  * offset used by git log. */
5601                 if (strncmp(line, "    ", 4))
5602                         break;
5603                 line += 4;
5604                 /* Well, if the title starts with a whitespace character,
5605                  * try to be forgiving.  Otherwise we end up with no title. */
5606                 while (isspace(*line))
5607                         line++;
5608                 if (*line == '\0')
5609                         break;
5610                 /* FIXME: More graceful handling of titles; append "..." to
5611                  * shortened titles, etc. */
5613                 string_ncopy(commit->title, line, strlen(line));
5614                 view->line[view->lines - 1].dirty = 1;
5615         }
5617         return TRUE;
5620 static enum request
5621 main_request(struct view *view, enum request request, struct line *line)
5623         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5625         switch (request) {
5626         case REQ_ENTER:
5627                 open_view(view, REQ_VIEW_DIFF, flags);
5628                 break;
5629         case REQ_REFRESH:
5630                 load_refs();
5631                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5632                 break;
5633         default:
5634                 return request;
5635         }
5637         return REQ_NONE;
5640 static bool
5641 grep_refs(struct ref **refs, regex_t *regex)
5643         regmatch_t pmatch;
5644         size_t i = 0;
5646         if (!refs)
5647                 return FALSE;
5648         do {
5649                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5650                         return TRUE;
5651         } while (refs[i++]->next);
5653         return FALSE;
5656 static bool
5657 main_grep(struct view *view, struct line *line)
5659         struct commit *commit = line->data;
5660         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5661         char buf[DATE_COLS + 1];
5662         regmatch_t pmatch;
5664         for (state = S_TITLE; state < S_END; state++) {
5665                 char *text;
5667                 switch (state) {
5668                 case S_TITLE:   text = commit->title;   break;
5669                 case S_AUTHOR:
5670                         if (!opt_author)
5671                                 continue;
5672                         text = commit->author;
5673                         break;
5674                 case S_DATE:
5675                         if (!opt_date)
5676                                 continue;
5677                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5678                                 continue;
5679                         text = buf;
5680                         break;
5681                 case S_REFS:
5682                         if (!opt_show_refs)
5683                                 continue;
5684                         if (grep_refs(commit->refs, view->regex) == TRUE)
5685                                 return TRUE;
5686                         continue;
5687                 default:
5688                         return FALSE;
5689                 }
5691                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5692                         return TRUE;
5693         }
5695         return FALSE;
5698 static void
5699 main_select(struct view *view, struct line *line)
5701         struct commit *commit = line->data;
5703         string_copy_rev(view->ref, commit->id);
5704         string_copy_rev(ref_commit, view->ref);
5707 static struct view_ops main_ops = {
5708         "commit",
5709         main_argv,
5710         NULL,
5711         main_read,
5712         main_draw,
5713         main_request,
5714         main_grep,
5715         main_select,
5716 };
5719 /*
5720  * Unicode / UTF-8 handling
5721  *
5722  * NOTE: Much of the following code for dealing with unicode is derived from
5723  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5724  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5725  */
5727 /* I've (over)annotated a lot of code snippets because I am not entirely
5728  * confident that the approach taken by this small UTF-8 interface is correct.
5729  * --jonas */
5731 static inline int
5732 unicode_width(unsigned long c)
5734         if (c >= 0x1100 &&
5735            (c <= 0x115f                         /* Hangul Jamo */
5736             || c == 0x2329
5737             || c == 0x232a
5738             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5739                                                 /* CJK ... Yi */
5740             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5741             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5742             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5743             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5744             || (c >= 0xffe0  && c <= 0xffe6)
5745             || (c >= 0x20000 && c <= 0x2fffd)
5746             || (c >= 0x30000 && c <= 0x3fffd)))
5747                 return 2;
5749         if (c == '\t')
5750                 return opt_tab_size;
5752         return 1;
5755 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5756  * Illegal bytes are set one. */
5757 static const unsigned char utf8_bytes[256] = {
5758         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,
5759         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,
5760         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,
5761         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,
5762         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,
5763         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,
5764         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,
5765         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,
5766 };
5768 /* Decode UTF-8 multi-byte representation into a unicode character. */
5769 static inline unsigned long
5770 utf8_to_unicode(const char *string, size_t length)
5772         unsigned long unicode;
5774         switch (length) {
5775         case 1:
5776                 unicode  =   string[0];
5777                 break;
5778         case 2:
5779                 unicode  =  (string[0] & 0x1f) << 6;
5780                 unicode +=  (string[1] & 0x3f);
5781                 break;
5782         case 3:
5783                 unicode  =  (string[0] & 0x0f) << 12;
5784                 unicode += ((string[1] & 0x3f) << 6);
5785                 unicode +=  (string[2] & 0x3f);
5786                 break;
5787         case 4:
5788                 unicode  =  (string[0] & 0x0f) << 18;
5789                 unicode += ((string[1] & 0x3f) << 12);
5790                 unicode += ((string[2] & 0x3f) << 6);
5791                 unicode +=  (string[3] & 0x3f);
5792                 break;
5793         case 5:
5794                 unicode  =  (string[0] & 0x0f) << 24;
5795                 unicode += ((string[1] & 0x3f) << 18);
5796                 unicode += ((string[2] & 0x3f) << 12);
5797                 unicode += ((string[3] & 0x3f) << 6);
5798                 unicode +=  (string[4] & 0x3f);
5799                 break;
5800         case 6:
5801                 unicode  =  (string[0] & 0x01) << 30;
5802                 unicode += ((string[1] & 0x3f) << 24);
5803                 unicode += ((string[2] & 0x3f) << 18);
5804                 unicode += ((string[3] & 0x3f) << 12);
5805                 unicode += ((string[4] & 0x3f) << 6);
5806                 unicode +=  (string[5] & 0x3f);
5807                 break;
5808         default:
5809                 die("Invalid unicode length");
5810         }
5812         /* Invalid characters could return the special 0xfffd value but NUL
5813          * should be just as good. */
5814         return unicode > 0xffff ? 0 : unicode;
5817 /* Calculates how much of string can be shown within the given maximum width
5818  * and sets trimmed parameter to non-zero value if all of string could not be
5819  * shown. If the reserve flag is TRUE, it will reserve at least one
5820  * trailing character, which can be useful when drawing a delimiter.
5821  *
5822  * Returns the number of bytes to output from string to satisfy max_width. */
5823 static size_t
5824 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5826         const char *start = string;
5827         const char *end = strchr(string, '\0');
5828         unsigned char last_bytes = 0;
5829         size_t last_ucwidth = 0;
5831         *width = 0;
5832         *trimmed = 0;
5834         while (string < end) {
5835                 int c = *(unsigned char *) string;
5836                 unsigned char bytes = utf8_bytes[c];
5837                 size_t ucwidth;
5838                 unsigned long unicode;
5840                 if (string + bytes > end)
5841                         break;
5843                 /* Change representation to figure out whether
5844                  * it is a single- or double-width character. */
5846                 unicode = utf8_to_unicode(string, bytes);
5847                 /* FIXME: Graceful handling of invalid unicode character. */
5848                 if (!unicode)
5849                         break;
5851                 ucwidth = unicode_width(unicode);
5852                 *width  += ucwidth;
5853                 if (*width > max_width) {
5854                         *trimmed = 1;
5855                         *width -= ucwidth;
5856                         if (reserve && *width == max_width) {
5857                                 string -= last_bytes;
5858                                 *width -= last_ucwidth;
5859                         }
5860                         break;
5861                 }
5863                 string  += bytes;
5864                 last_bytes = bytes;
5865                 last_ucwidth = ucwidth;
5866         }
5868         return string - start;
5872 /*
5873  * Status management
5874  */
5876 /* Whether or not the curses interface has been initialized. */
5877 static bool cursed = FALSE;
5879 /* The status window is used for polling keystrokes. */
5880 static WINDOW *status_win;
5882 static bool status_empty = TRUE;
5884 /* Update status and title window. */
5885 static void
5886 report(const char *msg, ...)
5888         struct view *view = display[current_view];
5890         if (input_mode)
5891                 return;
5893         if (!view) {
5894                 char buf[SIZEOF_STR];
5895                 va_list args;
5897                 va_start(args, msg);
5898                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5899                         buf[sizeof(buf) - 1] = 0;
5900                         buf[sizeof(buf) - 2] = '.';
5901                         buf[sizeof(buf) - 3] = '.';
5902                         buf[sizeof(buf) - 4] = '.';
5903                 }
5904                 va_end(args);
5905                 die("%s", buf);
5906         }
5908         if (!status_empty || *msg) {
5909                 va_list args;
5911                 va_start(args, msg);
5913                 wmove(status_win, 0, 0);
5914                 if (*msg) {
5915                         vwprintw(status_win, msg, args);
5916                         status_empty = FALSE;
5917                 } else {
5918                         status_empty = TRUE;
5919                 }
5920                 wclrtoeol(status_win);
5921                 wrefresh(status_win);
5923                 va_end(args);
5924         }
5926         update_view_title(view);
5927         update_display_cursor(view);
5930 /* Controls when nodelay should be in effect when polling user input. */
5931 static void
5932 set_nonblocking_input(bool loading)
5934         static unsigned int loading_views;
5936         if ((loading == FALSE && loading_views-- == 1) ||
5937             (loading == TRUE  && loading_views++ == 0))
5938                 nodelay(status_win, loading);
5941 static void
5942 init_display(void)
5944         int x, y;
5946         /* Initialize the curses library */
5947         if (isatty(STDIN_FILENO)) {
5948                 cursed = !!initscr();
5949                 opt_tty = stdin;
5950         } else {
5951                 /* Leave stdin and stdout alone when acting as a pager. */
5952                 opt_tty = fopen("/dev/tty", "r+");
5953                 if (!opt_tty)
5954                         die("Failed to open /dev/tty");
5955                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5956         }
5958         if (!cursed)
5959                 die("Failed to initialize curses");
5961         nonl();         /* Tell curses not to do NL->CR/NL on output */
5962         cbreak();       /* Take input chars one at a time, no wait for \n */
5963         noecho();       /* Don't echo input */
5964         leaveok(stdscr, TRUE);
5966         if (has_colors())
5967                 init_colors();
5969         getmaxyx(stdscr, y, x);
5970         status_win = newwin(1, 0, y - 1, 0);
5971         if (!status_win)
5972                 die("Failed to create status window");
5974         /* Enable keyboard mapping */
5975         keypad(status_win, TRUE);
5976         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5978         TABSIZE = opt_tab_size;
5979         if (opt_line_graphics) {
5980                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5981         }
5984 static int
5985 get_input(bool prompting)
5987         struct view *view;
5988         int i, key;
5990         if (prompting)
5991                 input_mode = TRUE;
5993         while (true) {
5994                 foreach_view (view, i)
5995                         update_view(view);
5997                 /* Refresh, accept single keystroke of input */
5998                 key = wgetch(status_win);
6000                 /* wgetch() with nodelay() enabled returns ERR when
6001                  * there's no input. */
6002                 if (key == ERR) {
6003                         doupdate();
6005                 } else if (key == KEY_RESIZE) {
6006                         int height, width;
6008                         getmaxyx(stdscr, height, width);
6010                         /* Resize the status view and let the view driver take
6011                          * care of resizing the displayed views. */
6012                         resize_display();
6013                         redraw_display(TRUE);
6014                         wresize(status_win, 1, width);
6015                         mvwin(status_win, height - 1, 0);
6016                         wrefresh(status_win);
6018                 } else {
6019                         input_mode = FALSE;
6020                         return key;
6021                 }
6022         }
6025 static bool
6026 prompt_yesno(const char *prompt)
6028         enum { WAIT, STOP, CANCEL  } status = WAIT;
6029         bool answer = FALSE;
6031         while (status == WAIT) {
6032                 int key;
6034                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
6035                 wclrtoeol(status_win);
6037                 key = get_input(TRUE);
6038                 switch (key) {
6039                 case 'y':
6040                 case 'Y':
6041                         answer = TRUE;
6042                         status = STOP;
6043                         break;
6045                 case KEY_ESC:
6046                 case KEY_RETURN:
6047                 case KEY_ENTER:
6048                 case KEY_BACKSPACE:
6049                 case 'n':
6050                 case 'N':
6051                 case '\n':
6052                 default:
6053                         answer = FALSE;
6054                         status = CANCEL;
6055                 }
6056         }
6058         /* Clear the status window */
6059         status_empty = FALSE;
6060         report("");
6062         return answer;
6065 static char *
6066 read_prompt(const char *prompt)
6068         enum { READING, STOP, CANCEL } status = READING;
6069         static char buf[SIZEOF_STR];
6070         int pos = 0;
6072         while (status == READING) {
6073                 int key;
6075                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6076                 wclrtoeol(status_win);
6078                 key = get_input(TRUE);
6079                 switch (key) {
6080                 case KEY_RETURN:
6081                 case KEY_ENTER:
6082                 case '\n':
6083                         status = pos ? STOP : CANCEL;
6084                         break;
6086                 case KEY_BACKSPACE:
6087                         if (pos > 0)
6088                                 pos--;
6089                         else
6090                                 status = CANCEL;
6091                         break;
6093                 case KEY_ESC:
6094                         status = CANCEL;
6095                         break;
6097                 default:
6098                         if (pos >= sizeof(buf)) {
6099                                 report("Input string too long");
6100                                 return NULL;
6101                         }
6103                         if (isprint(key))
6104                                 buf[pos++] = (char) key;
6105                 }
6106         }
6108         /* Clear the status window */
6109         status_empty = FALSE;
6110         report("");
6112         if (status == CANCEL)
6113                 return NULL;
6115         buf[pos++] = 0;
6117         return buf;
6120 /*
6121  * Repository properties
6122  */
6124 static int
6125 git_properties(const char **argv, const char *separators,
6126                int (*read_property)(char *, size_t, char *, size_t))
6128         struct io io = {};
6130         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6131                 return read_properties(&io, separators, read_property);
6132         return ERR;
6135 static struct ref *refs = NULL;
6136 static size_t refs_alloc = 0;
6137 static size_t refs_size = 0;
6139 /* Id <-> ref store */
6140 static struct ref ***id_refs = NULL;
6141 static size_t id_refs_alloc = 0;
6142 static size_t id_refs_size = 0;
6144 static int
6145 compare_refs(const void *ref1_, const void *ref2_)
6147         const struct ref *ref1 = *(const struct ref **)ref1_;
6148         const struct ref *ref2 = *(const struct ref **)ref2_;
6150         if (ref1->tag != ref2->tag)
6151                 return ref2->tag - ref1->tag;
6152         if (ref1->ltag != ref2->ltag)
6153                 return ref2->ltag - ref2->ltag;
6154         if (ref1->head != ref2->head)
6155                 return ref2->head - ref1->head;
6156         if (ref1->tracked != ref2->tracked)
6157                 return ref2->tracked - ref1->tracked;
6158         if (ref1->remote != ref2->remote)
6159                 return ref2->remote - ref1->remote;
6160         return strcmp(ref1->name, ref2->name);
6163 static struct ref **
6164 get_refs(const char *id)
6166         struct ref ***tmp_id_refs;
6167         struct ref **ref_list = NULL;
6168         size_t ref_list_alloc = 0;
6169         size_t ref_list_size = 0;
6170         size_t i;
6172         for (i = 0; i < id_refs_size; i++)
6173                 if (!strcmp(id, id_refs[i][0]->id))
6174                         return id_refs[i];
6176         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6177                                     sizeof(*id_refs));
6178         if (!tmp_id_refs)
6179                 return NULL;
6181         id_refs = tmp_id_refs;
6183         for (i = 0; i < refs_size; i++) {
6184                 struct ref **tmp;
6186                 if (strcmp(id, refs[i].id))
6187                         continue;
6189                 tmp = realloc_items(ref_list, &ref_list_alloc,
6190                                     ref_list_size + 1, sizeof(*ref_list));
6191                 if (!tmp) {
6192                         if (ref_list)
6193                                 free(ref_list);
6194                         return NULL;
6195                 }
6197                 ref_list = tmp;
6198                 ref_list[ref_list_size] = &refs[i];
6199                 /* XXX: The properties of the commit chains ensures that we can
6200                  * safely modify the shared ref. The repo references will
6201                  * always be similar for the same id. */
6202                 ref_list[ref_list_size]->next = 1;
6204                 ref_list_size++;
6205         }
6207         if (ref_list) {
6208                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6209                 ref_list[ref_list_size - 1]->next = 0;
6210                 id_refs[id_refs_size++] = ref_list;
6211         }
6213         return ref_list;
6216 static int
6217 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6219         struct ref *ref;
6220         bool tag = FALSE;
6221         bool ltag = FALSE;
6222         bool remote = FALSE;
6223         bool tracked = FALSE;
6224         bool check_replace = FALSE;
6225         bool head = FALSE;
6227         if (!prefixcmp(name, "refs/tags/")) {
6228                 if (!suffixcmp(name, namelen, "^{}")) {
6229                         namelen -= 3;
6230                         name[namelen] = 0;
6231                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6232                                 check_replace = TRUE;
6233                 } else {
6234                         ltag = TRUE;
6235                 }
6237                 tag = TRUE;
6238                 namelen -= STRING_SIZE("refs/tags/");
6239                 name    += STRING_SIZE("refs/tags/");
6241         } else if (!prefixcmp(name, "refs/remotes/")) {
6242                 remote = TRUE;
6243                 namelen -= STRING_SIZE("refs/remotes/");
6244                 name    += STRING_SIZE("refs/remotes/");
6245                 tracked  = !strcmp(opt_remote, name);
6247         } else if (!prefixcmp(name, "refs/heads/")) {
6248                 namelen -= STRING_SIZE("refs/heads/");
6249                 name    += STRING_SIZE("refs/heads/");
6250                 head     = !strncmp(opt_head, name, namelen);
6252         } else if (!strcmp(name, "HEAD")) {
6253                 string_ncopy(opt_head_rev, id, idlen);
6254                 return OK;
6255         }
6257         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6258                 /* it's an annotated tag, replace the previous sha1 with the
6259                  * resolved commit id; relies on the fact git-ls-remote lists
6260                  * the commit id of an annotated tag right before the commit id
6261                  * it points to. */
6262                 refs[refs_size - 1].ltag = ltag;
6263                 string_copy_rev(refs[refs_size - 1].id, id);
6265                 return OK;
6266         }
6267         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6268         if (!refs)
6269                 return ERR;
6271         ref = &refs[refs_size++];
6272         ref->name = malloc(namelen + 1);
6273         if (!ref->name)
6274                 return ERR;
6276         strncpy(ref->name, name, namelen);
6277         ref->name[namelen] = 0;
6278         ref->head = head;
6279         ref->tag = tag;
6280         ref->ltag = ltag;
6281         ref->remote = remote;
6282         ref->tracked = tracked;
6283         string_copy_rev(ref->id, id);
6285         return OK;
6288 static int
6289 load_refs(void)
6291         static const char *ls_remote_argv[SIZEOF_ARG] = {
6292                 "git", "ls-remote", ".", NULL
6293         };
6294         static bool init = FALSE;
6296         if (!init) {
6297                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6298                 init = TRUE;
6299         }
6301         if (!*opt_git_dir)
6302                 return OK;
6304         while (refs_size > 0)
6305                 free(refs[--refs_size].name);
6306         while (id_refs_size > 0)
6307                 free(id_refs[--id_refs_size]);
6309         return git_properties(ls_remote_argv, "\t", read_ref);
6312 static int
6313 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6315         if (!strcmp(name, "i18n.commitencoding"))
6316                 string_ncopy(opt_encoding, value, valuelen);
6318         if (!strcmp(name, "core.editor"))
6319                 string_ncopy(opt_editor, value, valuelen);
6321         /* branch.<head>.remote */
6322         if (*opt_head &&
6323             !strncmp(name, "branch.", 7) &&
6324             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6325             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6326                 string_ncopy(opt_remote, value, valuelen);
6328         if (*opt_head && *opt_remote &&
6329             !strncmp(name, "branch.", 7) &&
6330             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6331             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6332                 size_t from = strlen(opt_remote);
6334                 if (!prefixcmp(value, "refs/heads/")) {
6335                         value += STRING_SIZE("refs/heads/");
6336                         valuelen -= STRING_SIZE("refs/heads/");
6337                 }
6339                 if (!string_format_from(opt_remote, &from, "/%s", value))
6340                         opt_remote[0] = 0;
6341         }
6343         return OK;
6346 static int
6347 load_git_config(void)
6349         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6351         return git_properties(config_list_argv, "=", read_repo_config_option);
6354 static int
6355 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6357         if (!opt_git_dir[0]) {
6358                 string_ncopy(opt_git_dir, name, namelen);
6360         } else if (opt_is_inside_work_tree == -1) {
6361                 /* This can be 3 different values depending on the
6362                  * version of git being used. If git-rev-parse does not
6363                  * understand --is-inside-work-tree it will simply echo
6364                  * the option else either "true" or "false" is printed.
6365                  * Default to true for the unknown case. */
6366                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6367         } else {
6368                 string_ncopy(opt_cdup, name, namelen);
6369         }
6371         return OK;
6374 static int
6375 load_repo_info(void)
6377         const char *head_argv[] = {
6378                 "git", "symbolic-ref", "HEAD", NULL
6379         };
6380         const char *rev_parse_argv[] = {
6381                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6382                         "--show-cdup", NULL
6383         };
6385         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6386                 chomp_string(opt_head);
6387                 if (!prefixcmp(opt_head, "refs/heads/")) {
6388                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6390                         memmove(opt_head, offset, strlen(offset) + 1);
6391                 }
6392         }
6394         return git_properties(rev_parse_argv, "=", read_repo_info);
6397 static int
6398 read_properties(struct io *io, const char *separators,
6399                 int (*read_property)(char *, size_t, char *, size_t))
6401         char *name;
6402         int state = OK;
6404         if (!start_io(io))
6405                 return ERR;
6407         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6408                 char *value;
6409                 size_t namelen;
6410                 size_t valuelen;
6412                 name = chomp_string(name);
6413                 namelen = strcspn(name, separators);
6415                 if (name[namelen]) {
6416                         name[namelen] = 0;
6417                         value = chomp_string(name + namelen + 1);
6418                         valuelen = strlen(value);
6420                 } else {
6421                         value = "";
6422                         valuelen = 0;
6423                 }
6425                 state = read_property(name, namelen, value, valuelen);
6426         }
6428         if (state != ERR && io_error(io))
6429                 state = ERR;
6430         done_io(io);
6432         return state;
6436 /*
6437  * Main
6438  */
6440 static void __NORETURN
6441 quit(int sig)
6443         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6444         if (cursed)
6445                 endwin();
6446         exit(0);
6449 static void __NORETURN
6450 die(const char *err, ...)
6452         va_list args;
6454         endwin();
6456         va_start(args, err);
6457         fputs("tig: ", stderr);
6458         vfprintf(stderr, err, args);
6459         fputs("\n", stderr);
6460         va_end(args);
6462         exit(1);
6465 static void
6466 warn(const char *msg, ...)
6468         va_list args;
6470         va_start(args, msg);
6471         fputs("tig warning: ", stderr);
6472         vfprintf(stderr, msg, args);
6473         fputs("\n", stderr);
6474         va_end(args);
6477 int
6478 main(int argc, const char *argv[])
6480         const char **run_argv = NULL;
6481         struct view *view;
6482         enum request request;
6483         size_t i;
6485         signal(SIGINT, quit);
6487         if (setlocale(LC_ALL, "")) {
6488                 char *codeset = nl_langinfo(CODESET);
6490                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6491         }
6493         if (load_repo_info() == ERR)
6494                 die("Failed to load repo info.");
6496         if (load_options() == ERR)
6497                 die("Failed to load user config.");
6499         if (load_git_config() == ERR)
6500                 die("Failed to load repo config.");
6502         request = parse_options(argc, argv, &run_argv);
6503         if (request == REQ_NONE)
6504                 return 0;
6506         /* Require a git repository unless when running in pager mode. */
6507         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6508                 die("Not a git repository");
6510         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6511                 opt_utf8 = FALSE;
6513         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6514                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6515                 if (opt_iconv == ICONV_NONE)
6516                         die("Failed to initialize character set conversion");
6517         }
6519         if (load_refs() == ERR)
6520                 die("Failed to load refs.");
6522         foreach_view (view, i)
6523                 argv_from_env(view->ops->argv, view->cmd_env);
6525         init_display();
6527         if (request == REQ_VIEW_PAGER || run_argv) {
6528                 if (request == REQ_VIEW_PAGER)
6529                         io_open(&VIEW(request)->io, "");
6530                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6531                         die("Failed to format arguments");
6532                 open_view(NULL, request, OPEN_PREPARED);
6533                 request = REQ_NONE;
6534         }
6536         while (view_driver(display[current_view], request)) {
6537                 int key = get_input(FALSE);
6539                 view = display[current_view];
6540                 request = get_keybinding(view->keymap, key);
6542                 /* Some low-level request handling. This keeps access to
6543                  * status_win restricted. */
6544                 switch (request) {
6545                 case REQ_PROMPT:
6546                 {
6547                         char *cmd = read_prompt(":");
6549                         if (cmd) {
6550                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6551                                 const char *argv[SIZEOF_ARG] = { "git" };
6552                                 int argc = 1;
6554                                 /* When running random commands, initially show the
6555                                  * command in the title. However, it maybe later be
6556                                  * overwritten if a commit line is selected. */
6557                                 string_ncopy(next->ref, cmd, strlen(cmd));
6559                                 if (!argv_from_string(argv, &argc, cmd)) {
6560                                         report("Too many arguments");
6561                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6562                                         report("Failed to format command");
6563                                 } else {
6564                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6565                                 }
6566                         }
6568                         request = REQ_NONE;
6569                         break;
6570                 }
6571                 case REQ_SEARCH:
6572                 case REQ_SEARCH_BACK:
6573                 {
6574                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6575                         char *search = read_prompt(prompt);
6577                         if (search)
6578                                 string_ncopy(opt_search, search, strlen(search));
6579                         else
6580                                 request = REQ_NONE;
6581                         break;
6582                 }
6583                 default:
6584                         break;
6585                 }
6586         }
6588         quit(0);
6590         return 0;