Code

Add support for opening any blob in an editor
[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_(SCREEN_RESIZE,     "Resize the screen"), \
713         REQ_(SHOW_VERSION,      "Show version information"), \
714         REQ_(STOP_LOADING,      "Stop all loading views"), \
715         REQ_(EDIT,              "Open in editor"), \
716         REQ_(NONE,              "Do nothing")
719 /* User action requests. */
720 enum request {
721 #define REQ_GROUP(help)
722 #define REQ_(req, help) REQ_##req
724         /* Offset all requests to avoid conflicts with ncurses getch values. */
725         REQ_OFFSET = KEY_MAX + 1,
726         REQ_INFO
728 #undef  REQ_GROUP
729 #undef  REQ_
730 };
732 struct request_info {
733         enum request request;
734         const char *name;
735         int namelen;
736         const char *help;
737 };
739 static struct request_info req_info[] = {
740 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
741 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
742         REQ_INFO
743 #undef  REQ_GROUP
744 #undef  REQ_
745 };
747 static enum request
748 get_request(const char *name)
750         int namelen = strlen(name);
751         int i;
753         for (i = 0; i < ARRAY_SIZE(req_info); i++)
754                 if (req_info[i].namelen == namelen &&
755                     !string_enum_compare(req_info[i].name, name, namelen))
756                         return req_info[i].request;
758         return REQ_NONE;
762 /*
763  * Options
764  */
766 static const char usage[] =
767 "tig " TIG_VERSION " (" __DATE__ ")\n"
768 "\n"
769 "Usage: tig        [options] [revs] [--] [paths]\n"
770 "   or: tig show   [options] [revs] [--] [paths]\n"
771 "   or: tig blame  [rev] path\n"
772 "   or: tig status\n"
773 "   or: tig <      [git command output]\n"
774 "\n"
775 "Options:\n"
776 "  -v, --version   Show version and exit\n"
777 "  -h, --help      Show help message and exit";
779 /* Option and state variables. */
780 static bool opt_date                    = TRUE;
781 static bool opt_author                  = TRUE;
782 static bool opt_line_number             = FALSE;
783 static bool opt_line_graphics           = TRUE;
784 static bool opt_rev_graph               = FALSE;
785 static bool opt_show_refs               = TRUE;
786 static int opt_num_interval             = NUMBER_INTERVAL;
787 static int opt_tab_size                 = TAB_SIZE;
788 static int opt_author_cols              = AUTHOR_COLS-1;
789 static char opt_path[SIZEOF_STR]        = "";
790 static char opt_file[SIZEOF_STR]        = "";
791 static char opt_ref[SIZEOF_REF]         = "";
792 static char opt_head[SIZEOF_REF]        = "";
793 static char opt_head_rev[SIZEOF_REV]    = "";
794 static char opt_remote[SIZEOF_REF]      = "";
795 static char opt_encoding[20]            = "UTF-8";
796 static bool opt_utf8                    = TRUE;
797 static char opt_codeset[20]             = "UTF-8";
798 static iconv_t opt_iconv                = ICONV_NONE;
799 static char opt_search[SIZEOF_STR]      = "";
800 static char opt_cdup[SIZEOF_STR]        = "";
801 static char opt_git_dir[SIZEOF_STR]     = "";
802 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
803 static char opt_editor[SIZEOF_STR]      = "";
804 static FILE *opt_tty                    = NULL;
806 #define is_initial_commit()     (!*opt_head_rev)
807 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
809 static enum request
810 parse_options(int argc, const char *argv[], const char ***run_argv)
812         enum request request = REQ_VIEW_MAIN;
813         const char *subcommand;
814         bool seen_dashdash = FALSE;
815         /* XXX: This is vulnerable to the user overriding options
816          * required for the main view parser. */
817         static const char *custom_argv[SIZEOF_ARG] = {
818                 "git", "log", "--no-color", "--pretty=raw", "--parents",
819                         "--topo-order", NULL
820         };
821         int i, j = 6;
823         if (!isatty(STDIN_FILENO))
824                 return REQ_VIEW_PAGER;
826         if (argc <= 1)
827                 return REQ_VIEW_MAIN;
829         subcommand = argv[1];
830         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
831                 if (!strcmp(subcommand, "-S"))
832                         warn("`-S' has been deprecated; use `tig status' instead");
833                 if (argc > 2)
834                         warn("ignoring arguments after `%s'", subcommand);
835                 return REQ_VIEW_STATUS;
837         } else if (!strcmp(subcommand, "blame")) {
838                 if (argc <= 2 || argc > 4)
839                         die("invalid number of options to blame\n\n%s", usage);
841                 i = 2;
842                 if (argc == 4) {
843                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
844                         i++;
845                 }
847                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
848                 return REQ_VIEW_BLAME;
850         } else if (!strcmp(subcommand, "show")) {
851                 request = REQ_VIEW_DIFF;
853         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
854                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
855                 warn("`tig %s' has been deprecated", subcommand);
857         } else {
858                 subcommand = NULL;
859         }
861         if (subcommand) {
862                 custom_argv[1] = subcommand;
863                 j = 2;
864         }
866         for (i = 1 + !!subcommand; i < argc; i++) {
867                 const char *opt = argv[i];
869                 if (seen_dashdash || !strcmp(opt, "--")) {
870                         seen_dashdash = TRUE;
872                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
873                         printf("tig version %s\n", TIG_VERSION);
874                         return REQ_NONE;
876                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
877                         printf("%s\n", usage);
878                         return REQ_NONE;
879                 }
881                 custom_argv[j++] = opt;
882                 if (j >= ARRAY_SIZE(custom_argv))
883                         die("command too long");
884         }
886         custom_argv[j] = NULL;
887         *run_argv = custom_argv;
889         return request;
893 /*
894  * Line-oriented content detection.
895  */
897 #define LINE_INFO \
898 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
899 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
900 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
901 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
902 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
903 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
905 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
906 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
907 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
908 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
909 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
910 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
911 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
912 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
913 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
914 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
915 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
916 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
917 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
918 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
919 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
920 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
921 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
922 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
924 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
927 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
928 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
929 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
930 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
932 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
933 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
934 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
935 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
936 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
937 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
938 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
939 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
940 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
941 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
942 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
943 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
944 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
945 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
946 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
947 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
948 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
949 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
950 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
951 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
953 enum line_type {
954 #define LINE(type, line, fg, bg, attr) \
955         LINE_##type
956         LINE_INFO,
957         LINE_NONE
958 #undef  LINE
959 };
961 struct line_info {
962         const char *name;       /* Option name. */
963         int namelen;            /* Size of option name. */
964         const char *line;       /* The start of line to match. */
965         int linelen;            /* Size of string to match. */
966         int fg, bg, attr;       /* Color and text attributes for the lines. */
967 };
969 static struct line_info line_info[] = {
970 #define LINE(type, line, fg, bg, attr) \
971         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
972         LINE_INFO
973 #undef  LINE
974 };
976 static enum line_type
977 get_line_type(const char *line)
979         int linelen = strlen(line);
980         enum line_type type;
982         for (type = 0; type < ARRAY_SIZE(line_info); type++)
983                 /* Case insensitive search matches Signed-off-by lines better. */
984                 if (linelen >= line_info[type].linelen &&
985                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
986                         return type;
988         return LINE_DEFAULT;
991 static inline int
992 get_line_attr(enum line_type type)
994         assert(type < ARRAY_SIZE(line_info));
995         return COLOR_PAIR(type) | line_info[type].attr;
998 static struct line_info *
999 get_line_info(const char *name)
1001         size_t namelen = strlen(name);
1002         enum line_type type;
1004         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1005                 if (namelen == line_info[type].namelen &&
1006                     !string_enum_compare(line_info[type].name, name, namelen))
1007                         return &line_info[type];
1009         return NULL;
1012 static void
1013 init_colors(void)
1015         int default_bg = line_info[LINE_DEFAULT].bg;
1016         int default_fg = line_info[LINE_DEFAULT].fg;
1017         enum line_type type;
1019         start_color();
1021         if (assume_default_colors(default_fg, default_bg) == ERR) {
1022                 default_bg = COLOR_BLACK;
1023                 default_fg = COLOR_WHITE;
1024         }
1026         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1027                 struct line_info *info = &line_info[type];
1028                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1029                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1031                 init_pair(type, fg, bg);
1032         }
1035 struct line {
1036         enum line_type type;
1038         /* State flags */
1039         unsigned int selected:1;
1040         unsigned int dirty:1;
1041         unsigned int cleareol:1;
1043         void *data;             /* User data */
1044 };
1047 /*
1048  * Keys
1049  */
1051 struct keybinding {
1052         int alias;
1053         enum request request;
1054 };
1056 static struct keybinding default_keybindings[] = {
1057         /* View switching */
1058         { 'm',          REQ_VIEW_MAIN },
1059         { 'd',          REQ_VIEW_DIFF },
1060         { 'l',          REQ_VIEW_LOG },
1061         { 't',          REQ_VIEW_TREE },
1062         { 'f',          REQ_VIEW_BLOB },
1063         { 'B',          REQ_VIEW_BLAME },
1064         { 'p',          REQ_VIEW_PAGER },
1065         { 'h',          REQ_VIEW_HELP },
1066         { 'S',          REQ_VIEW_STATUS },
1067         { 'c',          REQ_VIEW_STAGE },
1069         /* View manipulation */
1070         { 'q',          REQ_VIEW_CLOSE },
1071         { KEY_TAB,      REQ_VIEW_NEXT },
1072         { KEY_RETURN,   REQ_ENTER },
1073         { KEY_UP,       REQ_PREVIOUS },
1074         { KEY_DOWN,     REQ_NEXT },
1075         { 'R',          REQ_REFRESH },
1076         { KEY_F(5),     REQ_REFRESH },
1077         { 'O',          REQ_MAXIMIZE },
1079         /* Cursor navigation */
1080         { 'k',          REQ_MOVE_UP },
1081         { 'j',          REQ_MOVE_DOWN },
1082         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1083         { KEY_END,      REQ_MOVE_LAST_LINE },
1084         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1085         { ' ',          REQ_MOVE_PAGE_DOWN },
1086         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1087         { 'b',          REQ_MOVE_PAGE_UP },
1088         { '-',          REQ_MOVE_PAGE_UP },
1090         /* Scrolling */
1091         { KEY_IC,       REQ_SCROLL_LINE_UP },
1092         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1093         { 'w',          REQ_SCROLL_PAGE_UP },
1094         { 's',          REQ_SCROLL_PAGE_DOWN },
1096         /* Searching */
1097         { '/',          REQ_SEARCH },
1098         { '?',          REQ_SEARCH_BACK },
1099         { 'n',          REQ_FIND_NEXT },
1100         { 'N',          REQ_FIND_PREV },
1102         /* Misc */
1103         { 'Q',          REQ_QUIT },
1104         { 'z',          REQ_STOP_LOADING },
1105         { 'v',          REQ_SHOW_VERSION },
1106         { 'r',          REQ_SCREEN_REDRAW },
1107         { '.',          REQ_TOGGLE_LINENO },
1108         { 'D',          REQ_TOGGLE_DATE },
1109         { 'A',          REQ_TOGGLE_AUTHOR },
1110         { 'g',          REQ_TOGGLE_REV_GRAPH },
1111         { 'F',          REQ_TOGGLE_REFS },
1112         { ':',          REQ_PROMPT },
1113         { 'u',          REQ_STATUS_UPDATE },
1114         { '!',          REQ_STATUS_REVERT },
1115         { 'M',          REQ_STATUS_MERGE },
1116         { '@',          REQ_STAGE_NEXT },
1117         { ',',          REQ_TREE_PARENT },
1118         { 'e',          REQ_EDIT },
1120         /* Using the ncurses SIGWINCH handler. */
1121         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1122 };
1124 #define KEYMAP_INFO \
1125         KEYMAP_(GENERIC), \
1126         KEYMAP_(MAIN), \
1127         KEYMAP_(DIFF), \
1128         KEYMAP_(LOG), \
1129         KEYMAP_(TREE), \
1130         KEYMAP_(BLOB), \
1131         KEYMAP_(BLAME), \
1132         KEYMAP_(PAGER), \
1133         KEYMAP_(HELP), \
1134         KEYMAP_(STATUS), \
1135         KEYMAP_(STAGE)
1137 enum keymap {
1138 #define KEYMAP_(name) KEYMAP_##name
1139         KEYMAP_INFO
1140 #undef  KEYMAP_
1141 };
1143 static struct int_map keymap_table[] = {
1144 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1145         KEYMAP_INFO
1146 #undef  KEYMAP_
1147 };
1149 #define set_keymap(map, name) \
1150         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1152 struct keybinding_table {
1153         struct keybinding *data;
1154         size_t size;
1155 };
1157 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1159 static void
1160 add_keybinding(enum keymap keymap, enum request request, int key)
1162         struct keybinding_table *table = &keybindings[keymap];
1164         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1165         if (!table->data)
1166                 die("Failed to allocate keybinding");
1167         table->data[table->size].alias = key;
1168         table->data[table->size++].request = request;
1171 /* Looks for a key binding first in the given map, then in the generic map, and
1172  * lastly in the default keybindings. */
1173 static enum request
1174 get_keybinding(enum keymap keymap, int key)
1176         size_t i;
1178         for (i = 0; i < keybindings[keymap].size; i++)
1179                 if (keybindings[keymap].data[i].alias == key)
1180                         return keybindings[keymap].data[i].request;
1182         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1183                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1184                         return keybindings[KEYMAP_GENERIC].data[i].request;
1186         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1187                 if (default_keybindings[i].alias == key)
1188                         return default_keybindings[i].request;
1190         return (enum request) key;
1194 struct key {
1195         const char *name;
1196         int value;
1197 };
1199 static struct key key_table[] = {
1200         { "Enter",      KEY_RETURN },
1201         { "Space",      ' ' },
1202         { "Backspace",  KEY_BACKSPACE },
1203         { "Tab",        KEY_TAB },
1204         { "Escape",     KEY_ESC },
1205         { "Left",       KEY_LEFT },
1206         { "Right",      KEY_RIGHT },
1207         { "Up",         KEY_UP },
1208         { "Down",       KEY_DOWN },
1209         { "Insert",     KEY_IC },
1210         { "Delete",     KEY_DC },
1211         { "Hash",       '#' },
1212         { "Home",       KEY_HOME },
1213         { "End",        KEY_END },
1214         { "PageUp",     KEY_PPAGE },
1215         { "PageDown",   KEY_NPAGE },
1216         { "F1",         KEY_F(1) },
1217         { "F2",         KEY_F(2) },
1218         { "F3",         KEY_F(3) },
1219         { "F4",         KEY_F(4) },
1220         { "F5",         KEY_F(5) },
1221         { "F6",         KEY_F(6) },
1222         { "F7",         KEY_F(7) },
1223         { "F8",         KEY_F(8) },
1224         { "F9",         KEY_F(9) },
1225         { "F10",        KEY_F(10) },
1226         { "F11",        KEY_F(11) },
1227         { "F12",        KEY_F(12) },
1228 };
1230 static int
1231 get_key_value(const char *name)
1233         int i;
1235         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1236                 if (!strcasecmp(key_table[i].name, name))
1237                         return key_table[i].value;
1239         if (strlen(name) == 1 && isprint(*name))
1240                 return (int) *name;
1242         return ERR;
1245 static const char *
1246 get_key_name(int key_value)
1248         static char key_char[] = "'X'";
1249         const char *seq = NULL;
1250         int key;
1252         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1253                 if (key_table[key].value == key_value)
1254                         seq = key_table[key].name;
1256         if (seq == NULL &&
1257             key_value < 127 &&
1258             isprint(key_value)) {
1259                 key_char[1] = (char) key_value;
1260                 seq = key_char;
1261         }
1263         return seq ? seq : "(no key)";
1266 static const char *
1267 get_key(enum request request)
1269         static char buf[BUFSIZ];
1270         size_t pos = 0;
1271         char *sep = "";
1272         int i;
1274         buf[pos] = 0;
1276         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1277                 struct keybinding *keybinding = &default_keybindings[i];
1279                 if (keybinding->request != request)
1280                         continue;
1282                 if (!string_format_from(buf, &pos, "%s%s", sep,
1283                                         get_key_name(keybinding->alias)))
1284                         return "Too many keybindings!";
1285                 sep = ", ";
1286         }
1288         return buf;
1291 struct run_request {
1292         enum keymap keymap;
1293         int key;
1294         const char *argv[SIZEOF_ARG];
1295 };
1297 static struct run_request *run_request;
1298 static size_t run_requests;
1300 static enum request
1301 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1303         struct run_request *req;
1305         if (argc >= ARRAY_SIZE(req->argv) - 1)
1306                 return REQ_NONE;
1308         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1309         if (!req)
1310                 return REQ_NONE;
1312         run_request = req;
1313         req = &run_request[run_requests];
1314         req->keymap = keymap;
1315         req->key = key;
1316         req->argv[0] = NULL;
1318         if (!format_argv(req->argv, argv, FORMAT_NONE))
1319                 return REQ_NONE;
1321         return REQ_NONE + ++run_requests;
1324 static struct run_request *
1325 get_run_request(enum request request)
1327         if (request <= REQ_NONE)
1328                 return NULL;
1329         return &run_request[request - REQ_NONE - 1];
1332 static void
1333 add_builtin_run_requests(void)
1335         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1336         const char *gc[] = { "git", "gc", NULL };
1337         struct {
1338                 enum keymap keymap;
1339                 int key;
1340                 int argc;
1341                 const char **argv;
1342         } reqs[] = {
1343                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1344                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1345         };
1346         int i;
1348         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1349                 enum request req;
1351                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1352                 if (req != REQ_NONE)
1353                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1354         }
1357 /*
1358  * User config file handling.
1359  */
1361 static struct int_map color_map[] = {
1362 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1363         COLOR_MAP(DEFAULT),
1364         COLOR_MAP(BLACK),
1365         COLOR_MAP(BLUE),
1366         COLOR_MAP(CYAN),
1367         COLOR_MAP(GREEN),
1368         COLOR_MAP(MAGENTA),
1369         COLOR_MAP(RED),
1370         COLOR_MAP(WHITE),
1371         COLOR_MAP(YELLOW),
1372 };
1374 #define set_color(color, name) \
1375         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1377 static struct int_map attr_map[] = {
1378 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1379         ATTR_MAP(NORMAL),
1380         ATTR_MAP(BLINK),
1381         ATTR_MAP(BOLD),
1382         ATTR_MAP(DIM),
1383         ATTR_MAP(REVERSE),
1384         ATTR_MAP(STANDOUT),
1385         ATTR_MAP(UNDERLINE),
1386 };
1388 #define set_attribute(attr, name) \
1389         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1391 static int   config_lineno;
1392 static bool  config_errors;
1393 static const char *config_msg;
1395 /* Wants: object fgcolor bgcolor [attr] */
1396 static int
1397 option_color_command(int argc, const char *argv[])
1399         struct line_info *info;
1401         if (argc != 3 && argc != 4) {
1402                 config_msg = "Wrong number of arguments given to color command";
1403                 return ERR;
1404         }
1406         info = get_line_info(argv[0]);
1407         if (!info) {
1408                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1409                         info = get_line_info("delimiter");
1411                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1412                         info = get_line_info("date");
1414                 } else {
1415                         config_msg = "Unknown color name";
1416                         return ERR;
1417                 }
1418         }
1420         if (set_color(&info->fg, argv[1]) == ERR ||
1421             set_color(&info->bg, argv[2]) == ERR) {
1422                 config_msg = "Unknown color";
1423                 return ERR;
1424         }
1426         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1427                 config_msg = "Unknown attribute";
1428                 return ERR;
1429         }
1431         return OK;
1434 static bool parse_bool(const char *s)
1436         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1437                 !strcmp(s, "yes")) ? TRUE : FALSE;
1440 static int
1441 parse_int(const char *s, int default_value, int min, int max)
1443         int value = atoi(s);
1445         return (value < min || value > max) ? default_value : value;
1448 /* Wants: name = value */
1449 static int
1450 option_set_command(int argc, const char *argv[])
1452         if (argc != 3) {
1453                 config_msg = "Wrong number of arguments given to set command";
1454                 return ERR;
1455         }
1457         if (strcmp(argv[1], "=")) {
1458                 config_msg = "No value assigned";
1459                 return ERR;
1460         }
1462         if (!strcmp(argv[0], "show-author")) {
1463                 opt_author = parse_bool(argv[2]);
1464                 return OK;
1465         }
1467         if (!strcmp(argv[0], "show-date")) {
1468                 opt_date = parse_bool(argv[2]);
1469                 return OK;
1470         }
1472         if (!strcmp(argv[0], "show-rev-graph")) {
1473                 opt_rev_graph = parse_bool(argv[2]);
1474                 return OK;
1475         }
1477         if (!strcmp(argv[0], "show-refs")) {
1478                 opt_show_refs = parse_bool(argv[2]);
1479                 return OK;
1480         }
1482         if (!strcmp(argv[0], "show-line-numbers")) {
1483                 opt_line_number = parse_bool(argv[2]);
1484                 return OK;
1485         }
1487         if (!strcmp(argv[0], "line-graphics")) {
1488                 opt_line_graphics = parse_bool(argv[2]);
1489                 return OK;
1490         }
1492         if (!strcmp(argv[0], "line-number-interval")) {
1493                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1494                 return OK;
1495         }
1497         if (!strcmp(argv[0], "author-width")) {
1498                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1499                 return OK;
1500         }
1502         if (!strcmp(argv[0], "tab-size")) {
1503                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1504                 return OK;
1505         }
1507         if (!strcmp(argv[0], "commit-encoding")) {
1508                 const char *arg = argv[2];
1509                 int arglen = strlen(arg);
1511                 switch (arg[0]) {
1512                 case '"':
1513                 case '\'':
1514                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1515                                 config_msg = "Unmatched quotation";
1516                                 return ERR;
1517                         }
1518                         arg += 1; arglen -= 2;
1519                 default:
1520                         string_ncopy(opt_encoding, arg, strlen(arg));
1521                         return OK;
1522                 }
1523         }
1525         config_msg = "Unknown variable name";
1526         return ERR;
1529 /* Wants: mode request key */
1530 static int
1531 option_bind_command(int argc, const char *argv[])
1533         enum request request;
1534         int keymap;
1535         int key;
1537         if (argc < 3) {
1538                 config_msg = "Wrong number of arguments given to bind command";
1539                 return ERR;
1540         }
1542         if (set_keymap(&keymap, argv[0]) == ERR) {
1543                 config_msg = "Unknown key map";
1544                 return ERR;
1545         }
1547         key = get_key_value(argv[1]);
1548         if (key == ERR) {
1549                 config_msg = "Unknown key";
1550                 return ERR;
1551         }
1553         request = get_request(argv[2]);
1554         if (request == REQ_NONE) {
1555                 const char *obsolete[] = { "cherry-pick" };
1556                 size_t namelen = strlen(argv[2]);
1557                 int i;
1559                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1560                         if (namelen == strlen(obsolete[i]) &&
1561                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1562                                 config_msg = "Obsolete request name";
1563                                 return ERR;
1564                         }
1565                 }
1566         }
1567         if (request == REQ_NONE && *argv[2]++ == '!')
1568                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1569         if (request == REQ_NONE) {
1570                 config_msg = "Unknown request name";
1571                 return ERR;
1572         }
1574         add_keybinding(keymap, request, key);
1576         return OK;
1579 static int
1580 set_option(const char *opt, char *value)
1582         const char *argv[SIZEOF_ARG];
1583         int argc = 0;
1585         if (!argv_from_string(argv, &argc, value)) {
1586                 config_msg = "Too many option arguments";
1587                 return ERR;
1588         }
1590         if (!strcmp(opt, "color"))
1591                 return option_color_command(argc, argv);
1593         if (!strcmp(opt, "set"))
1594                 return option_set_command(argc, argv);
1596         if (!strcmp(opt, "bind"))
1597                 return option_bind_command(argc, argv);
1599         config_msg = "Unknown option command";
1600         return ERR;
1603 static int
1604 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1606         int status = OK;
1608         config_lineno++;
1609         config_msg = "Internal error";
1611         /* Check for comment markers, since read_properties() will
1612          * only ensure opt and value are split at first " \t". */
1613         optlen = strcspn(opt, "#");
1614         if (optlen == 0)
1615                 return OK;
1617         if (opt[optlen] != 0) {
1618                 config_msg = "No option value";
1619                 status = ERR;
1621         }  else {
1622                 /* Look for comment endings in the value. */
1623                 size_t len = strcspn(value, "#");
1625                 if (len < valuelen) {
1626                         valuelen = len;
1627                         value[valuelen] = 0;
1628                 }
1630                 status = set_option(opt, value);
1631         }
1633         if (status == ERR) {
1634                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1635                         config_lineno, (int) optlen, opt, config_msg);
1636                 config_errors = TRUE;
1637         }
1639         /* Always keep going if errors are encountered. */
1640         return OK;
1643 static void
1644 load_option_file(const char *path)
1646         struct io io = {};
1648         /* It's ok that the file doesn't exist. */
1649         if (!io_open(&io, path))
1650                 return;
1652         config_lineno = 0;
1653         config_errors = FALSE;
1655         if (read_properties(&io, " \t", read_option) == ERR ||
1656             config_errors == TRUE)
1657                 fprintf(stderr, "Errors while loading %s.\n", path);
1660 static int
1661 load_options(void)
1663         const char *home = getenv("HOME");
1664         const char *tigrc_user = getenv("TIGRC_USER");
1665         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1666         char buf[SIZEOF_STR];
1668         add_builtin_run_requests();
1670         if (!tigrc_system) {
1671                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1672                         return ERR;
1673                 tigrc_system = buf;
1674         }
1675         load_option_file(tigrc_system);
1677         if (!tigrc_user) {
1678                 if (!home || !string_format(buf, "%s/.tigrc", home))
1679                         return ERR;
1680                 tigrc_user = buf;
1681         }
1682         load_option_file(tigrc_user);
1684         return OK;
1688 /*
1689  * The viewer
1690  */
1692 struct view;
1693 struct view_ops;
1695 /* The display array of active views and the index of the current view. */
1696 static struct view *display[2];
1697 static unsigned int current_view;
1699 /* Reading from the prompt? */
1700 static bool input_mode = FALSE;
1702 #define foreach_displayed_view(view, i) \
1703         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1705 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1707 /* Current head and commit ID */
1708 static char ref_blob[SIZEOF_REF]        = "";
1709 static char ref_commit[SIZEOF_REF]      = "HEAD";
1710 static char ref_head[SIZEOF_REF]        = "HEAD";
1712 struct view {
1713         const char *name;       /* View name */
1714         const char *cmd_env;    /* Command line set via environment */
1715         const char *id;         /* Points to either of ref_{head,commit,blob} */
1717         struct view_ops *ops;   /* View operations */
1719         enum keymap keymap;     /* What keymap does this view have */
1720         bool git_dir;           /* Whether the view requires a git directory. */
1722         char ref[SIZEOF_REF];   /* Hovered commit reference */
1723         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1725         int height, width;      /* The width and height of the main window */
1726         WINDOW *win;            /* The main window */
1727         WINDOW *title;          /* The title window living below the main window */
1729         /* Navigation */
1730         unsigned long offset;   /* Offset of the window top */
1731         unsigned long lineno;   /* Current line number */
1733         /* Searching */
1734         char grep[SIZEOF_STR];  /* Search string */
1735         regex_t *regex;         /* Pre-compiled regex */
1737         /* If non-NULL, points to the view that opened this view. If this view
1738          * is closed tig will switch back to the parent view. */
1739         struct view *parent;
1741         /* Buffering */
1742         size_t lines;           /* Total number of lines */
1743         struct line *line;      /* Line index */
1744         size_t line_alloc;      /* Total number of allocated lines */
1745         unsigned int digits;    /* Number of digits in the lines member. */
1747         /* Drawing */
1748         struct line *curline;   /* Line currently being drawn. */
1749         enum line_type curtype; /* Attribute currently used for drawing. */
1750         unsigned long col;      /* Column when drawing. */
1752         /* Loading */
1753         struct io io;
1754         struct io *pipe;
1755         time_t start_time;
1756         time_t update_secs;
1757 };
1759 struct view_ops {
1760         /* What type of content being displayed. Used in the title bar. */
1761         const char *type;
1762         /* Default command arguments. */
1763         const char **argv;
1764         /* Open and reads in all view content. */
1765         bool (*open)(struct view *view);
1766         /* Read one line; updates view->line. */
1767         bool (*read)(struct view *view, char *data);
1768         /* Draw one line; @lineno must be < view->height. */
1769         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1770         /* Depending on view handle a special requests. */
1771         enum request (*request)(struct view *view, enum request request, struct line *line);
1772         /* Search for regex in a line. */
1773         bool (*grep)(struct view *view, struct line *line);
1774         /* Select line */
1775         void (*select)(struct view *view, struct line *line);
1776 };
1778 static struct view_ops blame_ops;
1779 static struct view_ops blob_ops;
1780 static struct view_ops diff_ops;
1781 static struct view_ops help_ops;
1782 static struct view_ops log_ops;
1783 static struct view_ops main_ops;
1784 static struct view_ops pager_ops;
1785 static struct view_ops stage_ops;
1786 static struct view_ops status_ops;
1787 static struct view_ops tree_ops;
1789 #define VIEW_STR(name, env, ref, ops, map, git) \
1790         { name, #env, ref, ops, map, git }
1792 #define VIEW_(id, name, ops, git, ref) \
1793         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1796 static struct view views[] = {
1797         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1798         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1799         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1800         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1801         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1802         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1803         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1804         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1805         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1806         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1807 };
1809 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1810 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1812 #define foreach_view(view, i) \
1813         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1815 #define view_is_displayed(view) \
1816         (view == display[0] || view == display[1])
1819 enum line_graphic {
1820         LINE_GRAPHIC_VLINE
1821 };
1823 static int line_graphics[] = {
1824         /* LINE_GRAPHIC_VLINE: */ '|'
1825 };
1827 static inline void
1828 set_view_attr(struct view *view, enum line_type type)
1830         if (!view->curline->selected && view->curtype != type) {
1831                 wattrset(view->win, get_line_attr(type));
1832                 wchgat(view->win, -1, 0, type, NULL);
1833                 view->curtype = type;
1834         }
1837 static int
1838 draw_chars(struct view *view, enum line_type type, const char *string,
1839            int max_len, bool use_tilde)
1841         int len = 0;
1842         int col = 0;
1843         int trimmed = FALSE;
1845         if (max_len <= 0)
1846                 return 0;
1848         if (opt_utf8) {
1849                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1850         } else {
1851                 col = len = strlen(string);
1852                 if (len > max_len) {
1853                         if (use_tilde) {
1854                                 max_len -= 1;
1855                         }
1856                         col = len = max_len;
1857                         trimmed = TRUE;
1858                 }
1859         }
1861         set_view_attr(view, type);
1862         waddnstr(view->win, string, len);
1863         if (trimmed && use_tilde) {
1864                 set_view_attr(view, LINE_DELIMITER);
1865                 waddch(view->win, '~');
1866                 col++;
1867         }
1869         return col;
1872 static int
1873 draw_space(struct view *view, enum line_type type, int max, int spaces)
1875         static char space[] = "                    ";
1876         int col = 0;
1878         spaces = MIN(max, spaces);
1880         while (spaces > 0) {
1881                 int len = MIN(spaces, sizeof(space) - 1);
1883                 col += draw_chars(view, type, space, spaces, FALSE);
1884                 spaces -= len;
1885         }
1887         return col;
1890 static bool
1891 draw_lineno(struct view *view, unsigned int lineno)
1893         char number[10];
1894         int digits3 = view->digits < 3 ? 3 : view->digits;
1895         int max_number = MIN(digits3, STRING_SIZE(number));
1896         int max = view->width - view->col;
1897         int col;
1899         if (max < max_number)
1900                 max_number = max;
1902         lineno += view->offset + 1;
1903         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1904                 static char fmt[] = "%1ld";
1906                 if (view->digits <= 9)
1907                         fmt[1] = '0' + digits3;
1909                 if (!string_format(number, fmt, lineno))
1910                         number[0] = 0;
1911                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1912         } else {
1913                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1914         }
1916         if (col < max) {
1917                 set_view_attr(view, LINE_DEFAULT);
1918                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1919                 col++;
1920         }
1922         if (col < max)
1923                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1924         view->col += col;
1926         return view->width - view->col <= 0;
1929 static bool
1930 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1932         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1933         return view->width - view->col <= 0;
1936 static bool
1937 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1939         int max = view->width - view->col;
1940         int i;
1942         if (max < size)
1943                 size = max;
1945         set_view_attr(view, type);
1946         /* Using waddch() instead of waddnstr() ensures that
1947          * they'll be rendered correctly for the cursor line. */
1948         for (i = 0; i < size; i++)
1949                 waddch(view->win, graphic[i]);
1951         view->col += size;
1952         if (size < max) {
1953                 waddch(view->win, ' ');
1954                 view->col++;
1955         }
1957         return view->width - view->col <= 0;
1960 static bool
1961 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1963         int max = MIN(view->width - view->col, len);
1964         int col;
1966         if (text)
1967                 col = draw_chars(view, type, text, max - 1, trim);
1968         else
1969                 col = draw_space(view, type, max - 1, max - 1);
1971         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1972         return view->width - view->col <= 0;
1975 static bool
1976 draw_date(struct view *view, struct tm *time)
1978         char buf[DATE_COLS];
1979         char *date;
1980         int timelen = 0;
1982         if (time)
1983                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1984         date = timelen ? buf : NULL;
1986         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1989 static bool
1990 draw_view_line(struct view *view, unsigned int lineno)
1992         struct line *line;
1993         bool selected = (view->offset + lineno == view->lineno);
1994         bool draw_ok;
1996         assert(view_is_displayed(view));
1998         if (view->offset + lineno >= view->lines)
1999                 return FALSE;
2001         line = &view->line[view->offset + lineno];
2003         wmove(view->win, lineno, 0);
2004         if (line->cleareol)
2005                 wclrtoeol(view->win);
2006         view->col = 0;
2007         view->curline = line;
2008         view->curtype = LINE_NONE;
2009         line->selected = FALSE;
2010         line->dirty = line->cleareol = 0;
2012         if (selected) {
2013                 set_view_attr(view, LINE_CURSOR);
2014                 line->selected = TRUE;
2015                 view->ops->select(view, line);
2016         }
2018         scrollok(view->win, FALSE);
2019         draw_ok = view->ops->draw(view, line, lineno);
2020         scrollok(view->win, TRUE);
2022         return draw_ok;
2025 static void
2026 redraw_view_dirty(struct view *view)
2028         bool dirty = FALSE;
2029         int lineno;
2031         for (lineno = 0; lineno < view->height; lineno++) {
2032                 if (view->offset + lineno >= view->lines)
2033                         break;
2034                 if (!view->line[view->offset + lineno].dirty)
2035                         continue;
2036                 dirty = TRUE;
2037                 if (!draw_view_line(view, lineno))
2038                         break;
2039         }
2041         if (!dirty)
2042                 return;
2043         redrawwin(view->win);
2044         if (input_mode)
2045                 wnoutrefresh(view->win);
2046         else
2047                 wrefresh(view->win);
2050 static void
2051 redraw_view_from(struct view *view, int lineno)
2053         assert(0 <= lineno && lineno < view->height);
2055         for (; lineno < view->height; lineno++) {
2056                 if (!draw_view_line(view, lineno))
2057                         break;
2058         }
2060         redrawwin(view->win);
2061         if (input_mode)
2062                 wnoutrefresh(view->win);
2063         else
2064                 wrefresh(view->win);
2067 static void
2068 redraw_view(struct view *view)
2070         werase(view->win);
2071         redraw_view_from(view, 0);
2075 static void
2076 update_view_title(struct view *view)
2078         char buf[SIZEOF_STR];
2079         char state[SIZEOF_STR];
2080         size_t bufpos = 0, statelen = 0;
2082         assert(view_is_displayed(view));
2084         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2085                 unsigned int view_lines = view->offset + view->height;
2086                 unsigned int lines = view->lines
2087                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2088                                    : 0;
2090                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2091                                    view->ops->type,
2092                                    view->lineno + 1,
2093                                    view->lines,
2094                                    lines);
2096         }
2098         if (view->pipe) {
2099                 time_t secs = time(NULL) - view->start_time;
2101                 /* Three git seconds are a long time ... */
2102                 if (secs > 2)
2103                         string_format_from(state, &statelen, " loading %lds", secs);
2104         }
2106         string_format_from(buf, &bufpos, "[%s]", view->name);
2107         if (*view->ref && bufpos < view->width) {
2108                 size_t refsize = strlen(view->ref);
2109                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2111                 if (minsize < view->width)
2112                         refsize = view->width - minsize + 7;
2113                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2114         }
2116         if (statelen && bufpos < view->width) {
2117                 string_format_from(buf, &bufpos, "%s", state);
2118         }
2120         if (view == display[current_view])
2121                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2122         else
2123                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2125         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2126         wclrtoeol(view->title);
2127         wmove(view->title, 0, view->width - 1);
2129         if (input_mode)
2130                 wnoutrefresh(view->title);
2131         else
2132                 wrefresh(view->title);
2135 static void
2136 resize_display(void)
2138         int offset, i;
2139         struct view *base = display[0];
2140         struct view *view = display[1] ? display[1] : display[0];
2142         /* Setup window dimensions */
2144         getmaxyx(stdscr, base->height, base->width);
2146         /* Make room for the status window. */
2147         base->height -= 1;
2149         if (view != base) {
2150                 /* Horizontal split. */
2151                 view->width   = base->width;
2152                 view->height  = SCALE_SPLIT_VIEW(base->height);
2153                 base->height -= view->height;
2155                 /* Make room for the title bar. */
2156                 view->height -= 1;
2157         }
2159         /* Make room for the title bar. */
2160         base->height -= 1;
2162         offset = 0;
2164         foreach_displayed_view (view, i) {
2165                 if (!view->win) {
2166                         view->win = newwin(view->height, 0, offset, 0);
2167                         if (!view->win)
2168                                 die("Failed to create %s view", view->name);
2170                         scrollok(view->win, TRUE);
2172                         view->title = newwin(1, 0, offset + view->height, 0);
2173                         if (!view->title)
2174                                 die("Failed to create title window");
2176                 } else {
2177                         wresize(view->win, view->height, view->width);
2178                         mvwin(view->win,   offset, 0);
2179                         mvwin(view->title, offset + view->height, 0);
2180                 }
2182                 offset += view->height + 1;
2183         }
2186 static void
2187 redraw_display(bool clear)
2189         struct view *view;
2190         int i;
2192         foreach_displayed_view (view, i) {
2193                 if (clear)
2194                         wclear(view->win);
2195                 redraw_view(view);
2196                 update_view_title(view);
2197         }
2200 static void
2201 update_display_cursor(struct view *view)
2203         /* Move the cursor to the right-most column of the cursor line.
2204          *
2205          * XXX: This could turn out to be a bit expensive, but it ensures that
2206          * the cursor does not jump around. */
2207         if (view->lines) {
2208                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2209                 wrefresh(view->win);
2210         }
2213 static void
2214 toggle_view_option(bool *option, const char *help)
2216         *option = !*option;
2217         redraw_display(FALSE);
2218         report("%sabling %s", *option ? "En" : "Dis", help);
2221 /*
2222  * Navigation
2223  */
2225 /* Scrolling backend */
2226 static void
2227 do_scroll_view(struct view *view, int lines)
2229         bool redraw_current_line = FALSE;
2231         /* The rendering expects the new offset. */
2232         view->offset += lines;
2234         assert(0 <= view->offset && view->offset < view->lines);
2235         assert(lines);
2237         /* Move current line into the view. */
2238         if (view->lineno < view->offset) {
2239                 view->lineno = view->offset;
2240                 redraw_current_line = TRUE;
2241         } else if (view->lineno >= view->offset + view->height) {
2242                 view->lineno = view->offset + view->height - 1;
2243                 redraw_current_line = TRUE;
2244         }
2246         assert(view->offset <= view->lineno && view->lineno < view->lines);
2248         /* Redraw the whole screen if scrolling is pointless. */
2249         if (view->height < ABS(lines)) {
2250                 redraw_view(view);
2252         } else {
2253                 int line = lines > 0 ? view->height - lines : 0;
2254                 int end = line + ABS(lines);
2256                 wscrl(view->win, lines);
2258                 for (; line < end; line++) {
2259                         if (!draw_view_line(view, line))
2260                                 break;
2261                 }
2263                 if (redraw_current_line)
2264                         draw_view_line(view, view->lineno - view->offset);
2265         }
2267         redrawwin(view->win);
2268         wrefresh(view->win);
2269         report("");
2272 /* Scroll frontend */
2273 static void
2274 scroll_view(struct view *view, enum request request)
2276         int lines = 1;
2278         assert(view_is_displayed(view));
2280         switch (request) {
2281         case REQ_SCROLL_PAGE_DOWN:
2282                 lines = view->height;
2283         case REQ_SCROLL_LINE_DOWN:
2284                 if (view->offset + lines > view->lines)
2285                         lines = view->lines - view->offset;
2287                 if (lines == 0 || view->offset + view->height >= view->lines) {
2288                         report("Cannot scroll beyond the last line");
2289                         return;
2290                 }
2291                 break;
2293         case REQ_SCROLL_PAGE_UP:
2294                 lines = view->height;
2295         case REQ_SCROLL_LINE_UP:
2296                 if (lines > view->offset)
2297                         lines = view->offset;
2299                 if (lines == 0) {
2300                         report("Cannot scroll beyond the first line");
2301                         return;
2302                 }
2304                 lines = -lines;
2305                 break;
2307         default:
2308                 die("request %d not handled in switch", request);
2309         }
2311         do_scroll_view(view, lines);
2314 /* Cursor moving */
2315 static void
2316 move_view(struct view *view, enum request request)
2318         int scroll_steps = 0;
2319         int steps;
2321         switch (request) {
2322         case REQ_MOVE_FIRST_LINE:
2323                 steps = -view->lineno;
2324                 break;
2326         case REQ_MOVE_LAST_LINE:
2327                 steps = view->lines - view->lineno - 1;
2328                 break;
2330         case REQ_MOVE_PAGE_UP:
2331                 steps = view->height > view->lineno
2332                       ? -view->lineno : -view->height;
2333                 break;
2335         case REQ_MOVE_PAGE_DOWN:
2336                 steps = view->lineno + view->height >= view->lines
2337                       ? view->lines - view->lineno - 1 : view->height;
2338                 break;
2340         case REQ_MOVE_UP:
2341                 steps = -1;
2342                 break;
2344         case REQ_MOVE_DOWN:
2345                 steps = 1;
2346                 break;
2348         default:
2349                 die("request %d not handled in switch", request);
2350         }
2352         if (steps <= 0 && view->lineno == 0) {
2353                 report("Cannot move beyond the first line");
2354                 return;
2356         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2357                 report("Cannot move beyond the last line");
2358                 return;
2359         }
2361         /* Move the current line */
2362         view->lineno += steps;
2363         assert(0 <= view->lineno && view->lineno < view->lines);
2365         /* Check whether the view needs to be scrolled */
2366         if (view->lineno < view->offset ||
2367             view->lineno >= view->offset + view->height) {
2368                 scroll_steps = steps;
2369                 if (steps < 0 && -steps > view->offset) {
2370                         scroll_steps = -view->offset;
2372                 } else if (steps > 0) {
2373                         if (view->lineno == view->lines - 1 &&
2374                             view->lines > view->height) {
2375                                 scroll_steps = view->lines - view->offset - 1;
2376                                 if (scroll_steps >= view->height)
2377                                         scroll_steps -= view->height - 1;
2378                         }
2379                 }
2380         }
2382         if (!view_is_displayed(view)) {
2383                 view->offset += scroll_steps;
2384                 assert(0 <= view->offset && view->offset < view->lines);
2385                 view->ops->select(view, &view->line[view->lineno]);
2386                 return;
2387         }
2389         /* Repaint the old "current" line if we be scrolling */
2390         if (ABS(steps) < view->height)
2391                 draw_view_line(view, view->lineno - steps - view->offset);
2393         if (scroll_steps) {
2394                 do_scroll_view(view, scroll_steps);
2395                 return;
2396         }
2398         /* Draw the current line */
2399         draw_view_line(view, view->lineno - view->offset);
2401         redrawwin(view->win);
2402         wrefresh(view->win);
2403         report("");
2407 /*
2408  * Searching
2409  */
2411 static void search_view(struct view *view, enum request request);
2413 static bool
2414 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2416         assert(view_is_displayed(view));
2418         if (!view->ops->grep(view, line))
2419                 return FALSE;
2421         if (lineno - view->offset >= view->height) {
2422                 view->offset = lineno;
2423                 view->lineno = lineno;
2424                 redraw_view(view);
2426         } else {
2427                 unsigned long old_lineno = view->lineno - view->offset;
2429                 view->lineno = lineno;
2430                 draw_view_line(view, old_lineno);
2432                 draw_view_line(view, view->lineno - view->offset);
2433                 redrawwin(view->win);
2434                 wrefresh(view->win);
2435         }
2437         report("Line %ld matches '%s'", lineno + 1, view->grep);
2438         return TRUE;
2441 static void
2442 find_next(struct view *view, enum request request)
2444         unsigned long lineno = view->lineno;
2445         int direction;
2447         if (!*view->grep) {
2448                 if (!*opt_search)
2449                         report("No previous search");
2450                 else
2451                         search_view(view, request);
2452                 return;
2453         }
2455         switch (request) {
2456         case REQ_SEARCH:
2457         case REQ_FIND_NEXT:
2458                 direction = 1;
2459                 break;
2461         case REQ_SEARCH_BACK:
2462         case REQ_FIND_PREV:
2463                 direction = -1;
2464                 break;
2466         default:
2467                 return;
2468         }
2470         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2471                 lineno += direction;
2473         /* Note, lineno is unsigned long so will wrap around in which case it
2474          * will become bigger than view->lines. */
2475         for (; lineno < view->lines; lineno += direction) {
2476                 struct line *line = &view->line[lineno];
2478                 if (find_next_line(view, lineno, line))
2479                         return;
2480         }
2482         report("No match found for '%s'", view->grep);
2485 static void
2486 search_view(struct view *view, enum request request)
2488         int regex_err;
2490         if (view->regex) {
2491                 regfree(view->regex);
2492                 *view->grep = 0;
2493         } else {
2494                 view->regex = calloc(1, sizeof(*view->regex));
2495                 if (!view->regex)
2496                         return;
2497         }
2499         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2500         if (regex_err != 0) {
2501                 char buf[SIZEOF_STR] = "unknown error";
2503                 regerror(regex_err, view->regex, buf, sizeof(buf));
2504                 report("Search failed: %s", buf);
2505                 return;
2506         }
2508         string_copy(view->grep, opt_search);
2510         find_next(view, request);
2513 /*
2514  * Incremental updating
2515  */
2517 static void
2518 reset_view(struct view *view)
2520         int i;
2522         for (i = 0; i < view->lines; i++)
2523                 free(view->line[i].data);
2524         free(view->line);
2526         view->line = NULL;
2527         view->offset = 0;
2528         view->lines  = 0;
2529         view->lineno = 0;
2530         view->line_alloc = 0;
2531         view->vid[0] = 0;
2532         view->update_secs = 0;
2535 static void
2536 free_argv(const char *argv[])
2538         int argc;
2540         for (argc = 0; argv[argc]; argc++)
2541                 free((void *) argv[argc]);
2544 static bool
2545 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2547         char buf[SIZEOF_STR];
2548         int argc;
2549         bool noreplace = flags == FORMAT_NONE;
2551         free_argv(dst_argv);
2553         for (argc = 0; src_argv[argc]; argc++) {
2554                 const char *arg = src_argv[argc];
2555                 size_t bufpos = 0;
2557                 while (arg) {
2558                         char *next = strstr(arg, "%(");
2559                         int len = next - arg;
2560                         const char *value;
2562                         if (!next || noreplace) {
2563                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2564                                         noreplace = TRUE;
2565                                 len = strlen(arg);
2566                                 value = "";
2568                         } else if (!prefixcmp(next, "%(directory)")) {
2569                                 value = opt_path;
2571                         } else if (!prefixcmp(next, "%(file)")) {
2572                                 value = opt_file;
2574                         } else if (!prefixcmp(next, "%(ref)")) {
2575                                 value = *opt_ref ? opt_ref : "HEAD";
2577                         } else if (!prefixcmp(next, "%(head)")) {
2578                                 value = ref_head;
2580                         } else if (!prefixcmp(next, "%(commit)")) {
2581                                 value = ref_commit;
2583                         } else if (!prefixcmp(next, "%(blob)")) {
2584                                 value = ref_blob;
2586                         } else {
2587                                 report("Unknown replacement: `%s`", next);
2588                                 return FALSE;
2589                         }
2591                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2592                                 return FALSE;
2594                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2595                 }
2597                 dst_argv[argc] = strdup(buf);
2598                 if (!dst_argv[argc])
2599                         break;
2600         }
2602         dst_argv[argc] = NULL;
2604         return src_argv[argc] == NULL;
2607 static void
2608 end_update(struct view *view, bool force)
2610         if (!view->pipe)
2611                 return;
2612         while (!view->ops->read(view, NULL))
2613                 if (!force)
2614                         return;
2615         set_nonblocking_input(FALSE);
2616         if (force)
2617                 kill_io(view->pipe);
2618         done_io(view->pipe);
2619         view->pipe = NULL;
2622 static void
2623 setup_update(struct view *view, const char *vid)
2625         set_nonblocking_input(TRUE);
2626         reset_view(view);
2627         string_copy_rev(view->vid, vid);
2628         view->pipe = &view->io;
2629         view->start_time = time(NULL);
2632 static bool
2633 prepare_update(struct view *view, const char *argv[], const char *dir,
2634                enum format_flags flags)
2636         if (view->pipe)
2637                 end_update(view, TRUE);
2638         return init_io_rd(&view->io, argv, dir, flags);
2641 static bool
2642 prepare_update_file(struct view *view, const char *name)
2644         if (view->pipe)
2645                 end_update(view, TRUE);
2646         return io_open(&view->io, name);
2649 static bool
2650 begin_update(struct view *view, bool refresh)
2652         if (view->pipe)
2653                 end_update(view, TRUE);
2655         if (refresh) {
2656                 if (!start_io(&view->io))
2657                         return FALSE;
2659         } else {
2660                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2661                         opt_path[0] = 0;
2663                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2664                         return FALSE;
2666                 /* Put the current ref_* value to the view title ref
2667                  * member. This is needed by the blob view. Most other
2668                  * views sets it automatically after loading because the
2669                  * first line is a commit line. */
2670                 string_copy_rev(view->ref, view->id);
2671         }
2673         setup_update(view, view->id);
2675         return TRUE;
2678 #define ITEM_CHUNK_SIZE 256
2679 static void *
2680 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2682         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2683         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2685         if (mem == NULL || num_chunks != num_chunks_new) {
2686                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2687                 mem = realloc(mem, *size * item_size);
2688         }
2690         return mem;
2693 static struct line *
2694 realloc_lines(struct view *view, size_t line_size)
2696         size_t alloc = view->line_alloc;
2697         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2698                                          sizeof(*view->line));
2700         if (!tmp)
2701                 return NULL;
2703         view->line = tmp;
2704         view->line_alloc = alloc;
2705         return view->line;
2708 static bool
2709 update_view(struct view *view)
2711         char out_buffer[BUFSIZ * 2];
2712         char *line;
2713         /* Clear the view and redraw everything since the tree sorting
2714          * might have rearranged things. */
2715         bool redraw = view->lines == 0;
2716         bool can_read = TRUE;
2718         if (!view->pipe)
2719                 return TRUE;
2721         if (!io_can_read(view->pipe)) {
2722                 if (view->lines == 0) {
2723                         time_t secs = time(NULL) - view->start_time;
2725                         if (secs > view->update_secs) {
2726                                 if (view->update_secs == 0)
2727                                         redraw_view(view);
2728                                 update_view_title(view);
2729                                 view->update_secs = secs;
2730                         }
2731                 }
2732                 return TRUE;
2733         }
2735         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2736                 if (opt_iconv != ICONV_NONE) {
2737                         ICONV_CONST char *inbuf = line;
2738                         size_t inlen = strlen(line) + 1;
2740                         char *outbuf = out_buffer;
2741                         size_t outlen = sizeof(out_buffer);
2743                         size_t ret;
2745                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2746                         if (ret != (size_t) -1)
2747                                 line = out_buffer;
2748                 }
2750                 if (!view->ops->read(view, line))
2751                         goto alloc_error;
2752         }
2754         {
2755                 unsigned long lines = view->lines;
2756                 int digits;
2758                 for (digits = 0; lines; digits++)
2759                         lines /= 10;
2761                 /* Keep the displayed view in sync with line number scaling. */
2762                 if (digits != view->digits) {
2763                         view->digits = digits;
2764                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2765                                 redraw = TRUE;
2766                 }
2767         }
2769         if (io_error(view->pipe)) {
2770                 report("Failed to read: %s", io_strerror(view->pipe));
2771                 end_update(view, TRUE);
2773         } else if (io_eof(view->pipe)) {
2774                 report("");
2775                 end_update(view, FALSE);
2776         }
2778         if (!view_is_displayed(view))
2779                 return TRUE;
2781         if (redraw)
2782                 redraw_view_from(view, 0);
2783         else
2784                 redraw_view_dirty(view);
2786         /* Update the title _after_ the redraw so that if the redraw picks up a
2787          * commit reference in view->ref it'll be available here. */
2788         update_view_title(view);
2789         return TRUE;
2791 alloc_error:
2792         report("Allocation failure");
2793         end_update(view, TRUE);
2794         return FALSE;
2797 static struct line *
2798 add_line_data(struct view *view, void *data, enum line_type type)
2800         struct line *line;
2802         if (!realloc_lines(view, view->lines + 1))
2803                 return NULL;
2805         line = &view->line[view->lines++];
2806         memset(line, 0, sizeof(*line));
2807         line->type = type;
2808         line->data = data;
2809         line->dirty = 1;
2811         return line;
2814 static struct line *
2815 add_line_text(struct view *view, const char *text, enum line_type type)
2817         char *data = text ? strdup(text) : NULL;
2819         return data ? add_line_data(view, data, type) : NULL;
2822 static struct line *
2823 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2825         char buf[SIZEOF_STR];
2826         va_list args;
2828         va_start(args, fmt);
2829         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2830                 buf[0] = 0;
2831         va_end(args);
2833         return buf[0] ? add_line_text(view, buf, type) : NULL;
2836 /*
2837  * View opening
2838  */
2840 enum open_flags {
2841         OPEN_DEFAULT = 0,       /* Use default view switching. */
2842         OPEN_SPLIT = 1,         /* Split current view. */
2843         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2844         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2845         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2846         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2847         OPEN_PREPARED = 32,     /* Open already prepared command. */
2848 };
2850 static void
2851 open_view(struct view *prev, enum request request, enum open_flags flags)
2853         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2854         bool split = !!(flags & OPEN_SPLIT);
2855         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2856         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2857         struct view *view = VIEW(request);
2858         int nviews = displayed_views();
2859         struct view *base_view = display[0];
2861         if (view == prev && nviews == 1 && !reload) {
2862                 report("Already in %s view", view->name);
2863                 return;
2864         }
2866         if (view->git_dir && !opt_git_dir[0]) {
2867                 report("The %s view is disabled in pager view", view->name);
2868                 return;
2869         }
2871         if (split) {
2872                 display[1] = view;
2873                 if (!backgrounded)
2874                         current_view = 1;
2875         } else if (!nomaximize) {
2876                 /* Maximize the current view. */
2877                 memset(display, 0, sizeof(display));
2878                 current_view = 0;
2879                 display[current_view] = view;
2880         }
2882         /* Resize the view when switching between split- and full-screen,
2883          * or when switching between two different full-screen views. */
2884         if (nviews != displayed_views() ||
2885             (nviews == 1 && base_view != display[0]))
2886                 resize_display();
2888         if (view->ops->open) {
2889                 if (!view->ops->open(view)) {
2890                         report("Failed to load %s view", view->name);
2891                         return;
2892                 }
2894         } else if ((reload || strcmp(view->vid, view->id)) &&
2895                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2896                 report("Failed to load %s view", view->name);
2897                 return;
2898         }
2900         if (split && prev->lineno - prev->offset >= prev->height) {
2901                 /* Take the title line into account. */
2902                 int lines = prev->lineno - prev->offset - prev->height + 1;
2904                 /* Scroll the view that was split if the current line is
2905                  * outside the new limited view. */
2906                 do_scroll_view(prev, lines);
2907         }
2909         if (prev && view != prev) {
2910                 if (split && !backgrounded) {
2911                         /* "Blur" the previous view. */
2912                         update_view_title(prev);
2913                 }
2915                 view->parent = prev;
2916         }
2918         if (view->pipe && view->lines == 0) {
2919                 /* Clear the old view and let the incremental updating refill
2920                  * the screen. */
2921                 werase(view->win);
2922                 report("");
2923         } else if (view_is_displayed(view)) {
2924                 redraw_view(view);
2925                 report("");
2926         }
2928         /* If the view is backgrounded the above calls to report()
2929          * won't redraw the view title. */
2930         if (backgrounded)
2931                 update_view_title(view);
2934 static void
2935 open_external_viewer(const char *argv[], const char *dir)
2937         def_prog_mode();           /* save current tty modes */
2938         endwin();                  /* restore original tty modes */
2939         run_io_fg(argv, dir);
2940         fprintf(stderr, "Press Enter to continue");
2941         getc(opt_tty);
2942         reset_prog_mode();
2943         redraw_display(TRUE);
2946 static void
2947 open_mergetool(const char *file)
2949         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2951         open_external_viewer(mergetool_argv, opt_cdup);
2954 static void
2955 open_editor(bool from_root, const char *file)
2957         const char *editor_argv[] = { "vi", file, NULL };
2958         const char *editor;
2960         editor = getenv("GIT_EDITOR");
2961         if (!editor && *opt_editor)
2962                 editor = opt_editor;
2963         if (!editor)
2964                 editor = getenv("VISUAL");
2965         if (!editor)
2966                 editor = getenv("EDITOR");
2967         if (!editor)
2968                 editor = "vi";
2970         editor_argv[0] = editor;
2971         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2974 static void
2975 open_run_request(enum request request)
2977         struct run_request *req = get_run_request(request);
2978         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2980         if (!req) {
2981                 report("Unknown run request");
2982                 return;
2983         }
2985         if (format_argv(argv, req->argv, FORMAT_ALL))
2986                 open_external_viewer(argv, NULL);
2987         free_argv(argv);
2990 /*
2991  * User request switch noodle
2992  */
2994 static int
2995 view_driver(struct view *view, enum request request)
2997         int i;
2999         if (request == REQ_NONE) {
3000                 doupdate();
3001                 return TRUE;
3002         }
3004         if (request > REQ_NONE) {
3005                 open_run_request(request);
3006                 /* FIXME: When all views can refresh always do this. */
3007                 if (view == VIEW(REQ_VIEW_STATUS) ||
3008                     view == VIEW(REQ_VIEW_MAIN) ||
3009                     view == VIEW(REQ_VIEW_LOG) ||
3010                     view == VIEW(REQ_VIEW_STAGE))
3011                         request = REQ_REFRESH;
3012                 else
3013                         return TRUE;
3014         }
3016         if (view && view->lines) {
3017                 request = view->ops->request(view, request, &view->line[view->lineno]);
3018                 if (request == REQ_NONE)
3019                         return TRUE;
3020         }
3022         switch (request) {
3023         case REQ_MOVE_UP:
3024         case REQ_MOVE_DOWN:
3025         case REQ_MOVE_PAGE_UP:
3026         case REQ_MOVE_PAGE_DOWN:
3027         case REQ_MOVE_FIRST_LINE:
3028         case REQ_MOVE_LAST_LINE:
3029                 move_view(view, request);
3030                 break;
3032         case REQ_SCROLL_LINE_DOWN:
3033         case REQ_SCROLL_LINE_UP:
3034         case REQ_SCROLL_PAGE_DOWN:
3035         case REQ_SCROLL_PAGE_UP:
3036                 scroll_view(view, request);
3037                 break;
3039         case REQ_VIEW_BLAME:
3040                 if (!opt_file[0]) {
3041                         report("No file chosen, press %s to open tree view",
3042                                get_key(REQ_VIEW_TREE));
3043                         break;
3044                 }
3045                 open_view(view, request, OPEN_DEFAULT);
3046                 break;
3048         case REQ_VIEW_BLOB:
3049                 if (!ref_blob[0]) {
3050                         report("No file chosen, press %s to open tree view",
3051                                get_key(REQ_VIEW_TREE));
3052                         break;
3053                 }
3054                 open_view(view, request, OPEN_DEFAULT);
3055                 break;
3057         case REQ_VIEW_PAGER:
3058                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3059                         report("No pager content, press %s to run command from prompt",
3060                                get_key(REQ_PROMPT));
3061                         break;
3062                 }
3063                 open_view(view, request, OPEN_DEFAULT);
3064                 break;
3066         case REQ_VIEW_STAGE:
3067                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3068                         report("No stage content, press %s to open the status view and choose file",
3069                                get_key(REQ_VIEW_STATUS));
3070                         break;
3071                 }
3072                 open_view(view, request, OPEN_DEFAULT);
3073                 break;
3075         case REQ_VIEW_STATUS:
3076                 if (opt_is_inside_work_tree == FALSE) {
3077                         report("The status view requires a working tree");
3078                         break;
3079                 }
3080                 open_view(view, request, OPEN_DEFAULT);
3081                 break;
3083         case REQ_VIEW_MAIN:
3084         case REQ_VIEW_DIFF:
3085         case REQ_VIEW_LOG:
3086         case REQ_VIEW_TREE:
3087         case REQ_VIEW_HELP:
3088                 open_view(view, request, OPEN_DEFAULT);
3089                 break;
3091         case REQ_NEXT:
3092         case REQ_PREVIOUS:
3093                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3095                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3096                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3097                    (view == VIEW(REQ_VIEW_DIFF) &&
3098                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3099                    (view == VIEW(REQ_VIEW_STAGE) &&
3100                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3101                    (view == VIEW(REQ_VIEW_BLOB) &&
3102                      view->parent == VIEW(REQ_VIEW_TREE))) {
3103                         int line;
3105                         view = view->parent;
3106                         line = view->lineno;
3107                         move_view(view, request);
3108                         if (view_is_displayed(view))
3109                                 update_view_title(view);
3110                         if (line != view->lineno)
3111                                 view->ops->request(view, REQ_ENTER,
3112                                                    &view->line[view->lineno]);
3114                 } else {
3115                         move_view(view, request);
3116                 }
3117                 break;
3119         case REQ_VIEW_NEXT:
3120         {
3121                 int nviews = displayed_views();
3122                 int next_view = (current_view + 1) % nviews;
3124                 if (next_view == current_view) {
3125                         report("Only one view is displayed");
3126                         break;
3127                 }
3129                 current_view = next_view;
3130                 /* Blur out the title of the previous view. */
3131                 update_view_title(view);
3132                 report("");
3133                 break;
3134         }
3135         case REQ_REFRESH:
3136                 report("Refreshing is not yet supported for the %s view", view->name);
3137                 break;
3139         case REQ_MAXIMIZE:
3140                 if (displayed_views() == 2)
3141                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3142                 break;
3144         case REQ_TOGGLE_LINENO:
3145                 toggle_view_option(&opt_line_number, "line numbers");
3146                 break;
3148         case REQ_TOGGLE_DATE:
3149                 toggle_view_option(&opt_date, "date display");
3150                 break;
3152         case REQ_TOGGLE_AUTHOR:
3153                 toggle_view_option(&opt_author, "author display");
3154                 break;
3156         case REQ_TOGGLE_REV_GRAPH:
3157                 toggle_view_option(&opt_rev_graph, "revision graph display");
3158                 break;
3160         case REQ_TOGGLE_REFS:
3161                 toggle_view_option(&opt_show_refs, "reference display");
3162                 break;
3164         case REQ_SEARCH:
3165         case REQ_SEARCH_BACK:
3166                 search_view(view, request);
3167                 break;
3169         case REQ_FIND_NEXT:
3170         case REQ_FIND_PREV:
3171                 find_next(view, request);
3172                 break;
3174         case REQ_STOP_LOADING:
3175                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3176                         view = &views[i];
3177                         if (view->pipe)
3178                                 report("Stopped loading the %s view", view->name),
3179                         end_update(view, TRUE);
3180                 }
3181                 break;
3183         case REQ_SHOW_VERSION:
3184                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3185                 return TRUE;
3187         case REQ_SCREEN_RESIZE:
3188                 resize_display();
3189                 /* Fall-through */
3190         case REQ_SCREEN_REDRAW:
3191                 redraw_display(TRUE);
3192                 break;
3194         case REQ_EDIT:
3195                 report("Nothing to edit");
3196                 break;
3198         case REQ_ENTER:
3199                 report("Nothing to enter");
3200                 break;
3202         case REQ_VIEW_CLOSE:
3203                 /* XXX: Mark closed views by letting view->parent point to the
3204                  * view itself. Parents to closed view should never be
3205                  * followed. */
3206                 if (view->parent &&
3207                     view->parent->parent != view->parent) {
3208                         memset(display, 0, sizeof(display));
3209                         current_view = 0;
3210                         display[current_view] = view->parent;
3211                         view->parent = view;
3212                         resize_display();
3213                         redraw_display(FALSE);
3214                         report("");
3215                         break;
3216                 }
3217                 /* Fall-through */
3218         case REQ_QUIT:
3219                 return FALSE;
3221         default:
3222                 report("Unknown key, press 'h' for help");
3223                 return TRUE;
3224         }
3226         return TRUE;
3230 /*
3231  * Pager backend
3232  */
3234 static bool
3235 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3237         char *text = line->data;
3239         if (opt_line_number && draw_lineno(view, lineno))
3240                 return TRUE;
3242         draw_text(view, line->type, text, TRUE);
3243         return TRUE;
3246 static bool
3247 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3249         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3250         char refbuf[SIZEOF_STR];
3251         char *ref = NULL;
3253         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3254                 ref = chomp_string(refbuf);
3256         if (!ref || !*ref)
3257                 return TRUE;
3259         /* This is the only fatal call, since it can "corrupt" the buffer. */
3260         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3261                 return FALSE;
3263         return TRUE;
3266 static void
3267 add_pager_refs(struct view *view, struct line *line)
3269         char buf[SIZEOF_STR];
3270         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3271         struct ref **refs;
3272         size_t bufpos = 0, refpos = 0;
3273         const char *sep = "Refs: ";
3274         bool is_tag = FALSE;
3276         assert(line->type == LINE_COMMIT);
3278         refs = get_refs(commit_id);
3279         if (!refs) {
3280                 if (view == VIEW(REQ_VIEW_DIFF))
3281                         goto try_add_describe_ref;
3282                 return;
3283         }
3285         do {
3286                 struct ref *ref = refs[refpos];
3287                 const char *fmt = ref->tag    ? "%s[%s]" :
3288                                   ref->remote ? "%s<%s>" : "%s%s";
3290                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3291                         return;
3292                 sep = ", ";
3293                 if (ref->tag)
3294                         is_tag = TRUE;
3295         } while (refs[refpos++]->next);
3297         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3298 try_add_describe_ref:
3299                 /* Add <tag>-g<commit_id> "fake" reference. */
3300                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3301                         return;
3302         }
3304         if (bufpos == 0)
3305                 return;
3307         add_line_text(view, buf, LINE_PP_REFS);
3310 static bool
3311 pager_read(struct view *view, char *data)
3313         struct line *line;
3315         if (!data)
3316                 return TRUE;
3318         line = add_line_text(view, data, get_line_type(data));
3319         if (!line)
3320                 return FALSE;
3322         if (line->type == LINE_COMMIT &&
3323             (view == VIEW(REQ_VIEW_DIFF) ||
3324              view == VIEW(REQ_VIEW_LOG)))
3325                 add_pager_refs(view, line);
3327         return TRUE;
3330 static enum request
3331 pager_request(struct view *view, enum request request, struct line *line)
3333         int split = 0;
3335         if (request != REQ_ENTER)
3336                 return request;
3338         if (line->type == LINE_COMMIT &&
3339            (view == VIEW(REQ_VIEW_LOG) ||
3340             view == VIEW(REQ_VIEW_PAGER))) {
3341                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3342                 split = 1;
3343         }
3345         /* Always scroll the view even if it was split. That way
3346          * you can use Enter to scroll through the log view and
3347          * split open each commit diff. */
3348         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3350         /* FIXME: A minor workaround. Scrolling the view will call report("")
3351          * but if we are scrolling a non-current view this won't properly
3352          * update the view title. */
3353         if (split)
3354                 update_view_title(view);
3356         return REQ_NONE;
3359 static bool
3360 pager_grep(struct view *view, struct line *line)
3362         regmatch_t pmatch;
3363         char *text = line->data;
3365         if (!*text)
3366                 return FALSE;
3368         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3369                 return FALSE;
3371         return TRUE;
3374 static void
3375 pager_select(struct view *view, struct line *line)
3377         if (line->type == LINE_COMMIT) {
3378                 char *text = (char *)line->data + STRING_SIZE("commit ");
3380                 if (view != VIEW(REQ_VIEW_PAGER))
3381                         string_copy_rev(view->ref, text);
3382                 string_copy_rev(ref_commit, text);
3383         }
3386 static struct view_ops pager_ops = {
3387         "line",
3388         NULL,
3389         NULL,
3390         pager_read,
3391         pager_draw,
3392         pager_request,
3393         pager_grep,
3394         pager_select,
3395 };
3397 static const char *log_argv[SIZEOF_ARG] = {
3398         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3399 };
3401 static enum request
3402 log_request(struct view *view, enum request request, struct line *line)
3404         switch (request) {
3405         case REQ_REFRESH:
3406                 load_refs();
3407                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3408                 return REQ_NONE;
3409         default:
3410                 return pager_request(view, request, line);
3411         }
3414 static struct view_ops log_ops = {
3415         "line",
3416         log_argv,
3417         NULL,
3418         pager_read,
3419         pager_draw,
3420         log_request,
3421         pager_grep,
3422         pager_select,
3423 };
3425 static const char *diff_argv[SIZEOF_ARG] = {
3426         "git", "show", "--pretty=fuller", "--no-color", "--root",
3427                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3428 };
3430 static struct view_ops diff_ops = {
3431         "line",
3432         diff_argv,
3433         NULL,
3434         pager_read,
3435         pager_draw,
3436         pager_request,
3437         pager_grep,
3438         pager_select,
3439 };
3441 /*
3442  * Help backend
3443  */
3445 static bool
3446 help_open(struct view *view)
3448         int lines = ARRAY_SIZE(req_info) + 2;
3449         int i;
3451         if (view->lines > 0)
3452                 return TRUE;
3454         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3455                 if (!req_info[i].request)
3456                         lines++;
3458         lines += run_requests + 1;
3460         view->line = calloc(lines, sizeof(*view->line));
3461         if (!view->line)
3462                 return FALSE;
3464         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3466         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3467                 const char *key;
3469                 if (req_info[i].request == REQ_NONE)
3470                         continue;
3472                 if (!req_info[i].request) {
3473                         add_line_text(view, "", LINE_DEFAULT);
3474                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3475                         continue;
3476                 }
3478                 key = get_key(req_info[i].request);
3479                 if (!*key)
3480                         key = "(no key defined)";
3482                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3483                                 key, req_info[i].help);
3484         }
3486         if (run_requests) {
3487                 add_line_text(view, "", LINE_DEFAULT);
3488                 add_line_text(view, "External commands:", LINE_DEFAULT);
3489         }
3491         for (i = 0; i < run_requests; i++) {
3492                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3493                 const char *key;
3494                 char cmd[SIZEOF_STR];
3495                 size_t bufpos;
3496                 int argc;
3498                 if (!req)
3499                         continue;
3501                 key = get_key_name(req->key);
3502                 if (!*key)
3503                         key = "(no key defined)";
3505                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3506                         if (!string_format_from(cmd, &bufpos, "%s%s",
3507                                                 argc ? " " : "", req->argv[argc]))
3508                                 return REQ_NONE;
3510                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3511                                 keymap_table[req->keymap].name, key, cmd);
3512         }
3514         return TRUE;
3517 static struct view_ops help_ops = {
3518         "line",
3519         NULL,
3520         help_open,
3521         NULL,
3522         pager_draw,
3523         pager_request,
3524         pager_grep,
3525         pager_select,
3526 };
3529 /*
3530  * Tree backend
3531  */
3533 struct tree_stack_entry {
3534         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3535         unsigned long lineno;           /* Line number to restore */
3536         char *name;                     /* Position of name in opt_path */
3537 };
3539 /* The top of the path stack. */
3540 static struct tree_stack_entry *tree_stack = NULL;
3541 unsigned long tree_lineno = 0;
3543 static void
3544 pop_tree_stack_entry(void)
3546         struct tree_stack_entry *entry = tree_stack;
3548         tree_lineno = entry->lineno;
3549         entry->name[0] = 0;
3550         tree_stack = entry->prev;
3551         free(entry);
3554 static void
3555 push_tree_stack_entry(const char *name, unsigned long lineno)
3557         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3558         size_t pathlen = strlen(opt_path);
3560         if (!entry)
3561                 return;
3563         entry->prev = tree_stack;
3564         entry->name = opt_path + pathlen;
3565         tree_stack = entry;
3567         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3568                 pop_tree_stack_entry();
3569                 return;
3570         }
3572         /* Move the current line to the first tree entry. */
3573         tree_lineno = 1;
3574         entry->lineno = lineno;
3577 /* Parse output from git-ls-tree(1):
3578  *
3579  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3580  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3581  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3582  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3583  */
3585 #define SIZEOF_TREE_ATTR \
3586         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3588 #define TREE_UP_FORMAT "040000 tree %s\t.."
3590 static const char *
3591 tree_path(struct line *line)
3593         const char *path = line->data;
3595         return path + SIZEOF_TREE_ATTR;
3598 static int
3599 tree_compare_entry(struct line *line1, struct line *line2)
3601         if (line1->type != line2->type)
3602                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3603         return strcmp(tree_path(line1), tree_path(line2));
3606 static bool
3607 tree_read(struct view *view, char *text)
3609         size_t textlen = text ? strlen(text) : 0;
3610         struct line *entry, *line;
3611         enum line_type type;
3613         if (!text)
3614                 return TRUE;
3615         if (textlen <= SIZEOF_TREE_ATTR)
3616                 return FALSE;
3618         type = text[STRING_SIZE("100644 ")] == 't'
3619              ? LINE_TREE_DIR : LINE_TREE_FILE;
3621         if (view->lines == 0 &&
3622             !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
3623                 return FALSE;
3625         /* Strip the path part ... */
3626         if (*opt_path) {
3627                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3628                 size_t striplen = strlen(opt_path);
3629                 char *path = text + SIZEOF_TREE_ATTR;
3631                 if (pathlen > striplen)
3632                         memmove(path, path + striplen,
3633                                 pathlen - striplen + 1);
3635                 /* Insert "link" to parent directory. */
3636                 if (view->lines == 1 &&
3637                     !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
3638                         return FALSE;
3639         }
3641         entry = add_line_text(view, text, type);
3642         if (!entry)
3643                 return FALSE;
3644         text = entry->data;
3646         /* Skip "Directory ..." and ".." line. */
3647         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3648                 if (tree_compare_entry(line, entry) <= 0)
3649                         continue;
3651                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3653                 line->data = text;
3654                 line->type = type;
3655                 for (; line <= entry; line++)
3656                         line->dirty = line->cleareol = 1;
3657                 return TRUE;
3658         }
3660         if (tree_lineno > view->lineno) {
3661                 view->lineno = tree_lineno;
3662                 tree_lineno = 0;
3663         }
3665         return TRUE;
3668 static void
3669 open_blob_editor()
3671         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3672         int fd = mkstemp(file);
3674         if (fd == -1)
3675                 report("Failed to create temporary file");
3676         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3677                 report("Failed to save blob data to file");
3678         else
3679                 open_editor(FALSE, file);
3680         if (fd != -1)
3681                 unlink(file);
3684 static enum request
3685 tree_request(struct view *view, enum request request, struct line *line)
3687         enum open_flags flags;
3689         switch (request) {
3690         case REQ_VIEW_BLAME:
3691                 if (line->type != LINE_TREE_FILE) {
3692                         report("Blame only supported for files");
3693                         return REQ_NONE;
3694                 }
3696                 string_copy(opt_ref, view->vid);
3697                 return request;
3699         case REQ_EDIT:
3700                 if (line->type != LINE_TREE_FILE) {
3701                         report("Edit only supported for files");
3702                 } else if (!is_head_commit(view->vid)) {
3703                         open_blob_editor();
3704                 } else {
3705                         open_editor(TRUE, opt_file);
3706                 }
3707                 return REQ_NONE;
3709         case REQ_TREE_PARENT:
3710                 if (!*opt_path) {
3711                         /* quit view if at top of tree */
3712                         return REQ_VIEW_CLOSE;
3713                 }
3714                 /* fake 'cd  ..' */
3715                 line = &view->line[1];
3716                 break;
3718         case REQ_ENTER:
3719                 break;
3721         default:
3722                 return request;
3723         }
3725         /* Cleanup the stack if the tree view is at a different tree. */
3726         while (!*opt_path && tree_stack)
3727                 pop_tree_stack_entry();
3729         switch (line->type) {
3730         case LINE_TREE_DIR:
3731                 /* Depending on whether it is a subdir or parent (updir?) link
3732                  * mangle the path buffer. */
3733                 if (line == &view->line[1] && *opt_path) {
3734                         pop_tree_stack_entry();
3736                 } else {
3737                         const char *basename = tree_path(line);
3739                         push_tree_stack_entry(basename, view->lineno);
3740                 }
3742                 /* Trees and subtrees share the same ID, so they are not not
3743                  * unique like blobs. */
3744                 flags = OPEN_RELOAD;
3745                 request = REQ_VIEW_TREE;
3746                 break;
3748         case LINE_TREE_FILE:
3749                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3750                 request = REQ_VIEW_BLOB;
3751                 break;
3753         default:
3754                 return TRUE;
3755         }
3757         open_view(view, request, flags);
3758         if (request == REQ_VIEW_TREE) {
3759                 view->lineno = tree_lineno;
3760         }
3762         return REQ_NONE;
3765 static void
3766 tree_select(struct view *view, struct line *line)
3768         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3770         if (line->type == LINE_TREE_FILE) {
3771                 string_copy_rev(ref_blob, text);
3772                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3774         } else if (line->type != LINE_TREE_DIR) {
3775                 return;
3776         }
3778         string_copy_rev(view->ref, text);
3781 static const char *tree_argv[SIZEOF_ARG] = {
3782         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3783 };
3785 static struct view_ops tree_ops = {
3786         "file",
3787         tree_argv,
3788         NULL,
3789         tree_read,
3790         pager_draw,
3791         tree_request,
3792         pager_grep,
3793         tree_select,
3794 };
3796 static bool
3797 blob_read(struct view *view, char *line)
3799         if (!line)
3800                 return TRUE;
3801         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3804 static enum request
3805 blob_request(struct view *view, enum request request, struct line *line)
3807         switch (request) {
3808         case REQ_EDIT:
3809                 open_blob_editor();
3810                 return REQ_NONE;
3811         default:
3812                 return pager_request(view, request, line);
3813         }
3816 static const char *blob_argv[SIZEOF_ARG] = {
3817         "git", "cat-file", "blob", "%(blob)", NULL
3818 };
3820 static struct view_ops blob_ops = {
3821         "line",
3822         blob_argv,
3823         NULL,
3824         blob_read,
3825         pager_draw,
3826         blob_request,
3827         pager_grep,
3828         pager_select,
3829 };
3831 /*
3832  * Blame backend
3833  *
3834  * Loading the blame view is a two phase job:
3835  *
3836  *  1. File content is read either using opt_file from the
3837  *     filesystem or using git-cat-file.
3838  *  2. Then blame information is incrementally added by
3839  *     reading output from git-blame.
3840  */
3842 static const char *blame_head_argv[] = {
3843         "git", "blame", "--incremental", "--", "%(file)", NULL
3844 };
3846 static const char *blame_ref_argv[] = {
3847         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3848 };
3850 static const char *blame_cat_file_argv[] = {
3851         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3852 };
3854 struct blame_commit {
3855         char id[SIZEOF_REV];            /* SHA1 ID. */
3856         char title[128];                /* First line of the commit message. */
3857         char author[75];                /* Author of the commit. */
3858         struct tm time;                 /* Date from the author ident. */
3859         char filename[128];             /* Name of file. */
3860 };
3862 struct blame {
3863         struct blame_commit *commit;
3864         char text[1];
3865 };
3867 static bool
3868 blame_open(struct view *view)
3870         if (*opt_ref || !io_open(&view->io, opt_file)) {
3871                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3872                         return FALSE;
3873         }
3875         setup_update(view, opt_file);
3876         string_format(view->ref, "%s ...", opt_file);
3878         return TRUE;
3881 static struct blame_commit *
3882 get_blame_commit(struct view *view, const char *id)
3884         size_t i;
3886         for (i = 0; i < view->lines; i++) {
3887                 struct blame *blame = view->line[i].data;
3889                 if (!blame->commit)
3890                         continue;
3892                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3893                         return blame->commit;
3894         }
3896         {
3897                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3899                 if (commit)
3900                         string_ncopy(commit->id, id, SIZEOF_REV);
3901                 return commit;
3902         }
3905 static bool
3906 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3908         const char *pos = *posref;
3910         *posref = NULL;
3911         pos = strchr(pos + 1, ' ');
3912         if (!pos || !isdigit(pos[1]))
3913                 return FALSE;
3914         *number = atoi(pos + 1);
3915         if (*number < min || *number > max)
3916                 return FALSE;
3918         *posref = pos;
3919         return TRUE;
3922 static struct blame_commit *
3923 parse_blame_commit(struct view *view, const char *text, int *blamed)
3925         struct blame_commit *commit;
3926         struct blame *blame;
3927         const char *pos = text + SIZEOF_REV - 1;
3928         size_t lineno;
3929         size_t group;
3931         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3932                 return NULL;
3934         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3935             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3936                 return NULL;
3938         commit = get_blame_commit(view, text);
3939         if (!commit)
3940                 return NULL;
3942         *blamed += group;
3943         while (group--) {
3944                 struct line *line = &view->line[lineno + group - 1];
3946                 blame = line->data;
3947                 blame->commit = commit;
3948                 line->dirty = 1;
3949         }
3951         return commit;
3954 static bool
3955 blame_read_file(struct view *view, const char *line, bool *read_file)
3957         if (!line) {
3958                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3959                 struct io io = {};
3961                 if (view->lines == 0 && !view->parent)
3962                         die("No blame exist for %s", view->vid);
3964                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3965                         report("Failed to load blame data");
3966                         return TRUE;
3967                 }
3969                 done_io(view->pipe);
3970                 view->io = io;
3971                 *read_file = FALSE;
3972                 return FALSE;
3974         } else {
3975                 size_t linelen = strlen(line);
3976                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3978                 blame->commit = NULL;
3979                 strncpy(blame->text, line, linelen);
3980                 blame->text[linelen] = 0;
3981                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3982         }
3985 static bool
3986 match_blame_header(const char *name, char **line)
3988         size_t namelen = strlen(name);
3989         bool matched = !strncmp(name, *line, namelen);
3991         if (matched)
3992                 *line += namelen;
3994         return matched;
3997 static bool
3998 blame_read(struct view *view, char *line)
4000         static struct blame_commit *commit = NULL;
4001         static int blamed = 0;
4002         static time_t author_time;
4003         static bool read_file = TRUE;
4005         if (read_file)
4006                 return blame_read_file(view, line, &read_file);
4008         if (!line) {
4009                 /* Reset all! */
4010                 commit = NULL;
4011                 blamed = 0;
4012                 read_file = TRUE;
4013                 string_format(view->ref, "%s", view->vid);
4014                 if (view_is_displayed(view)) {
4015                         update_view_title(view);
4016                         redraw_view_from(view, 0);
4017                 }
4018                 return TRUE;
4019         }
4021         if (!commit) {
4022                 commit = parse_blame_commit(view, line, &blamed);
4023                 string_format(view->ref, "%s %2d%%", view->vid,
4024                               blamed * 100 / view->lines);
4026         } else if (match_blame_header("author ", &line)) {
4027                 string_ncopy(commit->author, line, strlen(line));
4029         } else if (match_blame_header("author-time ", &line)) {
4030                 author_time = (time_t) atol(line);
4032         } else if (match_blame_header("author-tz ", &line)) {
4033                 long tz;
4035                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4036                 tz += ('0' - line[2]) * 60 * 60;
4037                 tz += ('0' - line[3]) * 60;
4038                 tz += ('0' - line[4]) * 60;
4040                 if (line[0] == '-')
4041                         tz = -tz;
4043                 author_time -= tz;
4044                 gmtime_r(&author_time, &commit->time);
4046         } else if (match_blame_header("summary ", &line)) {
4047                 string_ncopy(commit->title, line, strlen(line));
4049         } else if (match_blame_header("filename ", &line)) {
4050                 string_ncopy(commit->filename, line, strlen(line));
4051                 commit = NULL;
4052         }
4054         return TRUE;
4057 static bool
4058 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4060         struct blame *blame = line->data;
4061         struct tm *time = NULL;
4062         const char *id = NULL, *author = NULL;
4064         if (blame->commit && *blame->commit->filename) {
4065                 id = blame->commit->id;
4066                 author = blame->commit->author;
4067                 time = &blame->commit->time;
4068         }
4070         if (opt_date && draw_date(view, time))
4071                 return TRUE;
4073         if (opt_author &&
4074             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4075                 return TRUE;
4077         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4078                 return TRUE;
4080         if (draw_lineno(view, lineno))
4081                 return TRUE;
4083         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4084         return TRUE;
4087 static bool
4088 check_blame_commit(struct blame *blame)
4090         if (!blame->commit)
4091                 report("Commit data not loaded yet");
4092         else if (!strcmp(blame->commit->id, NULL_ID))
4093                 report("No commit exist for the selected line");
4094         else
4095                 return TRUE;
4096         return FALSE;
4099 static enum request
4100 blame_request(struct view *view, enum request request, struct line *line)
4102         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4103         struct blame *blame = line->data;
4105         switch (request) {
4106         case REQ_VIEW_BLAME:
4107                 if (check_blame_commit(blame)) {
4108                         string_copy(opt_ref, blame->commit->id);
4109                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4110                 }
4111                 break;
4113         case REQ_ENTER:
4114                 if (!blame->commit) {
4115                         report("No commit loaded yet");
4116                         break;
4117                 }
4119                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4120                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4121                         break;
4123                 if (!strcmp(blame->commit->id, NULL_ID)) {
4124                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4125                         const char *diff_index_argv[] = {
4126                                 "git", "diff-index", "--root", "--cached",
4127                                         "--patch-with-stat", "-C", "-M",
4128                                         "HEAD", "--", view->vid, NULL
4129                         };
4131                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4132                                 report("Failed to allocate diff command");
4133                                 break;
4134                         }
4135                         flags |= OPEN_PREPARED;
4136                 }
4138                 open_view(view, REQ_VIEW_DIFF, flags);
4139                 break;
4141         default:
4142                 return request;
4143         }
4145         return REQ_NONE;
4148 static bool
4149 blame_grep(struct view *view, struct line *line)
4151         struct blame *blame = line->data;
4152         struct blame_commit *commit = blame->commit;
4153         regmatch_t pmatch;
4155 #define MATCH(text, on)                                                 \
4156         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4158         if (commit) {
4159                 char buf[DATE_COLS + 1];
4161                 if (MATCH(commit->title, 1) ||
4162                     MATCH(commit->author, opt_author) ||
4163                     MATCH(commit->id, opt_date))
4164                         return TRUE;
4166                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4167                     MATCH(buf, 1))
4168                         return TRUE;
4169         }
4171         return MATCH(blame->text, 1);
4173 #undef MATCH
4176 static void
4177 blame_select(struct view *view, struct line *line)
4179         struct blame *blame = line->data;
4180         struct blame_commit *commit = blame->commit;
4182         if (!commit)
4183                 return;
4185         if (!strcmp(commit->id, NULL_ID))
4186                 string_ncopy(ref_commit, "HEAD", 4);
4187         else
4188                 string_copy_rev(ref_commit, commit->id);
4191 static struct view_ops blame_ops = {
4192         "line",
4193         NULL,
4194         blame_open,
4195         blame_read,
4196         blame_draw,
4197         blame_request,
4198         blame_grep,
4199         blame_select,
4200 };
4202 /*
4203  * Status backend
4204  */
4206 struct status {
4207         char status;
4208         struct {
4209                 mode_t mode;
4210                 char rev[SIZEOF_REV];
4211                 char name[SIZEOF_STR];
4212         } old;
4213         struct {
4214                 mode_t mode;
4215                 char rev[SIZEOF_REV];
4216                 char name[SIZEOF_STR];
4217         } new;
4218 };
4220 static char status_onbranch[SIZEOF_STR];
4221 static struct status stage_status;
4222 static enum line_type stage_line_type;
4223 static size_t stage_chunks;
4224 static int *stage_chunk;
4226 /* This should work even for the "On branch" line. */
4227 static inline bool
4228 status_has_none(struct view *view, struct line *line)
4230         return line < view->line + view->lines && !line[1].data;
4233 /* Get fields from the diff line:
4234  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4235  */
4236 static inline bool
4237 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4239         const char *old_mode = buf +  1;
4240         const char *new_mode = buf +  8;
4241         const char *old_rev  = buf + 15;
4242         const char *new_rev  = buf + 56;
4243         const char *status   = buf + 97;
4245         if (bufsize < 98 ||
4246             old_mode[-1] != ':' ||
4247             new_mode[-1] != ' ' ||
4248             old_rev[-1]  != ' ' ||
4249             new_rev[-1]  != ' ' ||
4250             status[-1]   != ' ')
4251                 return FALSE;
4253         file->status = *status;
4255         string_copy_rev(file->old.rev, old_rev);
4256         string_copy_rev(file->new.rev, new_rev);
4258         file->old.mode = strtoul(old_mode, NULL, 8);
4259         file->new.mode = strtoul(new_mode, NULL, 8);
4261         file->old.name[0] = file->new.name[0] = 0;
4263         return TRUE;
4266 static bool
4267 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4269         struct status *file = NULL;
4270         struct status *unmerged = NULL;
4271         char *buf;
4272         struct io io = {};
4274         if (!run_io(&io, argv, NULL, IO_RD))
4275                 return FALSE;
4277         add_line_data(view, NULL, type);
4279         while ((buf = io_get(&io, 0, TRUE))) {
4280                 if (!file) {
4281                         file = calloc(1, sizeof(*file));
4282                         if (!file || !add_line_data(view, file, type))
4283                                 goto error_out;
4284                 }
4286                 /* Parse diff info part. */
4287                 if (status) {
4288                         file->status = status;
4289                         if (status == 'A')
4290                                 string_copy(file->old.rev, NULL_ID);
4292                 } else if (!file->status) {
4293                         if (!status_get_diff(file, buf, strlen(buf)))
4294                                 goto error_out;
4296                         buf = io_get(&io, 0, TRUE);
4297                         if (!buf)
4298                                 break;
4300                         /* Collapse all 'M'odified entries that follow a
4301                          * associated 'U'nmerged entry. */
4302                         if (file->status == 'U') {
4303                                 unmerged = file;
4305                         } else if (unmerged) {
4306                                 int collapse = !strcmp(buf, unmerged->new.name);
4308                                 unmerged = NULL;
4309                                 if (collapse) {
4310                                         free(file);
4311                                         view->lines--;
4312                                         continue;
4313                                 }
4314                         }
4315                 }
4317                 /* Grab the old name for rename/copy. */
4318                 if (!*file->old.name &&
4319                     (file->status == 'R' || file->status == 'C')) {
4320                         string_ncopy(file->old.name, buf, strlen(buf));
4322                         buf = io_get(&io, 0, TRUE);
4323                         if (!buf)
4324                                 break;
4325                 }
4327                 /* git-ls-files just delivers a NUL separated list of
4328                  * file names similar to the second half of the
4329                  * git-diff-* output. */
4330                 string_ncopy(file->new.name, buf, strlen(buf));
4331                 if (!*file->old.name)
4332                         string_copy(file->old.name, file->new.name);
4333                 file = NULL;
4334         }
4336         if (io_error(&io)) {
4337 error_out:
4338                 done_io(&io);
4339                 return FALSE;
4340         }
4342         if (!view->line[view->lines - 1].data)
4343                 add_line_data(view, NULL, LINE_STAT_NONE);
4345         done_io(&io);
4346         return TRUE;
4349 /* Don't show unmerged entries in the staged section. */
4350 static const char *status_diff_index_argv[] = {
4351         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4352                              "--cached", "-M", "HEAD", NULL
4353 };
4355 static const char *status_diff_files_argv[] = {
4356         "git", "diff-files", "-z", NULL
4357 };
4359 static const char *status_list_other_argv[] = {
4360         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4361 };
4363 static const char *status_list_no_head_argv[] = {
4364         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4365 };
4367 static const char *update_index_argv[] = {
4368         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4369 };
4371 /* First parse staged info using git-diff-index(1), then parse unstaged
4372  * info using git-diff-files(1), and finally untracked files using
4373  * git-ls-files(1). */
4374 static bool
4375 status_open(struct view *view)
4377         unsigned long prev_lineno = view->lineno;
4379         reset_view(view);
4381         add_line_data(view, NULL, LINE_STAT_HEAD);
4382         if (is_initial_commit())
4383                 string_copy(status_onbranch, "Initial commit");
4384         else if (!*opt_head)
4385                 string_copy(status_onbranch, "Not currently on any branch");
4386         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4387                 return FALSE;
4389         run_io_bg(update_index_argv);
4391         if (is_initial_commit()) {
4392                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4393                         return FALSE;
4394         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4395                 return FALSE;
4396         }
4398         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4399             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4400                 return FALSE;
4402         /* If all went well restore the previous line number to stay in
4403          * the context or select a line with something that can be
4404          * updated. */
4405         if (prev_lineno >= view->lines)
4406                 prev_lineno = view->lines - 1;
4407         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4408                 prev_lineno++;
4409         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4410                 prev_lineno--;
4412         /* If the above fails, always skip the "On branch" line. */
4413         if (prev_lineno < view->lines)
4414                 view->lineno = prev_lineno;
4415         else
4416                 view->lineno = 1;
4418         if (view->lineno < view->offset)
4419                 view->offset = view->lineno;
4420         else if (view->offset + view->height <= view->lineno)
4421                 view->offset = view->lineno - view->height + 1;
4423         return TRUE;
4426 static bool
4427 status_draw(struct view *view, struct line *line, unsigned int lineno)
4429         struct status *status = line->data;
4430         enum line_type type;
4431         const char *text;
4433         if (!status) {
4434                 switch (line->type) {
4435                 case LINE_STAT_STAGED:
4436                         type = LINE_STAT_SECTION;
4437                         text = "Changes to be committed:";
4438                         break;
4440                 case LINE_STAT_UNSTAGED:
4441                         type = LINE_STAT_SECTION;
4442                         text = "Changed but not updated:";
4443                         break;
4445                 case LINE_STAT_UNTRACKED:
4446                         type = LINE_STAT_SECTION;
4447                         text = "Untracked files:";
4448                         break;
4450                 case LINE_STAT_NONE:
4451                         type = LINE_DEFAULT;
4452                         text = "    (no files)";
4453                         break;
4455                 case LINE_STAT_HEAD:
4456                         type = LINE_STAT_HEAD;
4457                         text = status_onbranch;
4458                         break;
4460                 default:
4461                         return FALSE;
4462                 }
4463         } else {
4464                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4466                 buf[0] = status->status;
4467                 if (draw_text(view, line->type, buf, TRUE))
4468                         return TRUE;
4469                 type = LINE_DEFAULT;
4470                 text = status->new.name;
4471         }
4473         draw_text(view, type, text, TRUE);
4474         return TRUE;
4477 static enum request
4478 status_enter(struct view *view, struct line *line)
4480         struct status *status = line->data;
4481         const char *oldpath = status ? status->old.name : NULL;
4482         /* Diffs for unmerged entries are empty when passing the new
4483          * path, so leave it empty. */
4484         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4485         const char *info;
4486         enum open_flags split;
4487         struct view *stage = VIEW(REQ_VIEW_STAGE);
4489         if (line->type == LINE_STAT_NONE ||
4490             (!status && line[1].type == LINE_STAT_NONE)) {
4491                 report("No file to diff");
4492                 return REQ_NONE;
4493         }
4495         switch (line->type) {
4496         case LINE_STAT_STAGED:
4497                 if (is_initial_commit()) {
4498                         const char *no_head_diff_argv[] = {
4499                                 "git", "diff", "--no-color", "--patch-with-stat",
4500                                         "--", "/dev/null", newpath, NULL
4501                         };
4503                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4504                                 return REQ_QUIT;
4505                 } else {
4506                         const char *index_show_argv[] = {
4507                                 "git", "diff-index", "--root", "--patch-with-stat",
4508                                         "-C", "-M", "--cached", "HEAD", "--",
4509                                         oldpath, newpath, NULL
4510                         };
4512                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4513                                 return REQ_QUIT;
4514                 }
4516                 if (status)
4517                         info = "Staged changes to %s";
4518                 else
4519                         info = "Staged changes";
4520                 break;
4522         case LINE_STAT_UNSTAGED:
4523         {
4524                 const char *files_show_argv[] = {
4525                         "git", "diff-files", "--root", "--patch-with-stat",
4526                                 "-C", "-M", "--", oldpath, newpath, NULL
4527                 };
4529                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4530                         return REQ_QUIT;
4531                 if (status)
4532                         info = "Unstaged changes to %s";
4533                 else
4534                         info = "Unstaged changes";
4535                 break;
4536         }
4537         case LINE_STAT_UNTRACKED:
4538                 if (!newpath) {
4539                         report("No file to show");
4540                         return REQ_NONE;
4541                 }
4543                 if (!suffixcmp(status->new.name, -1, "/")) {
4544                         report("Cannot display a directory");
4545                         return REQ_NONE;
4546                 }
4548                 if (!prepare_update_file(stage, newpath))
4549                         return REQ_QUIT;
4550                 info = "Untracked file %s";
4551                 break;
4553         case LINE_STAT_HEAD:
4554                 return REQ_NONE;
4556         default:
4557                 die("line type %d not handled in switch", line->type);
4558         }
4560         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4561         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4562         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4563                 if (status) {
4564                         stage_status = *status;
4565                 } else {
4566                         memset(&stage_status, 0, sizeof(stage_status));
4567                 }
4569                 stage_line_type = line->type;
4570                 stage_chunks = 0;
4571                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4572         }
4574         return REQ_NONE;
4577 static bool
4578 status_exists(struct status *status, enum line_type type)
4580         struct view *view = VIEW(REQ_VIEW_STATUS);
4581         struct line *line;
4583         for (line = view->line; line < view->line + view->lines; line++) {
4584                 struct status *pos = line->data;
4586                 if (line->type != type)
4587                         continue;
4588                 if (!pos && (!status || !status->status))
4589                         return TRUE;
4590                 if (pos && !strcmp(status->new.name, pos->new.name))
4591                         return TRUE;
4592         }
4594         return FALSE;
4598 static bool
4599 status_update_prepare(struct io *io, enum line_type type)
4601         const char *staged_argv[] = {
4602                 "git", "update-index", "-z", "--index-info", NULL
4603         };
4604         const char *others_argv[] = {
4605                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4606         };
4608         switch (type) {
4609         case LINE_STAT_STAGED:
4610                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4612         case LINE_STAT_UNSTAGED:
4613                 return run_io(io, others_argv, opt_cdup, IO_WR);
4615         case LINE_STAT_UNTRACKED:
4616                 return run_io(io, others_argv, NULL, IO_WR);
4618         default:
4619                 die("line type %d not handled in switch", type);
4620                 return FALSE;
4621         }
4624 static bool
4625 status_update_write(struct io *io, struct status *status, enum line_type type)
4627         char buf[SIZEOF_STR];
4628         size_t bufsize = 0;
4630         switch (type) {
4631         case LINE_STAT_STAGED:
4632                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4633                                         status->old.mode,
4634                                         status->old.rev,
4635                                         status->old.name, 0))
4636                         return FALSE;
4637                 break;
4639         case LINE_STAT_UNSTAGED:
4640         case LINE_STAT_UNTRACKED:
4641                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4642                         return FALSE;
4643                 break;
4645         default:
4646                 die("line type %d not handled in switch", type);
4647         }
4649         return io_write(io, buf, bufsize);
4652 static bool
4653 status_update_file(struct status *status, enum line_type type)
4655         struct io io = {};
4656         bool result;
4658         if (!status_update_prepare(&io, type))
4659                 return FALSE;
4661         result = status_update_write(&io, status, type);
4662         done_io(&io);
4663         return result;
4666 static bool
4667 status_update_files(struct view *view, struct line *line)
4669         struct io io = {};
4670         bool result = TRUE;
4671         struct line *pos = view->line + view->lines;
4672         int files = 0;
4673         int file, done;
4675         if (!status_update_prepare(&io, line->type))
4676                 return FALSE;
4678         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4679                 files++;
4681         for (file = 0, done = 0; result && file < files; line++, file++) {
4682                 int almost_done = file * 100 / files;
4684                 if (almost_done > done) {
4685                         done = almost_done;
4686                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4687                                       file, files, done);
4688                         update_view_title(view);
4689                 }
4690                 result = status_update_write(&io, line->data, line->type);
4691         }
4693         done_io(&io);
4694         return result;
4697 static bool
4698 status_update(struct view *view)
4700         struct line *line = &view->line[view->lineno];
4702         assert(view->lines);
4704         if (!line->data) {
4705                 /* This should work even for the "On branch" line. */
4706                 if (line < view->line + view->lines && !line[1].data) {
4707                         report("Nothing to update");
4708                         return FALSE;
4709                 }
4711                 if (!status_update_files(view, line + 1)) {
4712                         report("Failed to update file status");
4713                         return FALSE;
4714                 }
4716         } else if (!status_update_file(line->data, line->type)) {
4717                 report("Failed to update file status");
4718                 return FALSE;
4719         }
4721         return TRUE;
4724 static bool
4725 status_revert(struct status *status, enum line_type type, bool has_none)
4727         if (!status || type != LINE_STAT_UNSTAGED) {
4728                 if (type == LINE_STAT_STAGED) {
4729                         report("Cannot revert changes to staged files");
4730                 } else if (type == LINE_STAT_UNTRACKED) {
4731                         report("Cannot revert changes to untracked files");
4732                 } else if (has_none) {
4733                         report("Nothing to revert");
4734                 } else {
4735                         report("Cannot revert changes to multiple files");
4736                 }
4737                 return FALSE;
4739         } else {
4740                 const char *checkout_argv[] = {
4741                         "git", "checkout", "--", status->old.name, NULL
4742                 };
4744                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4745                         return FALSE;
4746                 return run_io_fg(checkout_argv, opt_cdup);
4747         }
4750 static enum request
4751 status_request(struct view *view, enum request request, struct line *line)
4753         struct status *status = line->data;
4755         switch (request) {
4756         case REQ_STATUS_UPDATE:
4757                 if (!status_update(view))
4758                         return REQ_NONE;
4759                 break;
4761         case REQ_STATUS_REVERT:
4762                 if (!status_revert(status, line->type, status_has_none(view, line)))
4763                         return REQ_NONE;
4764                 break;
4766         case REQ_STATUS_MERGE:
4767                 if (!status || status->status != 'U') {
4768                         report("Merging only possible for files with unmerged status ('U').");
4769                         return REQ_NONE;
4770                 }
4771                 open_mergetool(status->new.name);
4772                 break;
4774         case REQ_EDIT:
4775                 if (!status)
4776                         return request;
4777                 if (status->status == 'D') {
4778                         report("File has been deleted.");
4779                         return REQ_NONE;
4780                 }
4782                 open_editor(status->status != '?', status->new.name);
4783                 break;
4785         case REQ_VIEW_BLAME:
4786                 if (status) {
4787                         string_copy(opt_file, status->new.name);
4788                         opt_ref[0] = 0;
4789                 }
4790                 return request;
4792         case REQ_ENTER:
4793                 /* After returning the status view has been split to
4794                  * show the stage view. No further reloading is
4795                  * necessary. */
4796                 status_enter(view, line);
4797                 return REQ_NONE;
4799         case REQ_REFRESH:
4800                 /* Simply reload the view. */
4801                 break;
4803         default:
4804                 return request;
4805         }
4807         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4809         return REQ_NONE;
4812 static void
4813 status_select(struct view *view, struct line *line)
4815         struct status *status = line->data;
4816         char file[SIZEOF_STR] = "all files";
4817         const char *text;
4818         const char *key;
4820         if (status && !string_format(file, "'%s'", status->new.name))
4821                 return;
4823         if (!status && line[1].type == LINE_STAT_NONE)
4824                 line++;
4826         switch (line->type) {
4827         case LINE_STAT_STAGED:
4828                 text = "Press %s to unstage %s for commit";
4829                 break;
4831         case LINE_STAT_UNSTAGED:
4832                 text = "Press %s to stage %s for commit";
4833                 break;
4835         case LINE_STAT_UNTRACKED:
4836                 text = "Press %s to stage %s for addition";
4837                 break;
4839         case LINE_STAT_HEAD:
4840         case LINE_STAT_NONE:
4841                 text = "Nothing to update";
4842                 break;
4844         default:
4845                 die("line type %d not handled in switch", line->type);
4846         }
4848         if (status && status->status == 'U') {
4849                 text = "Press %s to resolve conflict in %s";
4850                 key = get_key(REQ_STATUS_MERGE);
4852         } else {
4853                 key = get_key(REQ_STATUS_UPDATE);
4854         }
4856         string_format(view->ref, text, key, file);
4859 static bool
4860 status_grep(struct view *view, struct line *line)
4862         struct status *status = line->data;
4863         enum { S_STATUS, S_NAME, S_END } state;
4864         char buf[2] = "?";
4865         regmatch_t pmatch;
4867         if (!status)
4868                 return FALSE;
4870         for (state = S_STATUS; state < S_END; state++) {
4871                 const char *text;
4873                 switch (state) {
4874                 case S_NAME:    text = status->new.name;        break;
4875                 case S_STATUS:
4876                         buf[0] = status->status;
4877                         text = buf;
4878                         break;
4880                 default:
4881                         return FALSE;
4882                 }
4884                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4885                         return TRUE;
4886         }
4888         return FALSE;
4891 static struct view_ops status_ops = {
4892         "file",
4893         NULL,
4894         status_open,
4895         NULL,
4896         status_draw,
4897         status_request,
4898         status_grep,
4899         status_select,
4900 };
4903 static bool
4904 stage_diff_write(struct io *io, struct line *line, struct line *end)
4906         while (line < end) {
4907                 if (!io_write(io, line->data, strlen(line->data)) ||
4908                     !io_write(io, "\n", 1))
4909                         return FALSE;
4910                 line++;
4911                 if (line->type == LINE_DIFF_CHUNK ||
4912                     line->type == LINE_DIFF_HEADER)
4913                         break;
4914         }
4916         return TRUE;
4919 static struct line *
4920 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4922         for (; view->line < line; line--)
4923                 if (line->type == type)
4924                         return line;
4926         return NULL;
4929 static bool
4930 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4932         const char *apply_argv[SIZEOF_ARG] = {
4933                 "git", "apply", "--whitespace=nowarn", NULL
4934         };
4935         struct line *diff_hdr;
4936         struct io io = {};
4937         int argc = 3;
4939         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4940         if (!diff_hdr)
4941                 return FALSE;
4943         if (!revert)
4944                 apply_argv[argc++] = "--cached";
4945         if (revert || stage_line_type == LINE_STAT_STAGED)
4946                 apply_argv[argc++] = "-R";
4947         apply_argv[argc++] = "-";
4948         apply_argv[argc++] = NULL;
4949         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4950                 return FALSE;
4952         if (!stage_diff_write(&io, diff_hdr, chunk) ||
4953             !stage_diff_write(&io, chunk, view->line + view->lines))
4954                 chunk = NULL;
4956         done_io(&io);
4957         run_io_bg(update_index_argv);
4959         return chunk ? TRUE : FALSE;
4962 static bool
4963 stage_update(struct view *view, struct line *line)
4965         struct line *chunk = NULL;
4967         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4968                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4970         if (chunk) {
4971                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4972                         report("Failed to apply chunk");
4973                         return FALSE;
4974                 }
4976         } else if (!stage_status.status) {
4977                 view = VIEW(REQ_VIEW_STATUS);
4979                 for (line = view->line; line < view->line + view->lines; line++)
4980                         if (line->type == stage_line_type)
4981                                 break;
4983                 if (!status_update_files(view, line + 1)) {
4984                         report("Failed to update files");
4985                         return FALSE;
4986                 }
4988         } else if (!status_update_file(&stage_status, stage_line_type)) {
4989                 report("Failed to update file");
4990                 return FALSE;
4991         }
4993         return TRUE;
4996 static bool
4997 stage_revert(struct view *view, struct line *line)
4999         struct line *chunk = NULL;
5001         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5002                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5004         if (chunk) {
5005                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5006                         return FALSE;
5008                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5009                         report("Failed to revert chunk");
5010                         return FALSE;
5011                 }
5012                 return TRUE;
5014         } else {
5015                 return status_revert(stage_status.status ? &stage_status : NULL,
5016                                      stage_line_type, FALSE);
5017         }
5021 static void
5022 stage_next(struct view *view, struct line *line)
5024         int i;
5026         if (!stage_chunks) {
5027                 static size_t alloc = 0;
5028                 int *tmp;
5030                 for (line = view->line; line < view->line + view->lines; line++) {
5031                         if (line->type != LINE_DIFF_CHUNK)
5032                                 continue;
5034                         tmp = realloc_items(stage_chunk, &alloc,
5035                                             stage_chunks, sizeof(*tmp));
5036                         if (!tmp) {
5037                                 report("Allocation failure");
5038                                 return;
5039                         }
5041                         stage_chunk = tmp;
5042                         stage_chunk[stage_chunks++] = line - view->line;
5043                 }
5044         }
5046         for (i = 0; i < stage_chunks; i++) {
5047                 if (stage_chunk[i] > view->lineno) {
5048                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5049                         report("Chunk %d of %d", i + 1, stage_chunks);
5050                         return;
5051                 }
5052         }
5054         report("No next chunk found");
5057 static enum request
5058 stage_request(struct view *view, enum request request, struct line *line)
5060         switch (request) {
5061         case REQ_STATUS_UPDATE:
5062                 if (!stage_update(view, line))
5063                         return REQ_NONE;
5064                 break;
5066         case REQ_STATUS_REVERT:
5067                 if (!stage_revert(view, line))
5068                         return REQ_NONE;
5069                 break;
5071         case REQ_STAGE_NEXT:
5072                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5073                         report("File is untracked; press %s to add",
5074                                get_key(REQ_STATUS_UPDATE));
5075                         return REQ_NONE;
5076                 }
5077                 stage_next(view, line);
5078                 return REQ_NONE;
5080         case REQ_EDIT:
5081                 if (!stage_status.new.name[0])
5082                         return request;
5083                 if (stage_status.status == 'D') {
5084                         report("File has been deleted.");
5085                         return REQ_NONE;
5086                 }
5088                 open_editor(stage_status.status != '?', stage_status.new.name);
5089                 break;
5091         case REQ_REFRESH:
5092                 /* Reload everything ... */
5093                 break;
5095         case REQ_VIEW_BLAME:
5096                 if (stage_status.new.name[0]) {
5097                         string_copy(opt_file, stage_status.new.name);
5098                         opt_ref[0] = 0;
5099                 }
5100                 return request;
5102         case REQ_ENTER:
5103                 return pager_request(view, request, line);
5105         default:
5106                 return request;
5107         }
5109         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5111         /* Check whether the staged entry still exists, and close the
5112          * stage view if it doesn't. */
5113         if (!status_exists(&stage_status, stage_line_type))
5114                 return REQ_VIEW_CLOSE;
5116         if (stage_line_type == LINE_STAT_UNTRACKED) {
5117                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5118                         report("Cannot display a directory");
5119                         return REQ_NONE;
5120                 }
5122                 if (!prepare_update_file(view, stage_status.new.name)) {
5123                         report("Failed to open file: %s", strerror(errno));
5124                         return REQ_NONE;
5125                 }
5126         }
5127         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5129         return REQ_NONE;
5132 static struct view_ops stage_ops = {
5133         "line",
5134         NULL,
5135         NULL,
5136         pager_read,
5137         pager_draw,
5138         stage_request,
5139         pager_grep,
5140         pager_select,
5141 };
5144 /*
5145  * Revision graph
5146  */
5148 struct commit {
5149         char id[SIZEOF_REV];            /* SHA1 ID. */
5150         char title[128];                /* First line of the commit message. */
5151         char author[75];                /* Author of the commit. */
5152         struct tm time;                 /* Date from the author ident. */
5153         struct ref **refs;              /* Repository references. */
5154         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5155         size_t graph_size;              /* The width of the graph array. */
5156         bool has_parents;               /* Rewritten --parents seen. */
5157 };
5159 /* Size of rev graph with no  "padding" columns */
5160 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5162 struct rev_graph {
5163         struct rev_graph *prev, *next, *parents;
5164         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5165         size_t size;
5166         struct commit *commit;
5167         size_t pos;
5168         unsigned int boundary:1;
5169 };
5171 /* Parents of the commit being visualized. */
5172 static struct rev_graph graph_parents[4];
5174 /* The current stack of revisions on the graph. */
5175 static struct rev_graph graph_stacks[4] = {
5176         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5177         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5178         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5179         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5180 };
5182 static inline bool
5183 graph_parent_is_merge(struct rev_graph *graph)
5185         return graph->parents->size > 1;
5188 static inline void
5189 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5191         struct commit *commit = graph->commit;
5193         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5194                 commit->graph[commit->graph_size++] = symbol;
5197 static void
5198 clear_rev_graph(struct rev_graph *graph)
5200         graph->boundary = 0;
5201         graph->size = graph->pos = 0;
5202         graph->commit = NULL;
5203         memset(graph->parents, 0, sizeof(*graph->parents));
5206 static void
5207 done_rev_graph(struct rev_graph *graph)
5209         if (graph_parent_is_merge(graph) &&
5210             graph->pos < graph->size - 1 &&
5211             graph->next->size == graph->size + graph->parents->size - 1) {
5212                 size_t i = graph->pos + graph->parents->size - 1;
5214                 graph->commit->graph_size = i * 2;
5215                 while (i < graph->next->size - 1) {
5216                         append_to_rev_graph(graph, ' ');
5217                         append_to_rev_graph(graph, '\\');
5218                         i++;
5219                 }
5220         }
5222         clear_rev_graph(graph);
5225 static void
5226 push_rev_graph(struct rev_graph *graph, const char *parent)
5228         int i;
5230         /* "Collapse" duplicate parents lines.
5231          *
5232          * FIXME: This needs to also update update the drawn graph but
5233          * for now it just serves as a method for pruning graph lines. */
5234         for (i = 0; i < graph->size; i++)
5235                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5236                         return;
5238         if (graph->size < SIZEOF_REVITEMS) {
5239                 string_copy_rev(graph->rev[graph->size++], parent);
5240         }
5243 static chtype
5244 get_rev_graph_symbol(struct rev_graph *graph)
5246         chtype symbol;
5248         if (graph->boundary)
5249                 symbol = REVGRAPH_BOUND;
5250         else if (graph->parents->size == 0)
5251                 symbol = REVGRAPH_INIT;
5252         else if (graph_parent_is_merge(graph))
5253                 symbol = REVGRAPH_MERGE;
5254         else if (graph->pos >= graph->size)
5255                 symbol = REVGRAPH_BRANCH;
5256         else
5257                 symbol = REVGRAPH_COMMIT;
5259         return symbol;
5262 static void
5263 draw_rev_graph(struct rev_graph *graph)
5265         struct rev_filler {
5266                 chtype separator, line;
5267         };
5268         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5269         static struct rev_filler fillers[] = {
5270                 { ' ',  '|' },
5271                 { '`',  '.' },
5272                 { '\'', ' ' },
5273                 { '/',  ' ' },
5274         };
5275         chtype symbol = get_rev_graph_symbol(graph);
5276         struct rev_filler *filler;
5277         size_t i;
5279         if (opt_line_graphics)
5280                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5282         filler = &fillers[DEFAULT];
5284         for (i = 0; i < graph->pos; i++) {
5285                 append_to_rev_graph(graph, filler->line);
5286                 if (graph_parent_is_merge(graph->prev) &&
5287                     graph->prev->pos == i)
5288                         filler = &fillers[RSHARP];
5290                 append_to_rev_graph(graph, filler->separator);
5291         }
5293         /* Place the symbol for this revision. */
5294         append_to_rev_graph(graph, symbol);
5296         if (graph->prev->size > graph->size)
5297                 filler = &fillers[RDIAG];
5298         else
5299                 filler = &fillers[DEFAULT];
5301         i++;
5303         for (; i < graph->size; i++) {
5304                 append_to_rev_graph(graph, filler->separator);
5305                 append_to_rev_graph(graph, filler->line);
5306                 if (graph_parent_is_merge(graph->prev) &&
5307                     i < graph->prev->pos + graph->parents->size)
5308                         filler = &fillers[RSHARP];
5309                 if (graph->prev->size > graph->size)
5310                         filler = &fillers[LDIAG];
5311         }
5313         if (graph->prev->size > graph->size) {
5314                 append_to_rev_graph(graph, filler->separator);
5315                 if (filler->line != ' ')
5316                         append_to_rev_graph(graph, filler->line);
5317         }
5320 /* Prepare the next rev graph */
5321 static void
5322 prepare_rev_graph(struct rev_graph *graph)
5324         size_t i;
5326         /* First, traverse all lines of revisions up to the active one. */
5327         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5328                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5329                         break;
5331                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5332         }
5334         /* Interleave the new revision parent(s). */
5335         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5336                 push_rev_graph(graph->next, graph->parents->rev[i]);
5338         /* Lastly, put any remaining revisions. */
5339         for (i = graph->pos + 1; i < graph->size; i++)
5340                 push_rev_graph(graph->next, graph->rev[i]);
5343 static void
5344 update_rev_graph(struct view *view, struct rev_graph *graph)
5346         /* If this is the finalizing update ... */
5347         if (graph->commit)
5348                 prepare_rev_graph(graph);
5350         /* Graph visualization needs a one rev look-ahead,
5351          * so the first update doesn't visualize anything. */
5352         if (!graph->prev->commit)
5353                 return;
5355         if (view->lines > 2)
5356                 view->line[view->lines - 3].dirty = 1;
5357         if (view->lines > 1)
5358                 view->line[view->lines - 2].dirty = 1;
5359         draw_rev_graph(graph->prev);
5360         done_rev_graph(graph->prev->prev);
5364 /*
5365  * Main view backend
5366  */
5368 static const char *main_argv[SIZEOF_ARG] = {
5369         "git", "log", "--no-color", "--pretty=raw", "--parents",
5370                       "--topo-order", "%(head)", NULL
5371 };
5373 static bool
5374 main_draw(struct view *view, struct line *line, unsigned int lineno)
5376         struct commit *commit = line->data;
5378         if (!*commit->author)
5379                 return FALSE;
5381         if (opt_date && draw_date(view, &commit->time))
5382                 return TRUE;
5384         if (opt_author &&
5385             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5386                 return TRUE;
5388         if (opt_rev_graph && commit->graph_size &&
5389             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5390                 return TRUE;
5392         if (opt_show_refs && commit->refs) {
5393                 size_t i = 0;
5395                 do {
5396                         enum line_type type;
5398                         if (commit->refs[i]->head)
5399                                 type = LINE_MAIN_HEAD;
5400                         else if (commit->refs[i]->ltag)
5401                                 type = LINE_MAIN_LOCAL_TAG;
5402                         else if (commit->refs[i]->tag)
5403                                 type = LINE_MAIN_TAG;
5404                         else if (commit->refs[i]->tracked)
5405                                 type = LINE_MAIN_TRACKED;
5406                         else if (commit->refs[i]->remote)
5407                                 type = LINE_MAIN_REMOTE;
5408                         else
5409                                 type = LINE_MAIN_REF;
5411                         if (draw_text(view, type, "[", TRUE) ||
5412                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5413                             draw_text(view, type, "]", TRUE))
5414                                 return TRUE;
5416                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5417                                 return TRUE;
5418                 } while (commit->refs[i++]->next);
5419         }
5421         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5422         return TRUE;
5425 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5426 static bool
5427 main_read(struct view *view, char *line)
5429         static struct rev_graph *graph = graph_stacks;
5430         enum line_type type;
5431         struct commit *commit;
5433         if (!line) {
5434                 int i;
5436                 if (!view->lines && !view->parent)
5437                         die("No revisions match the given arguments.");
5438                 if (view->lines > 0) {
5439                         commit = view->line[view->lines - 1].data;
5440                         view->line[view->lines - 1].dirty = 1;
5441                         if (!*commit->author) {
5442                                 view->lines--;
5443                                 free(commit);
5444                                 graph->commit = NULL;
5445                         }
5446                 }
5447                 update_rev_graph(view, graph);
5449                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5450                         clear_rev_graph(&graph_stacks[i]);
5451                 return TRUE;
5452         }
5454         type = get_line_type(line);
5455         if (type == LINE_COMMIT) {
5456                 commit = calloc(1, sizeof(struct commit));
5457                 if (!commit)
5458                         return FALSE;
5460                 line += STRING_SIZE("commit ");
5461                 if (*line == '-') {
5462                         graph->boundary = 1;
5463                         line++;
5464                 }
5466                 string_copy_rev(commit->id, line);
5467                 commit->refs = get_refs(commit->id);
5468                 graph->commit = commit;
5469                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5471                 while ((line = strchr(line, ' '))) {
5472                         line++;
5473                         push_rev_graph(graph->parents, line);
5474                         commit->has_parents = TRUE;
5475                 }
5476                 return TRUE;
5477         }
5479         if (!view->lines)
5480                 return TRUE;
5481         commit = view->line[view->lines - 1].data;
5483         switch (type) {
5484         case LINE_PARENT:
5485                 if (commit->has_parents)
5486                         break;
5487                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5488                 break;
5490         case LINE_AUTHOR:
5491         {
5492                 /* Parse author lines where the name may be empty:
5493                  *      author  <email@address.tld> 1138474660 +0100
5494                  */
5495                 char *ident = line + STRING_SIZE("author ");
5496                 char *nameend = strchr(ident, '<');
5497                 char *emailend = strchr(ident, '>');
5499                 if (!nameend || !emailend)
5500                         break;
5502                 update_rev_graph(view, graph);
5503                 graph = graph->next;
5505                 *nameend = *emailend = 0;
5506                 ident = chomp_string(ident);
5507                 if (!*ident) {
5508                         ident = chomp_string(nameend + 1);
5509                         if (!*ident)
5510                                 ident = "Unknown";
5511                 }
5513                 string_ncopy(commit->author, ident, strlen(ident));
5514                 view->line[view->lines - 1].dirty = 1;
5516                 /* Parse epoch and timezone */
5517                 if (emailend[1] == ' ') {
5518                         char *secs = emailend + 2;
5519                         char *zone = strchr(secs, ' ');
5520                         time_t time = (time_t) atol(secs);
5522                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5523                                 long tz;
5525                                 zone++;
5526                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5527                                 tz += ('0' - zone[2]) * 60 * 60;
5528                                 tz += ('0' - zone[3]) * 60;
5529                                 tz += ('0' - zone[4]) * 60;
5531                                 if (zone[0] == '-')
5532                                         tz = -tz;
5534                                 time -= tz;
5535                         }
5537                         gmtime_r(&time, &commit->time);
5538                 }
5539                 break;
5540         }
5541         default:
5542                 /* Fill in the commit title if it has not already been set. */
5543                 if (commit->title[0])
5544                         break;
5546                 /* Require titles to start with a non-space character at the
5547                  * offset used by git log. */
5548                 if (strncmp(line, "    ", 4))
5549                         break;
5550                 line += 4;
5551                 /* Well, if the title starts with a whitespace character,
5552                  * try to be forgiving.  Otherwise we end up with no title. */
5553                 while (isspace(*line))
5554                         line++;
5555                 if (*line == '\0')
5556                         break;
5557                 /* FIXME: More graceful handling of titles; append "..." to
5558                  * shortened titles, etc. */
5560                 string_ncopy(commit->title, line, strlen(line));
5561                 view->line[view->lines - 1].dirty = 1;
5562         }
5564         return TRUE;
5567 static enum request
5568 main_request(struct view *view, enum request request, struct line *line)
5570         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5572         switch (request) {
5573         case REQ_ENTER:
5574                 open_view(view, REQ_VIEW_DIFF, flags);
5575                 break;
5576         case REQ_REFRESH:
5577                 load_refs();
5578                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5579                 break;
5580         default:
5581                 return request;
5582         }
5584         return REQ_NONE;
5587 static bool
5588 grep_refs(struct ref **refs, regex_t *regex)
5590         regmatch_t pmatch;
5591         size_t i = 0;
5593         if (!refs)
5594                 return FALSE;
5595         do {
5596                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5597                         return TRUE;
5598         } while (refs[i++]->next);
5600         return FALSE;
5603 static bool
5604 main_grep(struct view *view, struct line *line)
5606         struct commit *commit = line->data;
5607         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5608         char buf[DATE_COLS + 1];
5609         regmatch_t pmatch;
5611         for (state = S_TITLE; state < S_END; state++) {
5612                 char *text;
5614                 switch (state) {
5615                 case S_TITLE:   text = commit->title;   break;
5616                 case S_AUTHOR:
5617                         if (!opt_author)
5618                                 continue;
5619                         text = commit->author;
5620                         break;
5621                 case S_DATE:
5622                         if (!opt_date)
5623                                 continue;
5624                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5625                                 continue;
5626                         text = buf;
5627                         break;
5628                 case S_REFS:
5629                         if (!opt_show_refs)
5630                                 continue;
5631                         if (grep_refs(commit->refs, view->regex) == TRUE)
5632                                 return TRUE;
5633                         continue;
5634                 default:
5635                         return FALSE;
5636                 }
5638                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5639                         return TRUE;
5640         }
5642         return FALSE;
5645 static void
5646 main_select(struct view *view, struct line *line)
5648         struct commit *commit = line->data;
5650         string_copy_rev(view->ref, commit->id);
5651         string_copy_rev(ref_commit, view->ref);
5654 static struct view_ops main_ops = {
5655         "commit",
5656         main_argv,
5657         NULL,
5658         main_read,
5659         main_draw,
5660         main_request,
5661         main_grep,
5662         main_select,
5663 };
5666 /*
5667  * Unicode / UTF-8 handling
5668  *
5669  * NOTE: Much of the following code for dealing with unicode is derived from
5670  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5671  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5672  */
5674 /* I've (over)annotated a lot of code snippets because I am not entirely
5675  * confident that the approach taken by this small UTF-8 interface is correct.
5676  * --jonas */
5678 static inline int
5679 unicode_width(unsigned long c)
5681         if (c >= 0x1100 &&
5682            (c <= 0x115f                         /* Hangul Jamo */
5683             || c == 0x2329
5684             || c == 0x232a
5685             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5686                                                 /* CJK ... Yi */
5687             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5688             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5689             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5690             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5691             || (c >= 0xffe0  && c <= 0xffe6)
5692             || (c >= 0x20000 && c <= 0x2fffd)
5693             || (c >= 0x30000 && c <= 0x3fffd)))
5694                 return 2;
5696         if (c == '\t')
5697                 return opt_tab_size;
5699         return 1;
5702 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5703  * Illegal bytes are set one. */
5704 static const unsigned char utf8_bytes[256] = {
5705         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,
5706         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,
5707         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,
5708         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,
5709         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,
5710         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,
5711         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,
5712         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,
5713 };
5715 /* Decode UTF-8 multi-byte representation into a unicode character. */
5716 static inline unsigned long
5717 utf8_to_unicode(const char *string, size_t length)
5719         unsigned long unicode;
5721         switch (length) {
5722         case 1:
5723                 unicode  =   string[0];
5724                 break;
5725         case 2:
5726                 unicode  =  (string[0] & 0x1f) << 6;
5727                 unicode +=  (string[1] & 0x3f);
5728                 break;
5729         case 3:
5730                 unicode  =  (string[0] & 0x0f) << 12;
5731                 unicode += ((string[1] & 0x3f) << 6);
5732                 unicode +=  (string[2] & 0x3f);
5733                 break;
5734         case 4:
5735                 unicode  =  (string[0] & 0x0f) << 18;
5736                 unicode += ((string[1] & 0x3f) << 12);
5737                 unicode += ((string[2] & 0x3f) << 6);
5738                 unicode +=  (string[3] & 0x3f);
5739                 break;
5740         case 5:
5741                 unicode  =  (string[0] & 0x0f) << 24;
5742                 unicode += ((string[1] & 0x3f) << 18);
5743                 unicode += ((string[2] & 0x3f) << 12);
5744                 unicode += ((string[3] & 0x3f) << 6);
5745                 unicode +=  (string[4] & 0x3f);
5746                 break;
5747         case 6:
5748                 unicode  =  (string[0] & 0x01) << 30;
5749                 unicode += ((string[1] & 0x3f) << 24);
5750                 unicode += ((string[2] & 0x3f) << 18);
5751                 unicode += ((string[3] & 0x3f) << 12);
5752                 unicode += ((string[4] & 0x3f) << 6);
5753                 unicode +=  (string[5] & 0x3f);
5754                 break;
5755         default:
5756                 die("Invalid unicode length");
5757         }
5759         /* Invalid characters could return the special 0xfffd value but NUL
5760          * should be just as good. */
5761         return unicode > 0xffff ? 0 : unicode;
5764 /* Calculates how much of string can be shown within the given maximum width
5765  * and sets trimmed parameter to non-zero value if all of string could not be
5766  * shown. If the reserve flag is TRUE, it will reserve at least one
5767  * trailing character, which can be useful when drawing a delimiter.
5768  *
5769  * Returns the number of bytes to output from string to satisfy max_width. */
5770 static size_t
5771 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5773         const char *start = string;
5774         const char *end = strchr(string, '\0');
5775         unsigned char last_bytes = 0;
5776         size_t last_ucwidth = 0;
5778         *width = 0;
5779         *trimmed = 0;
5781         while (string < end) {
5782                 int c = *(unsigned char *) string;
5783                 unsigned char bytes = utf8_bytes[c];
5784                 size_t ucwidth;
5785                 unsigned long unicode;
5787                 if (string + bytes > end)
5788                         break;
5790                 /* Change representation to figure out whether
5791                  * it is a single- or double-width character. */
5793                 unicode = utf8_to_unicode(string, bytes);
5794                 /* FIXME: Graceful handling of invalid unicode character. */
5795                 if (!unicode)
5796                         break;
5798                 ucwidth = unicode_width(unicode);
5799                 *width  += ucwidth;
5800                 if (*width > max_width) {
5801                         *trimmed = 1;
5802                         *width -= ucwidth;
5803                         if (reserve && *width == max_width) {
5804                                 string -= last_bytes;
5805                                 *width -= last_ucwidth;
5806                         }
5807                         break;
5808                 }
5810                 string  += bytes;
5811                 last_bytes = bytes;
5812                 last_ucwidth = ucwidth;
5813         }
5815         return string - start;
5819 /*
5820  * Status management
5821  */
5823 /* Whether or not the curses interface has been initialized. */
5824 static bool cursed = FALSE;
5826 /* The status window is used for polling keystrokes. */
5827 static WINDOW *status_win;
5829 static bool status_empty = TRUE;
5831 /* Update status and title window. */
5832 static void
5833 report(const char *msg, ...)
5835         struct view *view = display[current_view];
5837         if (input_mode)
5838                 return;
5840         if (!view) {
5841                 char buf[SIZEOF_STR];
5842                 va_list args;
5844                 va_start(args, msg);
5845                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5846                         buf[sizeof(buf) - 1] = 0;
5847                         buf[sizeof(buf) - 2] = '.';
5848                         buf[sizeof(buf) - 3] = '.';
5849                         buf[sizeof(buf) - 4] = '.';
5850                 }
5851                 va_end(args);
5852                 die("%s", buf);
5853         }
5855         if (!status_empty || *msg) {
5856                 va_list args;
5858                 va_start(args, msg);
5860                 wmove(status_win, 0, 0);
5861                 if (*msg) {
5862                         vwprintw(status_win, msg, args);
5863                         status_empty = FALSE;
5864                 } else {
5865                         status_empty = TRUE;
5866                 }
5867                 wclrtoeol(status_win);
5868                 wrefresh(status_win);
5870                 va_end(args);
5871         }
5873         update_view_title(view);
5874         update_display_cursor(view);
5877 /* Controls when nodelay should be in effect when polling user input. */
5878 static void
5879 set_nonblocking_input(bool loading)
5881         static unsigned int loading_views;
5883         if ((loading == FALSE && loading_views-- == 1) ||
5884             (loading == TRUE  && loading_views++ == 0))
5885                 nodelay(status_win, loading);
5888 static void
5889 init_display(void)
5891         int x, y;
5893         /* Initialize the curses library */
5894         if (isatty(STDIN_FILENO)) {
5895                 cursed = !!initscr();
5896                 opt_tty = stdin;
5897         } else {
5898                 /* Leave stdin and stdout alone when acting as a pager. */
5899                 opt_tty = fopen("/dev/tty", "r+");
5900                 if (!opt_tty)
5901                         die("Failed to open /dev/tty");
5902                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5903         }
5905         if (!cursed)
5906                 die("Failed to initialize curses");
5908         nonl();         /* Tell curses not to do NL->CR/NL on output */
5909         cbreak();       /* Take input chars one at a time, no wait for \n */
5910         noecho();       /* Don't echo input */
5911         leaveok(stdscr, TRUE);
5913         if (has_colors())
5914                 init_colors();
5916         getmaxyx(stdscr, y, x);
5917         status_win = newwin(1, 0, y - 1, 0);
5918         if (!status_win)
5919                 die("Failed to create status window");
5921         /* Enable keyboard mapping */
5922         keypad(status_win, TRUE);
5923         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5925         TABSIZE = opt_tab_size;
5926         if (opt_line_graphics) {
5927                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5928         }
5931 static bool
5932 prompt_yesno(const char *prompt)
5934         enum { WAIT, STOP, CANCEL  } status = WAIT;
5935         bool answer = FALSE;
5937         while (status == WAIT) {
5938                 struct view *view;
5939                 int i, key;
5941                 input_mode = TRUE;
5943                 foreach_view (view, i)
5944                         update_view(view);
5946                 input_mode = FALSE;
5948                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5949                 wclrtoeol(status_win);
5951                 /* Refresh, accept single keystroke of input */
5952                 key = wgetch(status_win);
5953                 switch (key) {
5954                 case ERR:
5955                         break;
5957                 case 'y':
5958                 case 'Y':
5959                         answer = TRUE;
5960                         status = STOP;
5961                         break;
5963                 case KEY_ESC:
5964                 case KEY_RETURN:
5965                 case KEY_ENTER:
5966                 case KEY_BACKSPACE:
5967                 case 'n':
5968                 case 'N':
5969                 case '\n':
5970                 default:
5971                         answer = FALSE;
5972                         status = CANCEL;
5973                 }
5974         }
5976         /* Clear the status window */
5977         status_empty = FALSE;
5978         report("");
5980         return answer;
5983 static char *
5984 read_prompt(const char *prompt)
5986         enum { READING, STOP, CANCEL } status = READING;
5987         static char buf[SIZEOF_STR];
5988         int pos = 0;
5990         while (status == READING) {
5991                 struct view *view;
5992                 int i, key;
5994                 input_mode = TRUE;
5996                 foreach_view (view, i)
5997                         update_view(view);
5999                 input_mode = FALSE;
6001                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6002                 wclrtoeol(status_win);
6004                 /* Refresh, accept single keystroke of input */
6005                 key = wgetch(status_win);
6006                 switch (key) {
6007                 case KEY_RETURN:
6008                 case KEY_ENTER:
6009                 case '\n':
6010                         status = pos ? STOP : CANCEL;
6011                         break;
6013                 case KEY_BACKSPACE:
6014                         if (pos > 0)
6015                                 pos--;
6016                         else
6017                                 status = CANCEL;
6018                         break;
6020                 case KEY_ESC:
6021                         status = CANCEL;
6022                         break;
6024                 case ERR:
6025                         break;
6027                 default:
6028                         if (pos >= sizeof(buf)) {
6029                                 report("Input string too long");
6030                                 return NULL;
6031                         }
6033                         if (isprint(key))
6034                                 buf[pos++] = (char) key;
6035                 }
6036         }
6038         /* Clear the status window */
6039         status_empty = FALSE;
6040         report("");
6042         if (status == CANCEL)
6043                 return NULL;
6045         buf[pos++] = 0;
6047         return buf;
6050 /*
6051  * Repository properties
6052  */
6054 static int
6055 git_properties(const char **argv, const char *separators,
6056                int (*read_property)(char *, size_t, char *, size_t))
6058         struct io io = {};
6060         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6061                 return read_properties(&io, separators, read_property);
6062         return ERR;
6065 static struct ref *refs = NULL;
6066 static size_t refs_alloc = 0;
6067 static size_t refs_size = 0;
6069 /* Id <-> ref store */
6070 static struct ref ***id_refs = NULL;
6071 static size_t id_refs_alloc = 0;
6072 static size_t id_refs_size = 0;
6074 static int
6075 compare_refs(const void *ref1_, const void *ref2_)
6077         const struct ref *ref1 = *(const struct ref **)ref1_;
6078         const struct ref *ref2 = *(const struct ref **)ref2_;
6080         if (ref1->tag != ref2->tag)
6081                 return ref2->tag - ref1->tag;
6082         if (ref1->ltag != ref2->ltag)
6083                 return ref2->ltag - ref2->ltag;
6084         if (ref1->head != ref2->head)
6085                 return ref2->head - ref1->head;
6086         if (ref1->tracked != ref2->tracked)
6087                 return ref2->tracked - ref1->tracked;
6088         if (ref1->remote != ref2->remote)
6089                 return ref2->remote - ref1->remote;
6090         return strcmp(ref1->name, ref2->name);
6093 static struct ref **
6094 get_refs(const char *id)
6096         struct ref ***tmp_id_refs;
6097         struct ref **ref_list = NULL;
6098         size_t ref_list_alloc = 0;
6099         size_t ref_list_size = 0;
6100         size_t i;
6102         for (i = 0; i < id_refs_size; i++)
6103                 if (!strcmp(id, id_refs[i][0]->id))
6104                         return id_refs[i];
6106         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6107                                     sizeof(*id_refs));
6108         if (!tmp_id_refs)
6109                 return NULL;
6111         id_refs = tmp_id_refs;
6113         for (i = 0; i < refs_size; i++) {
6114                 struct ref **tmp;
6116                 if (strcmp(id, refs[i].id))
6117                         continue;
6119                 tmp = realloc_items(ref_list, &ref_list_alloc,
6120                                     ref_list_size + 1, sizeof(*ref_list));
6121                 if (!tmp) {
6122                         if (ref_list)
6123                                 free(ref_list);
6124                         return NULL;
6125                 }
6127                 ref_list = tmp;
6128                 ref_list[ref_list_size] = &refs[i];
6129                 /* XXX: The properties of the commit chains ensures that we can
6130                  * safely modify the shared ref. The repo references will
6131                  * always be similar for the same id. */
6132                 ref_list[ref_list_size]->next = 1;
6134                 ref_list_size++;
6135         }
6137         if (ref_list) {
6138                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6139                 ref_list[ref_list_size - 1]->next = 0;
6140                 id_refs[id_refs_size++] = ref_list;
6141         }
6143         return ref_list;
6146 static int
6147 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6149         struct ref *ref;
6150         bool tag = FALSE;
6151         bool ltag = FALSE;
6152         bool remote = FALSE;
6153         bool tracked = FALSE;
6154         bool check_replace = FALSE;
6155         bool head = FALSE;
6157         if (!prefixcmp(name, "refs/tags/")) {
6158                 if (!suffixcmp(name, namelen, "^{}")) {
6159                         namelen -= 3;
6160                         name[namelen] = 0;
6161                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6162                                 check_replace = TRUE;
6163                 } else {
6164                         ltag = TRUE;
6165                 }
6167                 tag = TRUE;
6168                 namelen -= STRING_SIZE("refs/tags/");
6169                 name    += STRING_SIZE("refs/tags/");
6171         } else if (!prefixcmp(name, "refs/remotes/")) {
6172                 remote = TRUE;
6173                 namelen -= STRING_SIZE("refs/remotes/");
6174                 name    += STRING_SIZE("refs/remotes/");
6175                 tracked  = !strcmp(opt_remote, name);
6177         } else if (!prefixcmp(name, "refs/heads/")) {
6178                 namelen -= STRING_SIZE("refs/heads/");
6179                 name    += STRING_SIZE("refs/heads/");
6180                 head     = !strncmp(opt_head, name, namelen);
6182         } else if (!strcmp(name, "HEAD")) {
6183                 string_ncopy(opt_head_rev, id, idlen);
6184                 return OK;
6185         }
6187         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6188                 /* it's an annotated tag, replace the previous sha1 with the
6189                  * resolved commit id; relies on the fact git-ls-remote lists
6190                  * the commit id of an annotated tag right before the commit id
6191                  * it points to. */
6192                 refs[refs_size - 1].ltag = ltag;
6193                 string_copy_rev(refs[refs_size - 1].id, id);
6195                 return OK;
6196         }
6197         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6198         if (!refs)
6199                 return ERR;
6201         ref = &refs[refs_size++];
6202         ref->name = malloc(namelen + 1);
6203         if (!ref->name)
6204                 return ERR;
6206         strncpy(ref->name, name, namelen);
6207         ref->name[namelen] = 0;
6208         ref->head = head;
6209         ref->tag = tag;
6210         ref->ltag = ltag;
6211         ref->remote = remote;
6212         ref->tracked = tracked;
6213         string_copy_rev(ref->id, id);
6215         return OK;
6218 static int
6219 load_refs(void)
6221         static const char *ls_remote_argv[SIZEOF_ARG] = {
6222                 "git", "ls-remote", ".", NULL
6223         };
6224         static bool init = FALSE;
6226         if (!init) {
6227                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6228                 init = TRUE;
6229         }
6231         if (!*opt_git_dir)
6232                 return OK;
6234         while (refs_size > 0)
6235                 free(refs[--refs_size].name);
6236         while (id_refs_size > 0)
6237                 free(id_refs[--id_refs_size]);
6239         return git_properties(ls_remote_argv, "\t", read_ref);
6242 static int
6243 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6245         if (!strcmp(name, "i18n.commitencoding"))
6246                 string_ncopy(opt_encoding, value, valuelen);
6248         if (!strcmp(name, "core.editor"))
6249                 string_ncopy(opt_editor, value, valuelen);
6251         /* branch.<head>.remote */
6252         if (*opt_head &&
6253             !strncmp(name, "branch.", 7) &&
6254             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6255             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6256                 string_ncopy(opt_remote, value, valuelen);
6258         if (*opt_head && *opt_remote &&
6259             !strncmp(name, "branch.", 7) &&
6260             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6261             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6262                 size_t from = strlen(opt_remote);
6264                 if (!prefixcmp(value, "refs/heads/")) {
6265                         value += STRING_SIZE("refs/heads/");
6266                         valuelen -= STRING_SIZE("refs/heads/");
6267                 }
6269                 if (!string_format_from(opt_remote, &from, "/%s", value))
6270                         opt_remote[0] = 0;
6271         }
6273         return OK;
6276 static int
6277 load_git_config(void)
6279         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6281         return git_properties(config_list_argv, "=", read_repo_config_option);
6284 static int
6285 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6287         if (!opt_git_dir[0]) {
6288                 string_ncopy(opt_git_dir, name, namelen);
6290         } else if (opt_is_inside_work_tree == -1) {
6291                 /* This can be 3 different values depending on the
6292                  * version of git being used. If git-rev-parse does not
6293                  * understand --is-inside-work-tree it will simply echo
6294                  * the option else either "true" or "false" is printed.
6295                  * Default to true for the unknown case. */
6296                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6297         } else {
6298                 string_ncopy(opt_cdup, name, namelen);
6299         }
6301         return OK;
6304 static int
6305 load_repo_info(void)
6307         const char *head_argv[] = {
6308                 "git", "symbolic-ref", "HEAD", NULL
6309         };
6310         const char *rev_parse_argv[] = {
6311                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6312                         "--show-cdup", NULL
6313         };
6315         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6316                 chomp_string(opt_head);
6317                 if (!prefixcmp(opt_head, "refs/heads/")) {
6318                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6320                         memmove(opt_head, offset, strlen(offset) + 1);
6321                 }
6322         }
6324         return git_properties(rev_parse_argv, "=", read_repo_info);
6327 static int
6328 read_properties(struct io *io, const char *separators,
6329                 int (*read_property)(char *, size_t, char *, size_t))
6331         char *name;
6332         int state = OK;
6334         if (!start_io(io))
6335                 return ERR;
6337         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6338                 char *value;
6339                 size_t namelen;
6340                 size_t valuelen;
6342                 name = chomp_string(name);
6343                 namelen = strcspn(name, separators);
6345                 if (name[namelen]) {
6346                         name[namelen] = 0;
6347                         value = chomp_string(name + namelen + 1);
6348                         valuelen = strlen(value);
6350                 } else {
6351                         value = "";
6352                         valuelen = 0;
6353                 }
6355                 state = read_property(name, namelen, value, valuelen);
6356         }
6358         if (state != ERR && io_error(io))
6359                 state = ERR;
6360         done_io(io);
6362         return state;
6366 /*
6367  * Main
6368  */
6370 static void __NORETURN
6371 quit(int sig)
6373         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6374         if (cursed)
6375                 endwin();
6376         exit(0);
6379 static void __NORETURN
6380 die(const char *err, ...)
6382         va_list args;
6384         endwin();
6386         va_start(args, err);
6387         fputs("tig: ", stderr);
6388         vfprintf(stderr, err, args);
6389         fputs("\n", stderr);
6390         va_end(args);
6392         exit(1);
6395 static void
6396 warn(const char *msg, ...)
6398         va_list args;
6400         va_start(args, msg);
6401         fputs("tig warning: ", stderr);
6402         vfprintf(stderr, msg, args);
6403         fputs("\n", stderr);
6404         va_end(args);
6407 int
6408 main(int argc, const char *argv[])
6410         const char **run_argv = NULL;
6411         struct view *view;
6412         enum request request;
6413         size_t i;
6415         signal(SIGINT, quit);
6417         if (setlocale(LC_ALL, "")) {
6418                 char *codeset = nl_langinfo(CODESET);
6420                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6421         }
6423         if (load_repo_info() == ERR)
6424                 die("Failed to load repo info.");
6426         if (load_options() == ERR)
6427                 die("Failed to load user config.");
6429         if (load_git_config() == ERR)
6430                 die("Failed to load repo config.");
6432         request = parse_options(argc, argv, &run_argv);
6433         if (request == REQ_NONE)
6434                 return 0;
6436         /* Require a git repository unless when running in pager mode. */
6437         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6438                 die("Not a git repository");
6440         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6441                 opt_utf8 = FALSE;
6443         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6444                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6445                 if (opt_iconv == ICONV_NONE)
6446                         die("Failed to initialize character set conversion");
6447         }
6449         if (load_refs() == ERR)
6450                 die("Failed to load refs.");
6452         foreach_view (view, i)
6453                 argv_from_env(view->ops->argv, view->cmd_env);
6455         init_display();
6457         if (request == REQ_VIEW_PAGER || run_argv) {
6458                 if (request == REQ_VIEW_PAGER)
6459                         io_open(&VIEW(request)->io, "");
6460                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6461                         die("Failed to format arguments");
6462                 open_view(NULL, request, OPEN_PREPARED);
6463                 request = REQ_NONE;
6464         }
6466         while (view_driver(display[current_view], request)) {
6467                 int key;
6468                 int i;
6470                 foreach_view (view, i)
6471                         update_view(view);
6472                 view = display[current_view];
6474                 /* Refresh, accept single keystroke of input */
6475                 key = wgetch(status_win);
6477                 /* wgetch() with nodelay() enabled returns ERR when there's no
6478                  * input. */
6479                 if (key == ERR) {
6480                         request = REQ_NONE;
6481                         continue;
6482                 }
6484                 request = get_keybinding(view->keymap, key);
6486                 /* Some low-level request handling. This keeps access to
6487                  * status_win restricted. */
6488                 switch (request) {
6489                 case REQ_PROMPT:
6490                 {
6491                         char *cmd = read_prompt(":");
6493                         if (cmd) {
6494                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6495                                 const char *argv[SIZEOF_ARG] = { "git" };
6496                                 int argc = 1;
6498                                 /* When running random commands, initially show the
6499                                  * command in the title. However, it maybe later be
6500                                  * overwritten if a commit line is selected. */
6501                                 string_ncopy(next->ref, cmd, strlen(cmd));
6503                                 if (!argv_from_string(argv, &argc, cmd)) {
6504                                         report("Too many arguments");
6505                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6506                                         report("Failed to format command");
6507                                 } else {
6508                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6509                                 }
6510                         }
6512                         request = REQ_NONE;
6513                         break;
6514                 }
6515                 case REQ_SEARCH:
6516                 case REQ_SEARCH_BACK:
6517                 {
6518                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6519                         char *search = read_prompt(prompt);
6521                         if (search)
6522                                 string_ncopy(opt_search, search, strlen(search));
6523                         else
6524                                 request = REQ_NONE;
6525                         break;
6526                 }
6527                 case REQ_SCREEN_RESIZE:
6528                 {
6529                         int height, width;
6531                         getmaxyx(stdscr, height, width);
6533                         /* Resize the status view and let the view driver take
6534                          * care of resizing the displayed views. */
6535                         wresize(status_win, 1, width);
6536                         mvwin(status_win, height - 1, 0);
6537                         wrefresh(status_win);
6538                         break;
6539                 }
6540                 default:
6541                         break;
6542                 }
6543         }
6545         quit(0);
6547         return 0;