Code

Refactor user input handling into separate function
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static bool prompt_yesno(const char *prompt);
73 static int load_refs(void);
75 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
76 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
108 #define AUTHOR_COLS     20
109 #define ID_COLS         8
111 /* The default interval between line numbers. */
112 #define NUMBER_INTERVAL 5
114 #define TAB_SIZE        8
116 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char *name;             /* Ref name; tag or head names are shortened. */
132         char id[SIZEOF_REV];    /* Commit SHA1 ID */
133         unsigned int head:1;    /* Is it the current HEAD? */
134         unsigned int tag:1;     /* Is it a tag? */
135         unsigned int ltag:1;    /* If so, is the tag local? */
136         unsigned int remote:1;  /* Is it a remote ref? */
137         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138         unsigned int next:1;    /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152         const char *name;
153         int namelen;
154         int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159                  int *value, const char *name, int namelen)
162         int i;
164         for (i = 0; i < map_size; i++)
165                 if (namelen == map[i].namelen &&
166                     !strncasecmp(name, map[i].name, namelen)) {
167                         *value = map[i].value;
168                         return OK;
169                 }
171         return ERR;
175 /*
176  * String helpers
177  */
179 static inline void
180 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
182         if (srclen > dstlen - 1)
183                 srclen = dstlen - 1;
185         strncpy(dst, src, srclen);
186         dst[srclen] = 0;
189 /* Shorthands for safely copying into a fixed buffer. */
191 #define string_copy(dst, src) \
192         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
194 #define string_ncopy(dst, src, srclen) \
195         string_ncopy_do(dst, sizeof(dst), src, srclen)
197 #define string_copy_rev(dst, src) \
198         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
200 #define string_add(dst, from, src) \
201         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 static char *
204 chomp_string(char *name)
206         int namelen;
208         while (isspace(*name))
209                 name++;
211         namelen = strlen(name) - 1;
212         while (namelen > 0 && isspace(name[namelen]))
213                 name[namelen--] = 0;
215         return name;
218 static bool
219 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
221         va_list args;
222         size_t pos = bufpos ? *bufpos : 0;
224         va_start(args, fmt);
225         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
226         va_end(args);
228         if (bufpos)
229                 *bufpos = pos;
231         return pos >= bufsize ? FALSE : TRUE;
234 #define string_format(buf, fmt, args...) \
235         string_nformat(buf, sizeof(buf), NULL, fmt, args)
237 #define string_format_from(buf, from, fmt, args...) \
238         string_nformat(buf, sizeof(buf), from, fmt, args)
240 static int
241 string_enum_compare(const char *str1, const char *str2, int len)
243         size_t i;
245 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
247         /* Diff-Header == DIFF_HEADER */
248         for (i = 0; i < len; i++) {
249                 if (toupper(str1[i]) == toupper(str2[i]))
250                         continue;
252                 if (string_enum_sep(str1[i]) &&
253                     string_enum_sep(str2[i]))
254                         continue;
256                 return str1[i] - str2[i];
257         }
259         return 0;
262 #define prefixcmp(str1, str2) \
263         strncmp(str1, str2, STRING_SIZE(str2))
265 static inline int
266 suffixcmp(const char *str, int slen, const char *suffix)
268         size_t len = slen >= 0 ? slen : strlen(str);
269         size_t suffixlen = strlen(suffix);
271         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
275 static bool
276 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
278         int valuelen;
280         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
281                 bool advance = cmd[valuelen] != 0;
283                 cmd[valuelen] = 0;
284                 argv[(*argc)++] = chomp_string(cmd);
285                 cmd += valuelen + advance;
286         }
288         if (*argc < SIZEOF_ARG)
289                 argv[*argc] = NULL;
290         return *argc < SIZEOF_ARG;
293 static void
294 argv_from_env(const char **argv, const char *name)
296         char *env = argv ? getenv(name) : NULL;
297         int argc = 0;
299         if (env && *env)
300                 env = strdup(env);
301         if (env && !argv_from_string(argv, &argc, env))
302                 die("Too many arguments in the `%s` environment variable", name);
306 /*
307  * Executing external commands.
308  */
310 enum io_type {
311         IO_FD,                  /* File descriptor based IO. */
312         IO_BG,                  /* Execute command in the background. */
313         IO_FG,                  /* Execute command with same std{in,out,err}. */
314         IO_RD,                  /* Read only fork+exec IO. */
315         IO_WR,                  /* Write only fork+exec IO. */
316         IO_AP,                  /* Append fork+exec output to file. */
317 };
319 struct io {
320         enum io_type type;      /* The requested type of pipe. */
321         const char *dir;        /* Directory from which to execute. */
322         pid_t pid;              /* Pipe for reading or writing. */
323         int pipe;               /* Pipe end for reading or writing. */
324         int error;              /* Error status. */
325         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
326         char *buf;              /* Read buffer. */
327         size_t bufalloc;        /* Allocated buffer size. */
328         size_t bufsize;         /* Buffer content size. */
329         char *bufpos;           /* Current buffer position. */
330         unsigned int eof:1;     /* Has end of file been reached. */
331 };
333 static void
334 reset_io(struct io *io)
336         io->pipe = -1;
337         io->pid = 0;
338         io->buf = io->bufpos = NULL;
339         io->bufalloc = io->bufsize = 0;
340         io->error = 0;
341         io->eof = 0;
344 static void
345 init_io(struct io *io, const char *dir, enum io_type type)
347         reset_io(io);
348         io->type = type;
349         io->dir = dir;
352 static bool
353 init_io_rd(struct io *io, const char *argv[], const char *dir,
354                 enum format_flags flags)
356         init_io(io, dir, IO_RD);
357         return format_argv(io->argv, argv, flags);
360 static bool
361 io_open(struct io *io, const char *name)
363         init_io(io, NULL, IO_FD);
364         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
365         return io->pipe != -1;
368 static bool
369 kill_io(struct io *io)
371         return kill(io->pid, SIGKILL) != -1;
374 static bool
375 done_io(struct io *io)
377         pid_t pid = io->pid;
379         if (io->pipe != -1)
380                 close(io->pipe);
381         free(io->buf);
382         reset_io(io);
384         while (pid > 0) {
385                 int status;
386                 pid_t waiting = waitpid(pid, &status, 0);
388                 if (waiting < 0) {
389                         if (errno == EINTR)
390                                 continue;
391                         report("waitpid failed (%s)", strerror(errno));
392                         return FALSE;
393                 }
395                 return waiting == pid &&
396                        !WIFSIGNALED(status) &&
397                        WIFEXITED(status) &&
398                        !WEXITSTATUS(status);
399         }
401         return TRUE;
404 static bool
405 start_io(struct io *io)
407         int pipefds[2] = { -1, -1 };
409         if (io->type == IO_FD)
410                 return TRUE;
412         if ((io->type == IO_RD || io->type == IO_WR) &&
413             pipe(pipefds) < 0)
414                 return FALSE;
415         else if (io->type == IO_AP)
416                 pipefds[1] = io->pipe;
418         if ((io->pid = fork())) {
419                 if (pipefds[!(io->type == IO_WR)] != -1)
420                         close(pipefds[!(io->type == IO_WR)]);
421                 if (io->pid != -1) {
422                         io->pipe = pipefds[!!(io->type == IO_WR)];
423                         return TRUE;
424                 }
426         } else {
427                 if (io->type != IO_FG) {
428                         int devnull = open("/dev/null", O_RDWR);
429                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
430                         int writefd = (io->type == IO_RD || io->type == IO_AP)
431                                                         ? pipefds[1] : devnull;
433                         dup2(readfd,  STDIN_FILENO);
434                         dup2(writefd, STDOUT_FILENO);
435                         dup2(devnull, STDERR_FILENO);
437                         close(devnull);
438                         if (pipefds[0] != -1)
439                                 close(pipefds[0]);
440                         if (pipefds[1] != -1)
441                                 close(pipefds[1]);
442                 }
444                 if (io->dir && *io->dir && chdir(io->dir) == -1)
445                         die("Failed to change directory: %s", strerror(errno));
447                 execvp(io->argv[0], (char *const*) io->argv);
448                 die("Failed to execute program: %s", strerror(errno));
449         }
451         if (pipefds[!!(io->type == IO_WR)] != -1)
452                 close(pipefds[!!(io->type == IO_WR)]);
453         return FALSE;
456 static bool
457 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
459         init_io(io, dir, type);
460         if (!format_argv(io->argv, argv, FORMAT_NONE))
461                 return FALSE;
462         return start_io(io);
465 static int
466 run_io_do(struct io *io)
468         return start_io(io) && done_io(io);
471 static int
472 run_io_bg(const char **argv)
474         struct io io = {};
476         init_io(&io, NULL, IO_BG);
477         if (!format_argv(io.argv, argv, FORMAT_NONE))
478                 return FALSE;
479         return run_io_do(&io);
482 static bool
483 run_io_fg(const char **argv, const char *dir)
485         struct io io = {};
487         init_io(&io, dir, IO_FG);
488         if (!format_argv(io.argv, argv, FORMAT_NONE))
489                 return FALSE;
490         return run_io_do(&io);
493 static bool
494 run_io_append(const char **argv, enum format_flags flags, int fd)
496         struct io io = {};
498         init_io(&io, NULL, IO_AP);
499         io.pipe = fd;
500         if (format_argv(io.argv, argv, flags))
501                 return run_io_do(&io);
502         close(fd);
503         return FALSE;
506 static bool
507 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
509         return init_io_rd(io, argv, NULL, flags) && start_io(io);
512 static bool
513 io_eof(struct io *io)
515         return io->eof;
518 static int
519 io_error(struct io *io)
521         return io->error;
524 static bool
525 io_strerror(struct io *io)
527         return strerror(io->error);
530 static bool
531 io_can_read(struct io *io)
533         struct timeval tv = { 0, 500 };
534         fd_set fds;
536         FD_ZERO(&fds);
537         FD_SET(io->pipe, &fds);
539         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
542 static ssize_t
543 io_read(struct io *io, void *buf, size_t bufsize)
545         do {
546                 ssize_t readsize = read(io->pipe, buf, bufsize);
548                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
549                         continue;
550                 else if (readsize == -1)
551                         io->error = errno;
552                 else if (readsize == 0)
553                         io->eof = 1;
554                 return readsize;
555         } while (1);
558 static char *
559 io_get(struct io *io, int c, bool can_read)
561         char *eol;
562         ssize_t readsize;
564         if (!io->buf) {
565                 io->buf = io->bufpos = malloc(BUFSIZ);
566                 if (!io->buf)
567                         return NULL;
568                 io->bufalloc = BUFSIZ;
569                 io->bufsize = 0;
570         }
572         while (TRUE) {
573                 if (io->bufsize > 0) {
574                         eol = memchr(io->bufpos, c, io->bufsize);
575                         if (eol) {
576                                 char *line = io->bufpos;
578                                 *eol = 0;
579                                 io->bufpos = eol + 1;
580                                 io->bufsize -= io->bufpos - line;
581                                 return line;
582                         }
583                 }
585                 if (io_eof(io)) {
586                         if (io->bufsize) {
587                                 io->bufpos[io->bufsize] = 0;
588                                 io->bufsize = 0;
589                                 return io->bufpos;
590                         }
591                         return NULL;
592                 }
594                 if (!can_read)
595                         return NULL;
597                 if (io->bufsize > 0 && io->bufpos > io->buf)
598                         memmove(io->buf, io->bufpos, io->bufsize);
600                 io->bufpos = io->buf;
601                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
602                 if (io_error(io))
603                         return NULL;
604                 io->bufsize += readsize;
605         }
608 static bool
609 io_write(struct io *io, const void *buf, size_t bufsize)
611         size_t written = 0;
613         while (!io_error(io) && written < bufsize) {
614                 ssize_t size;
616                 size = write(io->pipe, buf + written, bufsize - written);
617                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
618                         continue;
619                 else if (size == -1)
620                         io->error = errno;
621                 else
622                         written += size;
623         }
625         return written == bufsize;
628 static bool
629 run_io_buf(const char **argv, char buf[], size_t bufsize)
631         struct io io = {};
632         bool error;
634         if (!run_io_rd(&io, argv, FORMAT_NONE))
635                 return FALSE;
637         io.buf = io.bufpos = buf;
638         io.bufalloc = bufsize;
639         error = !io_get(&io, '\n', TRUE) && io_error(&io);
640         io.buf = NULL;
642         return done_io(&io) || error;
645 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
647 /*
648  * User requests
649  */
651 #define REQ_INFO \
652         /* XXX: Keep the view request first and in sync with views[]. */ \
653         REQ_GROUP("View switching") \
654         REQ_(VIEW_MAIN,         "Show main view"), \
655         REQ_(VIEW_DIFF,         "Show diff view"), \
656         REQ_(VIEW_LOG,          "Show log view"), \
657         REQ_(VIEW_TREE,         "Show tree view"), \
658         REQ_(VIEW_BLOB,         "Show blob view"), \
659         REQ_(VIEW_BLAME,        "Show blame view"), \
660         REQ_(VIEW_HELP,         "Show help page"), \
661         REQ_(VIEW_PAGER,        "Show pager view"), \
662         REQ_(VIEW_STATUS,       "Show status view"), \
663         REQ_(VIEW_STAGE,        "Show stage view"), \
664         \
665         REQ_GROUP("View manipulation") \
666         REQ_(ENTER,             "Enter current line and scroll"), \
667         REQ_(NEXT,              "Move to next"), \
668         REQ_(PREVIOUS,          "Move to previous"), \
669         REQ_(VIEW_NEXT,         "Move focus to next view"), \
670         REQ_(REFRESH,           "Reload and refresh"), \
671         REQ_(MAXIMIZE,          "Maximize the current view"), \
672         REQ_(VIEW_CLOSE,        "Close the current view"), \
673         REQ_(QUIT,              "Close all views and quit"), \
674         \
675         REQ_GROUP("View specific requests") \
676         REQ_(STATUS_UPDATE,     "Update file status"), \
677         REQ_(STATUS_REVERT,     "Revert file changes"), \
678         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
679         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
680         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
681         \
682         REQ_GROUP("Cursor navigation") \
683         REQ_(MOVE_UP,           "Move cursor one line up"), \
684         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
685         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
686         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
687         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
688         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
689         \
690         REQ_GROUP("Scrolling") \
691         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
692         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
693         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
694         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
695         \
696         REQ_GROUP("Searching") \
697         REQ_(SEARCH,            "Search the view"), \
698         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
699         REQ_(FIND_NEXT,         "Find next search match"), \
700         REQ_(FIND_PREV,         "Find previous search match"), \
701         \
702         REQ_GROUP("Option manipulation") \
703         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
704         REQ_(TOGGLE_DATE,       "Toggle date display"), \
705         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
706         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
707         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
708         \
709         REQ_GROUP("Misc") \
710         REQ_(PROMPT,            "Bring up the prompt"), \
711         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
712         REQ_(SHOW_VERSION,      "Show version information"), \
713         REQ_(STOP_LOADING,      "Stop all loading views"), \
714         REQ_(EDIT,              "Open in editor"), \
715         REQ_(NONE,              "Do nothing")
718 /* User action requests. */
719 enum request {
720 #define REQ_GROUP(help)
721 #define REQ_(req, help) REQ_##req
723         /* Offset all requests to avoid conflicts with ncurses getch values. */
724         REQ_OFFSET = KEY_MAX + 1,
725         REQ_INFO
727 #undef  REQ_GROUP
728 #undef  REQ_
729 };
731 struct request_info {
732         enum request request;
733         const char *name;
734         int namelen;
735         const char *help;
736 };
738 static struct request_info req_info[] = {
739 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
740 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
741         REQ_INFO
742 #undef  REQ_GROUP
743 #undef  REQ_
744 };
746 static enum request
747 get_request(const char *name)
749         int namelen = strlen(name);
750         int i;
752         for (i = 0; i < ARRAY_SIZE(req_info); i++)
753                 if (req_info[i].namelen == namelen &&
754                     !string_enum_compare(req_info[i].name, name, namelen))
755                         return req_info[i].request;
757         return REQ_NONE;
761 /*
762  * Options
763  */
765 static const char usage[] =
766 "tig " TIG_VERSION " (" __DATE__ ")\n"
767 "\n"
768 "Usage: tig        [options] [revs] [--] [paths]\n"
769 "   or: tig show   [options] [revs] [--] [paths]\n"
770 "   or: tig blame  [rev] path\n"
771 "   or: tig status\n"
772 "   or: tig <      [git command output]\n"
773 "\n"
774 "Options:\n"
775 "  -v, --version   Show version and exit\n"
776 "  -h, --help      Show help message and exit";
778 /* Option and state variables. */
779 static bool opt_date                    = TRUE;
780 static bool opt_author                  = TRUE;
781 static bool opt_line_number             = FALSE;
782 static bool opt_line_graphics           = TRUE;
783 static bool opt_rev_graph               = FALSE;
784 static bool opt_show_refs               = TRUE;
785 static int opt_num_interval             = NUMBER_INTERVAL;
786 static int opt_tab_size                 = TAB_SIZE;
787 static int opt_author_cols              = AUTHOR_COLS-1;
788 static char opt_path[SIZEOF_STR]        = "";
789 static char opt_file[SIZEOF_STR]        = "";
790 static char opt_ref[SIZEOF_REF]         = "";
791 static char opt_head[SIZEOF_REF]        = "";
792 static char opt_head_rev[SIZEOF_REV]    = "";
793 static char opt_remote[SIZEOF_REF]      = "";
794 static char opt_encoding[20]            = "UTF-8";
795 static bool opt_utf8                    = TRUE;
796 static char opt_codeset[20]             = "UTF-8";
797 static iconv_t opt_iconv                = ICONV_NONE;
798 static char opt_search[SIZEOF_STR]      = "";
799 static char opt_cdup[SIZEOF_STR]        = "";
800 static char opt_git_dir[SIZEOF_STR]     = "";
801 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
802 static char opt_editor[SIZEOF_STR]      = "";
803 static FILE *opt_tty                    = NULL;
805 #define is_initial_commit()     (!*opt_head_rev)
806 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
808 static enum request
809 parse_options(int argc, const char *argv[], const char ***run_argv)
811         enum request request = REQ_VIEW_MAIN;
812         const char *subcommand;
813         bool seen_dashdash = FALSE;
814         /* XXX: This is vulnerable to the user overriding options
815          * required for the main view parser. */
816         static const char *custom_argv[SIZEOF_ARG] = {
817                 "git", "log", "--no-color", "--pretty=raw", "--parents",
818                         "--topo-order", NULL
819         };
820         int i, j = 6;
822         if (!isatty(STDIN_FILENO))
823                 return REQ_VIEW_PAGER;
825         if (argc <= 1)
826                 return REQ_VIEW_MAIN;
828         subcommand = argv[1];
829         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
830                 if (!strcmp(subcommand, "-S"))
831                         warn("`-S' has been deprecated; use `tig status' instead");
832                 if (argc > 2)
833                         warn("ignoring arguments after `%s'", subcommand);
834                 return REQ_VIEW_STATUS;
836         } else if (!strcmp(subcommand, "blame")) {
837                 if (argc <= 2 || argc > 4)
838                         die("invalid number of options to blame\n\n%s", usage);
840                 i = 2;
841                 if (argc == 4) {
842                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
843                         i++;
844                 }
846                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
847                 return REQ_VIEW_BLAME;
849         } else if (!strcmp(subcommand, "show")) {
850                 request = REQ_VIEW_DIFF;
852         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
853                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
854                 warn("`tig %s' has been deprecated", subcommand);
856         } else {
857                 subcommand = NULL;
858         }
860         if (subcommand) {
861                 custom_argv[1] = subcommand;
862                 j = 2;
863         }
865         for (i = 1 + !!subcommand; i < argc; i++) {
866                 const char *opt = argv[i];
868                 if (seen_dashdash || !strcmp(opt, "--")) {
869                         seen_dashdash = TRUE;
871                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
872                         printf("tig version %s\n", TIG_VERSION);
873                         return REQ_NONE;
875                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
876                         printf("%s\n", usage);
877                         return REQ_NONE;
878                 }
880                 custom_argv[j++] = opt;
881                 if (j >= ARRAY_SIZE(custom_argv))
882                         die("command too long");
883         }
885         custom_argv[j] = NULL;
886         *run_argv = custom_argv;
888         return request;
892 /*
893  * Line-oriented content detection.
894  */
896 #define LINE_INFO \
897 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
898 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
899 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
900 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
901 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
902 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
903 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
905 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
906 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
907 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
908 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
909 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
910 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
911 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
912 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
913 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
914 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
915 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
916 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
917 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
918 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
919 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
920 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
921 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
922 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
923 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
924 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
926 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
927 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
928 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
929 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
930 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
931 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
932 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
933 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
934 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
935 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
936 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
937 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
938 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
939 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
940 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
941 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
942 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
943 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
944 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
945 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
946 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
947 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
949 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
950 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
952 enum line_type {
953 #define LINE(type, line, fg, bg, attr) \
954         LINE_##type
955         LINE_INFO,
956         LINE_NONE
957 #undef  LINE
958 };
960 struct line_info {
961         const char *name;       /* Option name. */
962         int namelen;            /* Size of option name. */
963         const char *line;       /* The start of line to match. */
964         int linelen;            /* Size of string to match. */
965         int fg, bg, attr;       /* Color and text attributes for the lines. */
966 };
968 static struct line_info line_info[] = {
969 #define LINE(type, line, fg, bg, attr) \
970         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
971         LINE_INFO
972 #undef  LINE
973 };
975 static enum line_type
976 get_line_type(const char *line)
978         int linelen = strlen(line);
979         enum line_type type;
981         for (type = 0; type < ARRAY_SIZE(line_info); type++)
982                 /* Case insensitive search matches Signed-off-by lines better. */
983                 if (linelen >= line_info[type].linelen &&
984                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
985                         return type;
987         return LINE_DEFAULT;
990 static inline int
991 get_line_attr(enum line_type type)
993         assert(type < ARRAY_SIZE(line_info));
994         return COLOR_PAIR(type) | line_info[type].attr;
997 static struct line_info *
998 get_line_info(const char *name)
1000         size_t namelen = strlen(name);
1001         enum line_type type;
1003         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1004                 if (namelen == line_info[type].namelen &&
1005                     !string_enum_compare(line_info[type].name, name, namelen))
1006                         return &line_info[type];
1008         return NULL;
1011 static void
1012 init_colors(void)
1014         int default_bg = line_info[LINE_DEFAULT].bg;
1015         int default_fg = line_info[LINE_DEFAULT].fg;
1016         enum line_type type;
1018         start_color();
1020         if (assume_default_colors(default_fg, default_bg) == ERR) {
1021                 default_bg = COLOR_BLACK;
1022                 default_fg = COLOR_WHITE;
1023         }
1025         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1026                 struct line_info *info = &line_info[type];
1027                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1028                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1030                 init_pair(type, fg, bg);
1031         }
1034 struct line {
1035         enum line_type type;
1037         /* State flags */
1038         unsigned int selected:1;
1039         unsigned int dirty:1;
1040         unsigned int cleareol:1;
1042         void *data;             /* User data */
1043 };
1046 /*
1047  * Keys
1048  */
1050 struct keybinding {
1051         int alias;
1052         enum request request;
1053 };
1055 static struct keybinding default_keybindings[] = {
1056         /* View switching */
1057         { 'm',          REQ_VIEW_MAIN },
1058         { 'd',          REQ_VIEW_DIFF },
1059         { 'l',          REQ_VIEW_LOG },
1060         { 't',          REQ_VIEW_TREE },
1061         { 'f',          REQ_VIEW_BLOB },
1062         { 'B',          REQ_VIEW_BLAME },
1063         { 'p',          REQ_VIEW_PAGER },
1064         { 'h',          REQ_VIEW_HELP },
1065         { 'S',          REQ_VIEW_STATUS },
1066         { 'c',          REQ_VIEW_STAGE },
1068         /* View manipulation */
1069         { 'q',          REQ_VIEW_CLOSE },
1070         { KEY_TAB,      REQ_VIEW_NEXT },
1071         { KEY_RETURN,   REQ_ENTER },
1072         { KEY_UP,       REQ_PREVIOUS },
1073         { KEY_DOWN,     REQ_NEXT },
1074         { 'R',          REQ_REFRESH },
1075         { KEY_F(5),     REQ_REFRESH },
1076         { 'O',          REQ_MAXIMIZE },
1078         /* Cursor navigation */
1079         { 'k',          REQ_MOVE_UP },
1080         { 'j',          REQ_MOVE_DOWN },
1081         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1082         { KEY_END,      REQ_MOVE_LAST_LINE },
1083         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1084         { ' ',          REQ_MOVE_PAGE_DOWN },
1085         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1086         { 'b',          REQ_MOVE_PAGE_UP },
1087         { '-',          REQ_MOVE_PAGE_UP },
1089         /* Scrolling */
1090         { KEY_IC,       REQ_SCROLL_LINE_UP },
1091         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1092         { 'w',          REQ_SCROLL_PAGE_UP },
1093         { 's',          REQ_SCROLL_PAGE_DOWN },
1095         /* Searching */
1096         { '/',          REQ_SEARCH },
1097         { '?',          REQ_SEARCH_BACK },
1098         { 'n',          REQ_FIND_NEXT },
1099         { 'N',          REQ_FIND_PREV },
1101         /* Misc */
1102         { 'Q',          REQ_QUIT },
1103         { 'z',          REQ_STOP_LOADING },
1104         { 'v',          REQ_SHOW_VERSION },
1105         { 'r',          REQ_SCREEN_REDRAW },
1106         { '.',          REQ_TOGGLE_LINENO },
1107         { 'D',          REQ_TOGGLE_DATE },
1108         { 'A',          REQ_TOGGLE_AUTHOR },
1109         { 'g',          REQ_TOGGLE_REV_GRAPH },
1110         { 'F',          REQ_TOGGLE_REFS },
1111         { ':',          REQ_PROMPT },
1112         { 'u',          REQ_STATUS_UPDATE },
1113         { '!',          REQ_STATUS_REVERT },
1114         { 'M',          REQ_STATUS_MERGE },
1115         { '@',          REQ_STAGE_NEXT },
1116         { ',',          REQ_TREE_PARENT },
1117         { 'e',          REQ_EDIT },
1118 };
1120 #define KEYMAP_INFO \
1121         KEYMAP_(GENERIC), \
1122         KEYMAP_(MAIN), \
1123         KEYMAP_(DIFF), \
1124         KEYMAP_(LOG), \
1125         KEYMAP_(TREE), \
1126         KEYMAP_(BLOB), \
1127         KEYMAP_(BLAME), \
1128         KEYMAP_(PAGER), \
1129         KEYMAP_(HELP), \
1130         KEYMAP_(STATUS), \
1131         KEYMAP_(STAGE)
1133 enum keymap {
1134 #define KEYMAP_(name) KEYMAP_##name
1135         KEYMAP_INFO
1136 #undef  KEYMAP_
1137 };
1139 static struct int_map keymap_table[] = {
1140 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1141         KEYMAP_INFO
1142 #undef  KEYMAP_
1143 };
1145 #define set_keymap(map, name) \
1146         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1148 struct keybinding_table {
1149         struct keybinding *data;
1150         size_t size;
1151 };
1153 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1155 static void
1156 add_keybinding(enum keymap keymap, enum request request, int key)
1158         struct keybinding_table *table = &keybindings[keymap];
1160         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1161         if (!table->data)
1162                 die("Failed to allocate keybinding");
1163         table->data[table->size].alias = key;
1164         table->data[table->size++].request = request;
1167 /* Looks for a key binding first in the given map, then in the generic map, and
1168  * lastly in the default keybindings. */
1169 static enum request
1170 get_keybinding(enum keymap keymap, int key)
1172         size_t i;
1174         for (i = 0; i < keybindings[keymap].size; i++)
1175                 if (keybindings[keymap].data[i].alias == key)
1176                         return keybindings[keymap].data[i].request;
1178         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1179                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1180                         return keybindings[KEYMAP_GENERIC].data[i].request;
1182         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1183                 if (default_keybindings[i].alias == key)
1184                         return default_keybindings[i].request;
1186         return (enum request) key;
1190 struct key {
1191         const char *name;
1192         int value;
1193 };
1195 static struct key key_table[] = {
1196         { "Enter",      KEY_RETURN },
1197         { "Space",      ' ' },
1198         { "Backspace",  KEY_BACKSPACE },
1199         { "Tab",        KEY_TAB },
1200         { "Escape",     KEY_ESC },
1201         { "Left",       KEY_LEFT },
1202         { "Right",      KEY_RIGHT },
1203         { "Up",         KEY_UP },
1204         { "Down",       KEY_DOWN },
1205         { "Insert",     KEY_IC },
1206         { "Delete",     KEY_DC },
1207         { "Hash",       '#' },
1208         { "Home",       KEY_HOME },
1209         { "End",        KEY_END },
1210         { "PageUp",     KEY_PPAGE },
1211         { "PageDown",   KEY_NPAGE },
1212         { "F1",         KEY_F(1) },
1213         { "F2",         KEY_F(2) },
1214         { "F3",         KEY_F(3) },
1215         { "F4",         KEY_F(4) },
1216         { "F5",         KEY_F(5) },
1217         { "F6",         KEY_F(6) },
1218         { "F7",         KEY_F(7) },
1219         { "F8",         KEY_F(8) },
1220         { "F9",         KEY_F(9) },
1221         { "F10",        KEY_F(10) },
1222         { "F11",        KEY_F(11) },
1223         { "F12",        KEY_F(12) },
1224 };
1226 static int
1227 get_key_value(const char *name)
1229         int i;
1231         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1232                 if (!strcasecmp(key_table[i].name, name))
1233                         return key_table[i].value;
1235         if (strlen(name) == 1 && isprint(*name))
1236                 return (int) *name;
1238         return ERR;
1241 static const char *
1242 get_key_name(int key_value)
1244         static char key_char[] = "'X'";
1245         const char *seq = NULL;
1246         int key;
1248         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1249                 if (key_table[key].value == key_value)
1250                         seq = key_table[key].name;
1252         if (seq == NULL &&
1253             key_value < 127 &&
1254             isprint(key_value)) {
1255                 key_char[1] = (char) key_value;
1256                 seq = key_char;
1257         }
1259         return seq ? seq : "(no key)";
1262 static const char *
1263 get_key(enum request request)
1265         static char buf[BUFSIZ];
1266         size_t pos = 0;
1267         char *sep = "";
1268         int i;
1270         buf[pos] = 0;
1272         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1273                 struct keybinding *keybinding = &default_keybindings[i];
1275                 if (keybinding->request != request)
1276                         continue;
1278                 if (!string_format_from(buf, &pos, "%s%s", sep,
1279                                         get_key_name(keybinding->alias)))
1280                         return "Too many keybindings!";
1281                 sep = ", ";
1282         }
1284         return buf;
1287 struct run_request {
1288         enum keymap keymap;
1289         int key;
1290         const char *argv[SIZEOF_ARG];
1291 };
1293 static struct run_request *run_request;
1294 static size_t run_requests;
1296 static enum request
1297 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1299         struct run_request *req;
1301         if (argc >= ARRAY_SIZE(req->argv) - 1)
1302                 return REQ_NONE;
1304         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1305         if (!req)
1306                 return REQ_NONE;
1308         run_request = req;
1309         req = &run_request[run_requests];
1310         req->keymap = keymap;
1311         req->key = key;
1312         req->argv[0] = NULL;
1314         if (!format_argv(req->argv, argv, FORMAT_NONE))
1315                 return REQ_NONE;
1317         return REQ_NONE + ++run_requests;
1320 static struct run_request *
1321 get_run_request(enum request request)
1323         if (request <= REQ_NONE)
1324                 return NULL;
1325         return &run_request[request - REQ_NONE - 1];
1328 static void
1329 add_builtin_run_requests(void)
1331         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1332         const char *gc[] = { "git", "gc", NULL };
1333         struct {
1334                 enum keymap keymap;
1335                 int key;
1336                 int argc;
1337                 const char **argv;
1338         } reqs[] = {
1339                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1340                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1341         };
1342         int i;
1344         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1345                 enum request req;
1347                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1348                 if (req != REQ_NONE)
1349                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1350         }
1353 /*
1354  * User config file handling.
1355  */
1357 static struct int_map color_map[] = {
1358 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1359         COLOR_MAP(DEFAULT),
1360         COLOR_MAP(BLACK),
1361         COLOR_MAP(BLUE),
1362         COLOR_MAP(CYAN),
1363         COLOR_MAP(GREEN),
1364         COLOR_MAP(MAGENTA),
1365         COLOR_MAP(RED),
1366         COLOR_MAP(WHITE),
1367         COLOR_MAP(YELLOW),
1368 };
1370 #define set_color(color, name) \
1371         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1373 static struct int_map attr_map[] = {
1374 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1375         ATTR_MAP(NORMAL),
1376         ATTR_MAP(BLINK),
1377         ATTR_MAP(BOLD),
1378         ATTR_MAP(DIM),
1379         ATTR_MAP(REVERSE),
1380         ATTR_MAP(STANDOUT),
1381         ATTR_MAP(UNDERLINE),
1382 };
1384 #define set_attribute(attr, name) \
1385         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1387 static int   config_lineno;
1388 static bool  config_errors;
1389 static const char *config_msg;
1391 /* Wants: object fgcolor bgcolor [attr] */
1392 static int
1393 option_color_command(int argc, const char *argv[])
1395         struct line_info *info;
1397         if (argc != 3 && argc != 4) {
1398                 config_msg = "Wrong number of arguments given to color command";
1399                 return ERR;
1400         }
1402         info = get_line_info(argv[0]);
1403         if (!info) {
1404                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1405                         info = get_line_info("delimiter");
1407                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1408                         info = get_line_info("date");
1410                 } else {
1411                         config_msg = "Unknown color name";
1412                         return ERR;
1413                 }
1414         }
1416         if (set_color(&info->fg, argv[1]) == ERR ||
1417             set_color(&info->bg, argv[2]) == ERR) {
1418                 config_msg = "Unknown color";
1419                 return ERR;
1420         }
1422         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1423                 config_msg = "Unknown attribute";
1424                 return ERR;
1425         }
1427         return OK;
1430 static bool parse_bool(const char *s)
1432         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1433                 !strcmp(s, "yes")) ? TRUE : FALSE;
1436 static int
1437 parse_int(const char *s, int default_value, int min, int max)
1439         int value = atoi(s);
1441         return (value < min || value > max) ? default_value : value;
1444 /* Wants: name = value */
1445 static int
1446 option_set_command(int argc, const char *argv[])
1448         if (argc != 3) {
1449                 config_msg = "Wrong number of arguments given to set command";
1450                 return ERR;
1451         }
1453         if (strcmp(argv[1], "=")) {
1454                 config_msg = "No value assigned";
1455                 return ERR;
1456         }
1458         if (!strcmp(argv[0], "show-author")) {
1459                 opt_author = parse_bool(argv[2]);
1460                 return OK;
1461         }
1463         if (!strcmp(argv[0], "show-date")) {
1464                 opt_date = parse_bool(argv[2]);
1465                 return OK;
1466         }
1468         if (!strcmp(argv[0], "show-rev-graph")) {
1469                 opt_rev_graph = parse_bool(argv[2]);
1470                 return OK;
1471         }
1473         if (!strcmp(argv[0], "show-refs")) {
1474                 opt_show_refs = parse_bool(argv[2]);
1475                 return OK;
1476         }
1478         if (!strcmp(argv[0], "show-line-numbers")) {
1479                 opt_line_number = parse_bool(argv[2]);
1480                 return OK;
1481         }
1483         if (!strcmp(argv[0], "line-graphics")) {
1484                 opt_line_graphics = parse_bool(argv[2]);
1485                 return OK;
1486         }
1488         if (!strcmp(argv[0], "line-number-interval")) {
1489                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1490                 return OK;
1491         }
1493         if (!strcmp(argv[0], "author-width")) {
1494                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1495                 return OK;
1496         }
1498         if (!strcmp(argv[0], "tab-size")) {
1499                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1500                 return OK;
1501         }
1503         if (!strcmp(argv[0], "commit-encoding")) {
1504                 const char *arg = argv[2];
1505                 int arglen = strlen(arg);
1507                 switch (arg[0]) {
1508                 case '"':
1509                 case '\'':
1510                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1511                                 config_msg = "Unmatched quotation";
1512                                 return ERR;
1513                         }
1514                         arg += 1; arglen -= 2;
1515                 default:
1516                         string_ncopy(opt_encoding, arg, strlen(arg));
1517                         return OK;
1518                 }
1519         }
1521         config_msg = "Unknown variable name";
1522         return ERR;
1525 /* Wants: mode request key */
1526 static int
1527 option_bind_command(int argc, const char *argv[])
1529         enum request request;
1530         int keymap;
1531         int key;
1533         if (argc < 3) {
1534                 config_msg = "Wrong number of arguments given to bind command";
1535                 return ERR;
1536         }
1538         if (set_keymap(&keymap, argv[0]) == ERR) {
1539                 config_msg = "Unknown key map";
1540                 return ERR;
1541         }
1543         key = get_key_value(argv[1]);
1544         if (key == ERR) {
1545                 config_msg = "Unknown key";
1546                 return ERR;
1547         }
1549         request = get_request(argv[2]);
1550         if (request == REQ_NONE) {
1551                 const char *obsolete[] = { "cherry-pick", "screen-resize" };
1552                 size_t namelen = strlen(argv[2]);
1553                 int i;
1555                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1556                         if (namelen == strlen(obsolete[i]) &&
1557                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1558                                 config_msg = "Obsolete request name";
1559                                 return ERR;
1560                         }
1561                 }
1562         }
1563         if (request == REQ_NONE && *argv[2]++ == '!')
1564                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1565         if (request == REQ_NONE) {
1566                 config_msg = "Unknown request name";
1567                 return ERR;
1568         }
1570         add_keybinding(keymap, request, key);
1572         return OK;
1575 static int
1576 set_option(const char *opt, char *value)
1578         const char *argv[SIZEOF_ARG];
1579         int argc = 0;
1581         if (!argv_from_string(argv, &argc, value)) {
1582                 config_msg = "Too many option arguments";
1583                 return ERR;
1584         }
1586         if (!strcmp(opt, "color"))
1587                 return option_color_command(argc, argv);
1589         if (!strcmp(opt, "set"))
1590                 return option_set_command(argc, argv);
1592         if (!strcmp(opt, "bind"))
1593                 return option_bind_command(argc, argv);
1595         config_msg = "Unknown option command";
1596         return ERR;
1599 static int
1600 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1602         int status = OK;
1604         config_lineno++;
1605         config_msg = "Internal error";
1607         /* Check for comment markers, since read_properties() will
1608          * only ensure opt and value are split at first " \t". */
1609         optlen = strcspn(opt, "#");
1610         if (optlen == 0)
1611                 return OK;
1613         if (opt[optlen] != 0) {
1614                 config_msg = "No option value";
1615                 status = ERR;
1617         }  else {
1618                 /* Look for comment endings in the value. */
1619                 size_t len = strcspn(value, "#");
1621                 if (len < valuelen) {
1622                         valuelen = len;
1623                         value[valuelen] = 0;
1624                 }
1626                 status = set_option(opt, value);
1627         }
1629         if (status == ERR) {
1630                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1631                         config_lineno, (int) optlen, opt, config_msg);
1632                 config_errors = TRUE;
1633         }
1635         /* Always keep going if errors are encountered. */
1636         return OK;
1639 static void
1640 load_option_file(const char *path)
1642         struct io io = {};
1644         /* It's ok that the file doesn't exist. */
1645         if (!io_open(&io, path))
1646                 return;
1648         config_lineno = 0;
1649         config_errors = FALSE;
1651         if (read_properties(&io, " \t", read_option) == ERR ||
1652             config_errors == TRUE)
1653                 fprintf(stderr, "Errors while loading %s.\n", path);
1656 static int
1657 load_options(void)
1659         const char *home = getenv("HOME");
1660         const char *tigrc_user = getenv("TIGRC_USER");
1661         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1662         char buf[SIZEOF_STR];
1664         add_builtin_run_requests();
1666         if (!tigrc_system) {
1667                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1668                         return ERR;
1669                 tigrc_system = buf;
1670         }
1671         load_option_file(tigrc_system);
1673         if (!tigrc_user) {
1674                 if (!home || !string_format(buf, "%s/.tigrc", home))
1675                         return ERR;
1676                 tigrc_user = buf;
1677         }
1678         load_option_file(tigrc_user);
1680         return OK;
1684 /*
1685  * The viewer
1686  */
1688 struct view;
1689 struct view_ops;
1691 /* The display array of active views and the index of the current view. */
1692 static struct view *display[2];
1693 static unsigned int current_view;
1695 /* Reading from the prompt? */
1696 static bool input_mode = FALSE;
1698 #define foreach_displayed_view(view, i) \
1699         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1701 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1703 /* Current head and commit ID */
1704 static char ref_blob[SIZEOF_REF]        = "";
1705 static char ref_commit[SIZEOF_REF]      = "HEAD";
1706 static char ref_head[SIZEOF_REF]        = "HEAD";
1708 struct view {
1709         const char *name;       /* View name */
1710         const char *cmd_env;    /* Command line set via environment */
1711         const char *id;         /* Points to either of ref_{head,commit,blob} */
1713         struct view_ops *ops;   /* View operations */
1715         enum keymap keymap;     /* What keymap does this view have */
1716         bool git_dir;           /* Whether the view requires a git directory. */
1718         char ref[SIZEOF_REF];   /* Hovered commit reference */
1719         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1721         int height, width;      /* The width and height of the main window */
1722         WINDOW *win;            /* The main window */
1723         WINDOW *title;          /* The title window living below the main window */
1725         /* Navigation */
1726         unsigned long offset;   /* Offset of the window top */
1727         unsigned long lineno;   /* Current line number */
1729         /* Searching */
1730         char grep[SIZEOF_STR];  /* Search string */
1731         regex_t *regex;         /* Pre-compiled regex */
1733         /* If non-NULL, points to the view that opened this view. If this view
1734          * is closed tig will switch back to the parent view. */
1735         struct view *parent;
1737         /* Buffering */
1738         size_t lines;           /* Total number of lines */
1739         struct line *line;      /* Line index */
1740         size_t line_alloc;      /* Total number of allocated lines */
1741         unsigned int digits;    /* Number of digits in the lines member. */
1743         /* Drawing */
1744         struct line *curline;   /* Line currently being drawn. */
1745         enum line_type curtype; /* Attribute currently used for drawing. */
1746         unsigned long col;      /* Column when drawing. */
1748         /* Loading */
1749         struct io io;
1750         struct io *pipe;
1751         time_t start_time;
1752         time_t update_secs;
1753 };
1755 struct view_ops {
1756         /* What type of content being displayed. Used in the title bar. */
1757         const char *type;
1758         /* Default command arguments. */
1759         const char **argv;
1760         /* Open and reads in all view content. */
1761         bool (*open)(struct view *view);
1762         /* Read one line; updates view->line. */
1763         bool (*read)(struct view *view, char *data);
1764         /* Draw one line; @lineno must be < view->height. */
1765         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1766         /* Depending on view handle a special requests. */
1767         enum request (*request)(struct view *view, enum request request, struct line *line);
1768         /* Search for regex in a line. */
1769         bool (*grep)(struct view *view, struct line *line);
1770         /* Select line */
1771         void (*select)(struct view *view, struct line *line);
1772 };
1774 static struct view_ops blame_ops;
1775 static struct view_ops blob_ops;
1776 static struct view_ops diff_ops;
1777 static struct view_ops help_ops;
1778 static struct view_ops log_ops;
1779 static struct view_ops main_ops;
1780 static struct view_ops pager_ops;
1781 static struct view_ops stage_ops;
1782 static struct view_ops status_ops;
1783 static struct view_ops tree_ops;
1785 #define VIEW_STR(name, env, ref, ops, map, git) \
1786         { name, #env, ref, ops, map, git }
1788 #define VIEW_(id, name, ops, git, ref) \
1789         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1792 static struct view views[] = {
1793         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1794         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1795         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1796         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1797         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1798         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1799         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1800         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1801         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1802         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1803 };
1805 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1806 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1808 #define foreach_view(view, i) \
1809         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1811 #define view_is_displayed(view) \
1812         (view == display[0] || view == display[1])
1815 enum line_graphic {
1816         LINE_GRAPHIC_VLINE
1817 };
1819 static int line_graphics[] = {
1820         /* LINE_GRAPHIC_VLINE: */ '|'
1821 };
1823 static inline void
1824 set_view_attr(struct view *view, enum line_type type)
1826         if (!view->curline->selected && view->curtype != type) {
1827                 wattrset(view->win, get_line_attr(type));
1828                 wchgat(view->win, -1, 0, type, NULL);
1829                 view->curtype = type;
1830         }
1833 static int
1834 draw_chars(struct view *view, enum line_type type, const char *string,
1835            int max_len, bool use_tilde)
1837         int len = 0;
1838         int col = 0;
1839         int trimmed = FALSE;
1841         if (max_len <= 0)
1842                 return 0;
1844         if (opt_utf8) {
1845                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1846         } else {
1847                 col = len = strlen(string);
1848                 if (len > max_len) {
1849                         if (use_tilde) {
1850                                 max_len -= 1;
1851                         }
1852                         col = len = max_len;
1853                         trimmed = TRUE;
1854                 }
1855         }
1857         set_view_attr(view, type);
1858         waddnstr(view->win, string, len);
1859         if (trimmed && use_tilde) {
1860                 set_view_attr(view, LINE_DELIMITER);
1861                 waddch(view->win, '~');
1862                 col++;
1863         }
1865         return col;
1868 static int
1869 draw_space(struct view *view, enum line_type type, int max, int spaces)
1871         static char space[] = "                    ";
1872         int col = 0;
1874         spaces = MIN(max, spaces);
1876         while (spaces > 0) {
1877                 int len = MIN(spaces, sizeof(space) - 1);
1879                 col += draw_chars(view, type, space, spaces, FALSE);
1880                 spaces -= len;
1881         }
1883         return col;
1886 static bool
1887 draw_lineno(struct view *view, unsigned int lineno)
1889         char number[10];
1890         int digits3 = view->digits < 3 ? 3 : view->digits;
1891         int max_number = MIN(digits3, STRING_SIZE(number));
1892         int max = view->width - view->col;
1893         int col;
1895         if (max < max_number)
1896                 max_number = max;
1898         lineno += view->offset + 1;
1899         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1900                 static char fmt[] = "%1ld";
1902                 if (view->digits <= 9)
1903                         fmt[1] = '0' + digits3;
1905                 if (!string_format(number, fmt, lineno))
1906                         number[0] = 0;
1907                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1908         } else {
1909                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1910         }
1912         if (col < max) {
1913                 set_view_attr(view, LINE_DEFAULT);
1914                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1915                 col++;
1916         }
1918         if (col < max)
1919                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1920         view->col += col;
1922         return view->width - view->col <= 0;
1925 static bool
1926 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1928         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1929         return view->width - view->col <= 0;
1932 static bool
1933 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1935         int max = view->width - view->col;
1936         int i;
1938         if (max < size)
1939                 size = max;
1941         set_view_attr(view, type);
1942         /* Using waddch() instead of waddnstr() ensures that
1943          * they'll be rendered correctly for the cursor line. */
1944         for (i = 0; i < size; i++)
1945                 waddch(view->win, graphic[i]);
1947         view->col += size;
1948         if (size < max) {
1949                 waddch(view->win, ' ');
1950                 view->col++;
1951         }
1953         return view->width - view->col <= 0;
1956 static bool
1957 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1959         int max = MIN(view->width - view->col, len);
1960         int col;
1962         if (text)
1963                 col = draw_chars(view, type, text, max - 1, trim);
1964         else
1965                 col = draw_space(view, type, max - 1, max - 1);
1967         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1968         return view->width - view->col <= 0;
1971 static bool
1972 draw_date(struct view *view, struct tm *time)
1974         char buf[DATE_COLS];
1975         char *date;
1976         int timelen = 0;
1978         if (time)
1979                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1980         date = timelen ? buf : NULL;
1982         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1985 static bool
1986 draw_view_line(struct view *view, unsigned int lineno)
1988         struct line *line;
1989         bool selected = (view->offset + lineno == view->lineno);
1990         bool draw_ok;
1992         assert(view_is_displayed(view));
1994         if (view->offset + lineno >= view->lines)
1995                 return FALSE;
1997         line = &view->line[view->offset + lineno];
1999         wmove(view->win, lineno, 0);
2000         if (line->cleareol)
2001                 wclrtoeol(view->win);
2002         view->col = 0;
2003         view->curline = line;
2004         view->curtype = LINE_NONE;
2005         line->selected = FALSE;
2006         line->dirty = line->cleareol = 0;
2008         if (selected) {
2009                 set_view_attr(view, LINE_CURSOR);
2010                 line->selected = TRUE;
2011                 view->ops->select(view, line);
2012         }
2014         scrollok(view->win, FALSE);
2015         draw_ok = view->ops->draw(view, line, lineno);
2016         scrollok(view->win, TRUE);
2018         return draw_ok;
2021 static void
2022 redraw_view_dirty(struct view *view)
2024         bool dirty = FALSE;
2025         int lineno;
2027         for (lineno = 0; lineno < view->height; lineno++) {
2028                 if (view->offset + lineno >= view->lines)
2029                         break;
2030                 if (!view->line[view->offset + lineno].dirty)
2031                         continue;
2032                 dirty = TRUE;
2033                 if (!draw_view_line(view, lineno))
2034                         break;
2035         }
2037         if (!dirty)
2038                 return;
2039         redrawwin(view->win);
2040         if (input_mode)
2041                 wnoutrefresh(view->win);
2042         else
2043                 wrefresh(view->win);
2046 static void
2047 redraw_view_from(struct view *view, int lineno)
2049         assert(0 <= lineno && lineno < view->height);
2051         for (; lineno < view->height; lineno++) {
2052                 if (!draw_view_line(view, lineno))
2053                         break;
2054         }
2056         redrawwin(view->win);
2057         if (input_mode)
2058                 wnoutrefresh(view->win);
2059         else
2060                 wrefresh(view->win);
2063 static void
2064 redraw_view(struct view *view)
2066         werase(view->win);
2067         redraw_view_from(view, 0);
2071 static void
2072 update_view_title(struct view *view)
2074         char buf[SIZEOF_STR];
2075         char state[SIZEOF_STR];
2076         size_t bufpos = 0, statelen = 0;
2078         assert(view_is_displayed(view));
2080         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2081                 unsigned int view_lines = view->offset + view->height;
2082                 unsigned int lines = view->lines
2083                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2084                                    : 0;
2086                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2087                                    view->ops->type,
2088                                    view->lineno + 1,
2089                                    view->lines,
2090                                    lines);
2092         }
2094         if (view->pipe) {
2095                 time_t secs = time(NULL) - view->start_time;
2097                 /* Three git seconds are a long time ... */
2098                 if (secs > 2)
2099                         string_format_from(state, &statelen, " loading %lds", secs);
2100         }
2102         string_format_from(buf, &bufpos, "[%s]", view->name);
2103         if (*view->ref && bufpos < view->width) {
2104                 size_t refsize = strlen(view->ref);
2105                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2107                 if (minsize < view->width)
2108                         refsize = view->width - minsize + 7;
2109                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2110         }
2112         if (statelen && bufpos < view->width) {
2113                 string_format_from(buf, &bufpos, "%s", state);
2114         }
2116         if (view == display[current_view])
2117                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2118         else
2119                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2121         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2122         wclrtoeol(view->title);
2123         wmove(view->title, 0, view->width - 1);
2125         if (input_mode)
2126                 wnoutrefresh(view->title);
2127         else
2128                 wrefresh(view->title);
2131 static void
2132 resize_display(void)
2134         int offset, i;
2135         struct view *base = display[0];
2136         struct view *view = display[1] ? display[1] : display[0];
2138         /* Setup window dimensions */
2140         getmaxyx(stdscr, base->height, base->width);
2142         /* Make room for the status window. */
2143         base->height -= 1;
2145         if (view != base) {
2146                 /* Horizontal split. */
2147                 view->width   = base->width;
2148                 view->height  = SCALE_SPLIT_VIEW(base->height);
2149                 base->height -= view->height;
2151                 /* Make room for the title bar. */
2152                 view->height -= 1;
2153         }
2155         /* Make room for the title bar. */
2156         base->height -= 1;
2158         offset = 0;
2160         foreach_displayed_view (view, i) {
2161                 if (!view->win) {
2162                         view->win = newwin(view->height, 0, offset, 0);
2163                         if (!view->win)
2164                                 die("Failed to create %s view", view->name);
2166                         scrollok(view->win, TRUE);
2168                         view->title = newwin(1, 0, offset + view->height, 0);
2169                         if (!view->title)
2170                                 die("Failed to create title window");
2172                 } else {
2173                         wresize(view->win, view->height, view->width);
2174                         mvwin(view->win,   offset, 0);
2175                         mvwin(view->title, offset + view->height, 0);
2176                 }
2178                 offset += view->height + 1;
2179         }
2182 static void
2183 redraw_display(bool clear)
2185         struct view *view;
2186         int i;
2188         foreach_displayed_view (view, i) {
2189                 if (clear)
2190                         wclear(view->win);
2191                 redraw_view(view);
2192                 update_view_title(view);
2193         }
2196 static void
2197 update_display_cursor(struct view *view)
2199         /* Move the cursor to the right-most column of the cursor line.
2200          *
2201          * XXX: This could turn out to be a bit expensive, but it ensures that
2202          * the cursor does not jump around. */
2203         if (view->lines) {
2204                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2205                 wrefresh(view->win);
2206         }
2209 static void
2210 toggle_view_option(bool *option, const char *help)
2212         *option = !*option;
2213         redraw_display(FALSE);
2214         report("%sabling %s", *option ? "En" : "Dis", help);
2217 /*
2218  * Navigation
2219  */
2221 /* Scrolling backend */
2222 static void
2223 do_scroll_view(struct view *view, int lines)
2225         bool redraw_current_line = FALSE;
2227         /* The rendering expects the new offset. */
2228         view->offset += lines;
2230         assert(0 <= view->offset && view->offset < view->lines);
2231         assert(lines);
2233         /* Move current line into the view. */
2234         if (view->lineno < view->offset) {
2235                 view->lineno = view->offset;
2236                 redraw_current_line = TRUE;
2237         } else if (view->lineno >= view->offset + view->height) {
2238                 view->lineno = view->offset + view->height - 1;
2239                 redraw_current_line = TRUE;
2240         }
2242         assert(view->offset <= view->lineno && view->lineno < view->lines);
2244         /* Redraw the whole screen if scrolling is pointless. */
2245         if (view->height < ABS(lines)) {
2246                 redraw_view(view);
2248         } else {
2249                 int line = lines > 0 ? view->height - lines : 0;
2250                 int end = line + ABS(lines);
2252                 wscrl(view->win, lines);
2254                 for (; line < end; line++) {
2255                         if (!draw_view_line(view, line))
2256                                 break;
2257                 }
2259                 if (redraw_current_line)
2260                         draw_view_line(view, view->lineno - view->offset);
2261         }
2263         redrawwin(view->win);
2264         wrefresh(view->win);
2265         report("");
2268 /* Scroll frontend */
2269 static void
2270 scroll_view(struct view *view, enum request request)
2272         int lines = 1;
2274         assert(view_is_displayed(view));
2276         switch (request) {
2277         case REQ_SCROLL_PAGE_DOWN:
2278                 lines = view->height;
2279         case REQ_SCROLL_LINE_DOWN:
2280                 if (view->offset + lines > view->lines)
2281                         lines = view->lines - view->offset;
2283                 if (lines == 0 || view->offset + view->height >= view->lines) {
2284                         report("Cannot scroll beyond the last line");
2285                         return;
2286                 }
2287                 break;
2289         case REQ_SCROLL_PAGE_UP:
2290                 lines = view->height;
2291         case REQ_SCROLL_LINE_UP:
2292                 if (lines > view->offset)
2293                         lines = view->offset;
2295                 if (lines == 0) {
2296                         report("Cannot scroll beyond the first line");
2297                         return;
2298                 }
2300                 lines = -lines;
2301                 break;
2303         default:
2304                 die("request %d not handled in switch", request);
2305         }
2307         do_scroll_view(view, lines);
2310 /* Cursor moving */
2311 static void
2312 move_view(struct view *view, enum request request)
2314         int scroll_steps = 0;
2315         int steps;
2317         switch (request) {
2318         case REQ_MOVE_FIRST_LINE:
2319                 steps = -view->lineno;
2320                 break;
2322         case REQ_MOVE_LAST_LINE:
2323                 steps = view->lines - view->lineno - 1;
2324                 break;
2326         case REQ_MOVE_PAGE_UP:
2327                 steps = view->height > view->lineno
2328                       ? -view->lineno : -view->height;
2329                 break;
2331         case REQ_MOVE_PAGE_DOWN:
2332                 steps = view->lineno + view->height >= view->lines
2333                       ? view->lines - view->lineno - 1 : view->height;
2334                 break;
2336         case REQ_MOVE_UP:
2337                 steps = -1;
2338                 break;
2340         case REQ_MOVE_DOWN:
2341                 steps = 1;
2342                 break;
2344         default:
2345                 die("request %d not handled in switch", request);
2346         }
2348         if (steps <= 0 && view->lineno == 0) {
2349                 report("Cannot move beyond the first line");
2350                 return;
2352         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2353                 report("Cannot move beyond the last line");
2354                 return;
2355         }
2357         /* Move the current line */
2358         view->lineno += steps;
2359         assert(0 <= view->lineno && view->lineno < view->lines);
2361         /* Check whether the view needs to be scrolled */
2362         if (view->lineno < view->offset ||
2363             view->lineno >= view->offset + view->height) {
2364                 scroll_steps = steps;
2365                 if (steps < 0 && -steps > view->offset) {
2366                         scroll_steps = -view->offset;
2368                 } else if (steps > 0) {
2369                         if (view->lineno == view->lines - 1 &&
2370                             view->lines > view->height) {
2371                                 scroll_steps = view->lines - view->offset - 1;
2372                                 if (scroll_steps >= view->height)
2373                                         scroll_steps -= view->height - 1;
2374                         }
2375                 }
2376         }
2378         if (!view_is_displayed(view)) {
2379                 view->offset += scroll_steps;
2380                 assert(0 <= view->offset && view->offset < view->lines);
2381                 view->ops->select(view, &view->line[view->lineno]);
2382                 return;
2383         }
2385         /* Repaint the old "current" line if we be scrolling */
2386         if (ABS(steps) < view->height)
2387                 draw_view_line(view, view->lineno - steps - view->offset);
2389         if (scroll_steps) {
2390                 do_scroll_view(view, scroll_steps);
2391                 return;
2392         }
2394         /* Draw the current line */
2395         draw_view_line(view, view->lineno - view->offset);
2397         redrawwin(view->win);
2398         wrefresh(view->win);
2399         report("");
2403 /*
2404  * Searching
2405  */
2407 static void search_view(struct view *view, enum request request);
2409 static bool
2410 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2412         assert(view_is_displayed(view));
2414         if (!view->ops->grep(view, line))
2415                 return FALSE;
2417         if (lineno - view->offset >= view->height) {
2418                 view->offset = lineno;
2419                 view->lineno = lineno;
2420                 redraw_view(view);
2422         } else {
2423                 unsigned long old_lineno = view->lineno - view->offset;
2425                 view->lineno = lineno;
2426                 draw_view_line(view, old_lineno);
2428                 draw_view_line(view, view->lineno - view->offset);
2429                 redrawwin(view->win);
2430                 wrefresh(view->win);
2431         }
2433         report("Line %ld matches '%s'", lineno + 1, view->grep);
2434         return TRUE;
2437 static void
2438 find_next(struct view *view, enum request request)
2440         unsigned long lineno = view->lineno;
2441         int direction;
2443         if (!*view->grep) {
2444                 if (!*opt_search)
2445                         report("No previous search");
2446                 else
2447                         search_view(view, request);
2448                 return;
2449         }
2451         switch (request) {
2452         case REQ_SEARCH:
2453         case REQ_FIND_NEXT:
2454                 direction = 1;
2455                 break;
2457         case REQ_SEARCH_BACK:
2458         case REQ_FIND_PREV:
2459                 direction = -1;
2460                 break;
2462         default:
2463                 return;
2464         }
2466         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2467                 lineno += direction;
2469         /* Note, lineno is unsigned long so will wrap around in which case it
2470          * will become bigger than view->lines. */
2471         for (; lineno < view->lines; lineno += direction) {
2472                 struct line *line = &view->line[lineno];
2474                 if (find_next_line(view, lineno, line))
2475                         return;
2476         }
2478         report("No match found for '%s'", view->grep);
2481 static void
2482 search_view(struct view *view, enum request request)
2484         int regex_err;
2486         if (view->regex) {
2487                 regfree(view->regex);
2488                 *view->grep = 0;
2489         } else {
2490                 view->regex = calloc(1, sizeof(*view->regex));
2491                 if (!view->regex)
2492                         return;
2493         }
2495         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2496         if (regex_err != 0) {
2497                 char buf[SIZEOF_STR] = "unknown error";
2499                 regerror(regex_err, view->regex, buf, sizeof(buf));
2500                 report("Search failed: %s", buf);
2501                 return;
2502         }
2504         string_copy(view->grep, opt_search);
2506         find_next(view, request);
2509 /*
2510  * Incremental updating
2511  */
2513 static void
2514 reset_view(struct view *view)
2516         int i;
2518         for (i = 0; i < view->lines; i++)
2519                 free(view->line[i].data);
2520         free(view->line);
2522         view->line = NULL;
2523         view->offset = 0;
2524         view->lines  = 0;
2525         view->lineno = 0;
2526         view->line_alloc = 0;
2527         view->vid[0] = 0;
2528         view->update_secs = 0;
2531 static void
2532 free_argv(const char *argv[])
2534         int argc;
2536         for (argc = 0; argv[argc]; argc++)
2537                 free((void *) argv[argc]);
2540 static bool
2541 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2543         char buf[SIZEOF_STR];
2544         int argc;
2545         bool noreplace = flags == FORMAT_NONE;
2547         free_argv(dst_argv);
2549         for (argc = 0; src_argv[argc]; argc++) {
2550                 const char *arg = src_argv[argc];
2551                 size_t bufpos = 0;
2553                 while (arg) {
2554                         char *next = strstr(arg, "%(");
2555                         int len = next - arg;
2556                         const char *value;
2558                         if (!next || noreplace) {
2559                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2560                                         noreplace = TRUE;
2561                                 len = strlen(arg);
2562                                 value = "";
2564                         } else if (!prefixcmp(next, "%(directory)")) {
2565                                 value = opt_path;
2567                         } else if (!prefixcmp(next, "%(file)")) {
2568                                 value = opt_file;
2570                         } else if (!prefixcmp(next, "%(ref)")) {
2571                                 value = *opt_ref ? opt_ref : "HEAD";
2573                         } else if (!prefixcmp(next, "%(head)")) {
2574                                 value = ref_head;
2576                         } else if (!prefixcmp(next, "%(commit)")) {
2577                                 value = ref_commit;
2579                         } else if (!prefixcmp(next, "%(blob)")) {
2580                                 value = ref_blob;
2582                         } else {
2583                                 report("Unknown replacement: `%s`", next);
2584                                 return FALSE;
2585                         }
2587                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2588                                 return FALSE;
2590                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2591                 }
2593                 dst_argv[argc] = strdup(buf);
2594                 if (!dst_argv[argc])
2595                         break;
2596         }
2598         dst_argv[argc] = NULL;
2600         return src_argv[argc] == NULL;
2603 static void
2604 end_update(struct view *view, bool force)
2606         if (!view->pipe)
2607                 return;
2608         while (!view->ops->read(view, NULL))
2609                 if (!force)
2610                         return;
2611         set_nonblocking_input(FALSE);
2612         if (force)
2613                 kill_io(view->pipe);
2614         done_io(view->pipe);
2615         view->pipe = NULL;
2618 static void
2619 setup_update(struct view *view, const char *vid)
2621         set_nonblocking_input(TRUE);
2622         reset_view(view);
2623         string_copy_rev(view->vid, vid);
2624         view->pipe = &view->io;
2625         view->start_time = time(NULL);
2628 static bool
2629 prepare_update(struct view *view, const char *argv[], const char *dir,
2630                enum format_flags flags)
2632         if (view->pipe)
2633                 end_update(view, TRUE);
2634         return init_io_rd(&view->io, argv, dir, flags);
2637 static bool
2638 prepare_update_file(struct view *view, const char *name)
2640         if (view->pipe)
2641                 end_update(view, TRUE);
2642         return io_open(&view->io, name);
2645 static bool
2646 begin_update(struct view *view, bool refresh)
2648         if (view->pipe)
2649                 end_update(view, TRUE);
2651         if (refresh) {
2652                 if (!start_io(&view->io))
2653                         return FALSE;
2655         } else {
2656                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2657                         opt_path[0] = 0;
2659                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2660                         return FALSE;
2662                 /* Put the current ref_* value to the view title ref
2663                  * member. This is needed by the blob view. Most other
2664                  * views sets it automatically after loading because the
2665                  * first line is a commit line. */
2666                 string_copy_rev(view->ref, view->id);
2667         }
2669         setup_update(view, view->id);
2671         return TRUE;
2674 #define ITEM_CHUNK_SIZE 256
2675 static void *
2676 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2678         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2679         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2681         if (mem == NULL || num_chunks != num_chunks_new) {
2682                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2683                 mem = realloc(mem, *size * item_size);
2684         }
2686         return mem;
2689 static struct line *
2690 realloc_lines(struct view *view, size_t line_size)
2692         size_t alloc = view->line_alloc;
2693         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2694                                          sizeof(*view->line));
2696         if (!tmp)
2697                 return NULL;
2699         view->line = tmp;
2700         view->line_alloc = alloc;
2701         return view->line;
2704 static bool
2705 update_view(struct view *view)
2707         char out_buffer[BUFSIZ * 2];
2708         char *line;
2709         /* Clear the view and redraw everything since the tree sorting
2710          * might have rearranged things. */
2711         bool redraw = view->lines == 0;
2712         bool can_read = TRUE;
2714         if (!view->pipe)
2715                 return TRUE;
2717         if (!io_can_read(view->pipe)) {
2718                 if (view->lines == 0) {
2719                         time_t secs = time(NULL) - view->start_time;
2721                         if (secs > view->update_secs) {
2722                                 if (view->update_secs == 0)
2723                                         redraw_view(view);
2724                                 update_view_title(view);
2725                                 view->update_secs = secs;
2726                         }
2727                 }
2728                 return TRUE;
2729         }
2731         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2732                 if (opt_iconv != ICONV_NONE) {
2733                         ICONV_CONST char *inbuf = line;
2734                         size_t inlen = strlen(line) + 1;
2736                         char *outbuf = out_buffer;
2737                         size_t outlen = sizeof(out_buffer);
2739                         size_t ret;
2741                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2742                         if (ret != (size_t) -1)
2743                                 line = out_buffer;
2744                 }
2746                 if (!view->ops->read(view, line))
2747                         goto alloc_error;
2748         }
2750         {
2751                 unsigned long lines = view->lines;
2752                 int digits;
2754                 for (digits = 0; lines; digits++)
2755                         lines /= 10;
2757                 /* Keep the displayed view in sync with line number scaling. */
2758                 if (digits != view->digits) {
2759                         view->digits = digits;
2760                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2761                                 redraw = TRUE;
2762                 }
2763         }
2765         if (io_error(view->pipe)) {
2766                 report("Failed to read: %s", io_strerror(view->pipe));
2767                 end_update(view, TRUE);
2769         } else if (io_eof(view->pipe)) {
2770                 report("");
2771                 end_update(view, FALSE);
2772         }
2774         if (!view_is_displayed(view))
2775                 return TRUE;
2777         if (redraw)
2778                 redraw_view_from(view, 0);
2779         else
2780                 redraw_view_dirty(view);
2782         /* Update the title _after_ the redraw so that if the redraw picks up a
2783          * commit reference in view->ref it'll be available here. */
2784         update_view_title(view);
2785         return TRUE;
2787 alloc_error:
2788         report("Allocation failure");
2789         end_update(view, TRUE);
2790         return FALSE;
2793 static struct line *
2794 add_line_data(struct view *view, void *data, enum line_type type)
2796         struct line *line;
2798         if (!realloc_lines(view, view->lines + 1))
2799                 return NULL;
2801         line = &view->line[view->lines++];
2802         memset(line, 0, sizeof(*line));
2803         line->type = type;
2804         line->data = data;
2805         line->dirty = 1;
2807         return line;
2810 static struct line *
2811 add_line_text(struct view *view, const char *text, enum line_type type)
2813         char *data = text ? strdup(text) : NULL;
2815         return data ? add_line_data(view, data, type) : NULL;
2818 static struct line *
2819 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2821         char buf[SIZEOF_STR];
2822         va_list args;
2824         va_start(args, fmt);
2825         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2826                 buf[0] = 0;
2827         va_end(args);
2829         return buf[0] ? add_line_text(view, buf, type) : NULL;
2832 /*
2833  * View opening
2834  */
2836 enum open_flags {
2837         OPEN_DEFAULT = 0,       /* Use default view switching. */
2838         OPEN_SPLIT = 1,         /* Split current view. */
2839         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2840         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2841         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2842         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2843         OPEN_PREPARED = 32,     /* Open already prepared command. */
2844 };
2846 static void
2847 open_view(struct view *prev, enum request request, enum open_flags flags)
2849         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2850         bool split = !!(flags & OPEN_SPLIT);
2851         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2852         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2853         struct view *view = VIEW(request);
2854         int nviews = displayed_views();
2855         struct view *base_view = display[0];
2857         if (view == prev && nviews == 1 && !reload) {
2858                 report("Already in %s view", view->name);
2859                 return;
2860         }
2862         if (view->git_dir && !opt_git_dir[0]) {
2863                 report("The %s view is disabled in pager view", view->name);
2864                 return;
2865         }
2867         if (split) {
2868                 display[1] = view;
2869                 if (!backgrounded)
2870                         current_view = 1;
2871         } else if (!nomaximize) {
2872                 /* Maximize the current view. */
2873                 memset(display, 0, sizeof(display));
2874                 current_view = 0;
2875                 display[current_view] = view;
2876         }
2878         /* Resize the view when switching between split- and full-screen,
2879          * or when switching between two different full-screen views. */
2880         if (nviews != displayed_views() ||
2881             (nviews == 1 && base_view != display[0]))
2882                 resize_display();
2884         if (view->ops->open) {
2885                 if (!view->ops->open(view)) {
2886                         report("Failed to load %s view", view->name);
2887                         return;
2888                 }
2890         } else if ((reload || strcmp(view->vid, view->id)) &&
2891                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2892                 report("Failed to load %s view", view->name);
2893                 return;
2894         }
2896         if (split && prev->lineno - prev->offset >= prev->height) {
2897                 /* Take the title line into account. */
2898                 int lines = prev->lineno - prev->offset - prev->height + 1;
2900                 /* Scroll the view that was split if the current line is
2901                  * outside the new limited view. */
2902                 do_scroll_view(prev, lines);
2903         }
2905         if (prev && view != prev) {
2906                 if (split && !backgrounded) {
2907                         /* "Blur" the previous view. */
2908                         update_view_title(prev);
2909                 }
2911                 view->parent = prev;
2912         }
2914         if (view->pipe && view->lines == 0) {
2915                 /* Clear the old view and let the incremental updating refill
2916                  * the screen. */
2917                 werase(view->win);
2918                 report("");
2919         } else if (view_is_displayed(view)) {
2920                 redraw_view(view);
2921                 report("");
2922         }
2924         /* If the view is backgrounded the above calls to report()
2925          * won't redraw the view title. */
2926         if (backgrounded)
2927                 update_view_title(view);
2930 static void
2931 open_external_viewer(const char *argv[], const char *dir)
2933         def_prog_mode();           /* save current tty modes */
2934         endwin();                  /* restore original tty modes */
2935         run_io_fg(argv, dir);
2936         fprintf(stderr, "Press Enter to continue");
2937         getc(opt_tty);
2938         reset_prog_mode();
2939         redraw_display(TRUE);
2942 static void
2943 open_mergetool(const char *file)
2945         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2947         open_external_viewer(mergetool_argv, opt_cdup);
2950 static void
2951 open_editor(bool from_root, const char *file)
2953         const char *editor_argv[] = { "vi", file, NULL };
2954         const char *editor;
2956         editor = getenv("GIT_EDITOR");
2957         if (!editor && *opt_editor)
2958                 editor = opt_editor;
2959         if (!editor)
2960                 editor = getenv("VISUAL");
2961         if (!editor)
2962                 editor = getenv("EDITOR");
2963         if (!editor)
2964                 editor = "vi";
2966         editor_argv[0] = editor;
2967         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2970 static void
2971 open_run_request(enum request request)
2973         struct run_request *req = get_run_request(request);
2974         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2976         if (!req) {
2977                 report("Unknown run request");
2978                 return;
2979         }
2981         if (format_argv(argv, req->argv, FORMAT_ALL))
2982                 open_external_viewer(argv, NULL);
2983         free_argv(argv);
2986 /*
2987  * User request switch noodle
2988  */
2990 static int
2991 view_driver(struct view *view, enum request request)
2993         int i;
2995         if (request == REQ_NONE) {
2996                 doupdate();
2997                 return TRUE;
2998         }
3000         if (request > REQ_NONE) {
3001                 open_run_request(request);
3002                 /* FIXME: When all views can refresh always do this. */
3003                 if (view == VIEW(REQ_VIEW_STATUS) ||
3004                     view == VIEW(REQ_VIEW_MAIN) ||
3005                     view == VIEW(REQ_VIEW_LOG) ||
3006                     view == VIEW(REQ_VIEW_STAGE))
3007                         request = REQ_REFRESH;
3008                 else
3009                         return TRUE;
3010         }
3012         if (view && view->lines) {
3013                 request = view->ops->request(view, request, &view->line[view->lineno]);
3014                 if (request == REQ_NONE)
3015                         return TRUE;
3016         }
3018         switch (request) {
3019         case REQ_MOVE_UP:
3020         case REQ_MOVE_DOWN:
3021         case REQ_MOVE_PAGE_UP:
3022         case REQ_MOVE_PAGE_DOWN:
3023         case REQ_MOVE_FIRST_LINE:
3024         case REQ_MOVE_LAST_LINE:
3025                 move_view(view, request);
3026                 break;
3028         case REQ_SCROLL_LINE_DOWN:
3029         case REQ_SCROLL_LINE_UP:
3030         case REQ_SCROLL_PAGE_DOWN:
3031         case REQ_SCROLL_PAGE_UP:
3032                 scroll_view(view, request);
3033                 break;
3035         case REQ_VIEW_BLAME:
3036                 if (!opt_file[0]) {
3037                         report("No file chosen, press %s to open tree view",
3038                                get_key(REQ_VIEW_TREE));
3039                         break;
3040                 }
3041                 open_view(view, request, OPEN_DEFAULT);
3042                 break;
3044         case REQ_VIEW_BLOB:
3045                 if (!ref_blob[0]) {
3046                         report("No file chosen, press %s to open tree view",
3047                                get_key(REQ_VIEW_TREE));
3048                         break;
3049                 }
3050                 open_view(view, request, OPEN_DEFAULT);
3051                 break;
3053         case REQ_VIEW_PAGER:
3054                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3055                         report("No pager content, press %s to run command from prompt",
3056                                get_key(REQ_PROMPT));
3057                         break;
3058                 }
3059                 open_view(view, request, OPEN_DEFAULT);
3060                 break;
3062         case REQ_VIEW_STAGE:
3063                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3064                         report("No stage content, press %s to open the status view and choose file",
3065                                get_key(REQ_VIEW_STATUS));
3066                         break;
3067                 }
3068                 open_view(view, request, OPEN_DEFAULT);
3069                 break;
3071         case REQ_VIEW_STATUS:
3072                 if (opt_is_inside_work_tree == FALSE) {
3073                         report("The status view requires a working tree");
3074                         break;
3075                 }
3076                 open_view(view, request, OPEN_DEFAULT);
3077                 break;
3079         case REQ_VIEW_MAIN:
3080         case REQ_VIEW_DIFF:
3081         case REQ_VIEW_LOG:
3082         case REQ_VIEW_TREE:
3083         case REQ_VIEW_HELP:
3084                 open_view(view, request, OPEN_DEFAULT);
3085                 break;
3087         case REQ_NEXT:
3088         case REQ_PREVIOUS:
3089                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3091                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3092                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3093                    (view == VIEW(REQ_VIEW_DIFF) &&
3094                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3095                    (view == VIEW(REQ_VIEW_STAGE) &&
3096                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3097                    (view == VIEW(REQ_VIEW_BLOB) &&
3098                      view->parent == VIEW(REQ_VIEW_TREE))) {
3099                         int line;
3101                         view = view->parent;
3102                         line = view->lineno;
3103                         move_view(view, request);
3104                         if (view_is_displayed(view))
3105                                 update_view_title(view);
3106                         if (line != view->lineno)
3107                                 view->ops->request(view, REQ_ENTER,
3108                                                    &view->line[view->lineno]);
3110                 } else {
3111                         move_view(view, request);
3112                 }
3113                 break;
3115         case REQ_VIEW_NEXT:
3116         {
3117                 int nviews = displayed_views();
3118                 int next_view = (current_view + 1) % nviews;
3120                 if (next_view == current_view) {
3121                         report("Only one view is displayed");
3122                         break;
3123                 }
3125                 current_view = next_view;
3126                 /* Blur out the title of the previous view. */
3127                 update_view_title(view);
3128                 report("");
3129                 break;
3130         }
3131         case REQ_REFRESH:
3132                 report("Refreshing is not yet supported for the %s view", view->name);
3133                 break;
3135         case REQ_MAXIMIZE:
3136                 if (displayed_views() == 2)
3137                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3138                 break;
3140         case REQ_TOGGLE_LINENO:
3141                 toggle_view_option(&opt_line_number, "line numbers");
3142                 break;
3144         case REQ_TOGGLE_DATE:
3145                 toggle_view_option(&opt_date, "date display");
3146                 break;
3148         case REQ_TOGGLE_AUTHOR:
3149                 toggle_view_option(&opt_author, "author display");
3150                 break;
3152         case REQ_TOGGLE_REV_GRAPH:
3153                 toggle_view_option(&opt_rev_graph, "revision graph display");
3154                 break;
3156         case REQ_TOGGLE_REFS:
3157                 toggle_view_option(&opt_show_refs, "reference display");
3158                 break;
3160         case REQ_SEARCH:
3161         case REQ_SEARCH_BACK:
3162                 search_view(view, request);
3163                 break;
3165         case REQ_FIND_NEXT:
3166         case REQ_FIND_PREV:
3167                 find_next(view, request);
3168                 break;
3170         case REQ_STOP_LOADING:
3171                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3172                         view = &views[i];
3173                         if (view->pipe)
3174                                 report("Stopped loading the %s view", view->name),
3175                         end_update(view, TRUE);
3176                 }
3177                 break;
3179         case REQ_SHOW_VERSION:
3180                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3181                 return TRUE;
3183         case REQ_SCREEN_REDRAW:
3184                 redraw_display(TRUE);
3185                 break;
3187         case REQ_EDIT:
3188                 report("Nothing to edit");
3189                 break;
3191         case REQ_ENTER:
3192                 report("Nothing to enter");
3193                 break;
3195         case REQ_VIEW_CLOSE:
3196                 /* XXX: Mark closed views by letting view->parent point to the
3197                  * view itself. Parents to closed view should never be
3198                  * followed. */
3199                 if (view->parent &&
3200                     view->parent->parent != view->parent) {
3201                         memset(display, 0, sizeof(display));
3202                         current_view = 0;
3203                         display[current_view] = view->parent;
3204                         view->parent = view;
3205                         resize_display();
3206                         redraw_display(FALSE);
3207                         report("");
3208                         break;
3209                 }
3210                 /* Fall-through */
3211         case REQ_QUIT:
3212                 return FALSE;
3214         default:
3215                 report("Unknown key, press 'h' for help");
3216                 return TRUE;
3217         }
3219         return TRUE;
3223 /*
3224  * Pager backend
3225  */
3227 static bool
3228 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3230         char *text = line->data;
3232         if (opt_line_number && draw_lineno(view, lineno))
3233                 return TRUE;
3235         draw_text(view, line->type, text, TRUE);
3236         return TRUE;
3239 static bool
3240 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3242         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3243         char refbuf[SIZEOF_STR];
3244         char *ref = NULL;
3246         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3247                 ref = chomp_string(refbuf);
3249         if (!ref || !*ref)
3250                 return TRUE;
3252         /* This is the only fatal call, since it can "corrupt" the buffer. */
3253         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3254                 return FALSE;
3256         return TRUE;
3259 static void
3260 add_pager_refs(struct view *view, struct line *line)
3262         char buf[SIZEOF_STR];
3263         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3264         struct ref **refs;
3265         size_t bufpos = 0, refpos = 0;
3266         const char *sep = "Refs: ";
3267         bool is_tag = FALSE;
3269         assert(line->type == LINE_COMMIT);
3271         refs = get_refs(commit_id);
3272         if (!refs) {
3273                 if (view == VIEW(REQ_VIEW_DIFF))
3274                         goto try_add_describe_ref;
3275                 return;
3276         }
3278         do {
3279                 struct ref *ref = refs[refpos];
3280                 const char *fmt = ref->tag    ? "%s[%s]" :
3281                                   ref->remote ? "%s<%s>" : "%s%s";
3283                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3284                         return;
3285                 sep = ", ";
3286                 if (ref->tag)
3287                         is_tag = TRUE;
3288         } while (refs[refpos++]->next);
3290         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3291 try_add_describe_ref:
3292                 /* Add <tag>-g<commit_id> "fake" reference. */
3293                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3294                         return;
3295         }
3297         if (bufpos == 0)
3298                 return;
3300         add_line_text(view, buf, LINE_PP_REFS);
3303 static bool
3304 pager_read(struct view *view, char *data)
3306         struct line *line;
3308         if (!data)
3309                 return TRUE;
3311         line = add_line_text(view, data, get_line_type(data));
3312         if (!line)
3313                 return FALSE;
3315         if (line->type == LINE_COMMIT &&
3316             (view == VIEW(REQ_VIEW_DIFF) ||
3317              view == VIEW(REQ_VIEW_LOG)))
3318                 add_pager_refs(view, line);
3320         return TRUE;
3323 static enum request
3324 pager_request(struct view *view, enum request request, struct line *line)
3326         int split = 0;
3328         if (request != REQ_ENTER)
3329                 return request;
3331         if (line->type == LINE_COMMIT &&
3332            (view == VIEW(REQ_VIEW_LOG) ||
3333             view == VIEW(REQ_VIEW_PAGER))) {
3334                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3335                 split = 1;
3336         }
3338         /* Always scroll the view even if it was split. That way
3339          * you can use Enter to scroll through the log view and
3340          * split open each commit diff. */
3341         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3343         /* FIXME: A minor workaround. Scrolling the view will call report("")
3344          * but if we are scrolling a non-current view this won't properly
3345          * update the view title. */
3346         if (split)
3347                 update_view_title(view);
3349         return REQ_NONE;
3352 static bool
3353 pager_grep(struct view *view, struct line *line)
3355         regmatch_t pmatch;
3356         char *text = line->data;
3358         if (!*text)
3359                 return FALSE;
3361         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3362                 return FALSE;
3364         return TRUE;
3367 static void
3368 pager_select(struct view *view, struct line *line)
3370         if (line->type == LINE_COMMIT) {
3371                 char *text = (char *)line->data + STRING_SIZE("commit ");
3373                 if (view != VIEW(REQ_VIEW_PAGER))
3374                         string_copy_rev(view->ref, text);
3375                 string_copy_rev(ref_commit, text);
3376         }
3379 static struct view_ops pager_ops = {
3380         "line",
3381         NULL,
3382         NULL,
3383         pager_read,
3384         pager_draw,
3385         pager_request,
3386         pager_grep,
3387         pager_select,
3388 };
3390 static const char *log_argv[SIZEOF_ARG] = {
3391         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3392 };
3394 static enum request
3395 log_request(struct view *view, enum request request, struct line *line)
3397         switch (request) {
3398         case REQ_REFRESH:
3399                 load_refs();
3400                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3401                 return REQ_NONE;
3402         default:
3403                 return pager_request(view, request, line);
3404         }
3407 static struct view_ops log_ops = {
3408         "line",
3409         log_argv,
3410         NULL,
3411         pager_read,
3412         pager_draw,
3413         log_request,
3414         pager_grep,
3415         pager_select,
3416 };
3418 static const char *diff_argv[SIZEOF_ARG] = {
3419         "git", "show", "--pretty=fuller", "--no-color", "--root",
3420                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3421 };
3423 static struct view_ops diff_ops = {
3424         "line",
3425         diff_argv,
3426         NULL,
3427         pager_read,
3428         pager_draw,
3429         pager_request,
3430         pager_grep,
3431         pager_select,
3432 };
3434 /*
3435  * Help backend
3436  */
3438 static bool
3439 help_open(struct view *view)
3441         int lines = ARRAY_SIZE(req_info) + 2;
3442         int i;
3444         if (view->lines > 0)
3445                 return TRUE;
3447         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3448                 if (!req_info[i].request)
3449                         lines++;
3451         lines += run_requests + 1;
3453         view->line = calloc(lines, sizeof(*view->line));
3454         if (!view->line)
3455                 return FALSE;
3457         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3459         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3460                 const char *key;
3462                 if (req_info[i].request == REQ_NONE)
3463                         continue;
3465                 if (!req_info[i].request) {
3466                         add_line_text(view, "", LINE_DEFAULT);
3467                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3468                         continue;
3469                 }
3471                 key = get_key(req_info[i].request);
3472                 if (!*key)
3473                         key = "(no key defined)";
3475                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3476                                 key, req_info[i].help);
3477         }
3479         if (run_requests) {
3480                 add_line_text(view, "", LINE_DEFAULT);
3481                 add_line_text(view, "External commands:", LINE_DEFAULT);
3482         }
3484         for (i = 0; i < run_requests; i++) {
3485                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3486                 const char *key;
3487                 char cmd[SIZEOF_STR];
3488                 size_t bufpos;
3489                 int argc;
3491                 if (!req)
3492                         continue;
3494                 key = get_key_name(req->key);
3495                 if (!*key)
3496                         key = "(no key defined)";
3498                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3499                         if (!string_format_from(cmd, &bufpos, "%s%s",
3500                                                 argc ? " " : "", req->argv[argc]))
3501                                 return REQ_NONE;
3503                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3504                                 keymap_table[req->keymap].name, key, cmd);
3505         }
3507         return TRUE;
3510 static struct view_ops help_ops = {
3511         "line",
3512         NULL,
3513         help_open,
3514         NULL,
3515         pager_draw,
3516         pager_request,
3517         pager_grep,
3518         pager_select,
3519 };
3522 /*
3523  * Tree backend
3524  */
3526 struct tree_stack_entry {
3527         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3528         unsigned long lineno;           /* Line number to restore */
3529         char *name;                     /* Position of name in opt_path */
3530 };
3532 /* The top of the path stack. */
3533 static struct tree_stack_entry *tree_stack = NULL;
3534 unsigned long tree_lineno = 0;
3536 static void
3537 pop_tree_stack_entry(void)
3539         struct tree_stack_entry *entry = tree_stack;
3541         tree_lineno = entry->lineno;
3542         entry->name[0] = 0;
3543         tree_stack = entry->prev;
3544         free(entry);
3547 static void
3548 push_tree_stack_entry(const char *name, unsigned long lineno)
3550         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3551         size_t pathlen = strlen(opt_path);
3553         if (!entry)
3554                 return;
3556         entry->prev = tree_stack;
3557         entry->name = opt_path + pathlen;
3558         tree_stack = entry;
3560         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3561                 pop_tree_stack_entry();
3562                 return;
3563         }
3565         /* Move the current line to the first tree entry. */
3566         tree_lineno = 1;
3567         entry->lineno = lineno;
3570 /* Parse output from git-ls-tree(1):
3571  *
3572  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3573  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3574  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3575  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3576  */
3578 #define SIZEOF_TREE_ATTR \
3579         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3581 #define TREE_UP_FORMAT "040000 tree %s\t.."
3583 static const char *
3584 tree_path(struct line *line)
3586         const char *path = line->data;
3588         return path + SIZEOF_TREE_ATTR;
3591 static int
3592 tree_compare_entry(struct line *line1, struct line *line2)
3594         if (line1->type != line2->type)
3595                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3596         return strcmp(tree_path(line1), tree_path(line2));
3599 static bool
3600 tree_read(struct view *view, char *text)
3602         size_t textlen = text ? strlen(text) : 0;
3603         struct line *entry, *line;
3604         enum line_type type;
3606         if (!text)
3607                 return TRUE;
3608         if (textlen <= SIZEOF_TREE_ATTR)
3609                 return FALSE;
3611         type = text[STRING_SIZE("100644 ")] == 't'
3612              ? LINE_TREE_DIR : LINE_TREE_FILE;
3614         if (view->lines == 0 &&
3615             !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
3616                 return FALSE;
3618         /* Strip the path part ... */
3619         if (*opt_path) {
3620                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3621                 size_t striplen = strlen(opt_path);
3622                 char *path = text + SIZEOF_TREE_ATTR;
3624                 if (pathlen > striplen)
3625                         memmove(path, path + striplen,
3626                                 pathlen - striplen + 1);
3628                 /* Insert "link" to parent directory. */
3629                 if (view->lines == 1 &&
3630                     !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
3631                         return FALSE;
3632         }
3634         entry = add_line_text(view, text, type);
3635         if (!entry)
3636                 return FALSE;
3637         text = entry->data;
3639         /* Skip "Directory ..." and ".." line. */
3640         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3641                 if (tree_compare_entry(line, entry) <= 0)
3642                         continue;
3644                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3646                 line->data = text;
3647                 line->type = type;
3648                 for (; line <= entry; line++)
3649                         line->dirty = line->cleareol = 1;
3650                 return TRUE;
3651         }
3653         if (tree_lineno > view->lineno) {
3654                 view->lineno = tree_lineno;
3655                 tree_lineno = 0;
3656         }
3658         return TRUE;
3661 static void
3662 open_blob_editor()
3664         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3665         int fd = mkstemp(file);
3667         if (fd == -1)
3668                 report("Failed to create temporary file");
3669         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3670                 report("Failed to save blob data to file");
3671         else
3672                 open_editor(FALSE, file);
3673         if (fd != -1)
3674                 unlink(file);
3677 static enum request
3678 tree_request(struct view *view, enum request request, struct line *line)
3680         enum open_flags flags;
3682         switch (request) {
3683         case REQ_VIEW_BLAME:
3684                 if (line->type != LINE_TREE_FILE) {
3685                         report("Blame only supported for files");
3686                         return REQ_NONE;
3687                 }
3689                 string_copy(opt_ref, view->vid);
3690                 return request;
3692         case REQ_EDIT:
3693                 if (line->type != LINE_TREE_FILE) {
3694                         report("Edit only supported for files");
3695                 } else if (!is_head_commit(view->vid)) {
3696                         open_blob_editor();
3697                 } else {
3698                         open_editor(TRUE, opt_file);
3699                 }
3700                 return REQ_NONE;
3702         case REQ_TREE_PARENT:
3703                 if (!*opt_path) {
3704                         /* quit view if at top of tree */
3705                         return REQ_VIEW_CLOSE;
3706                 }
3707                 /* fake 'cd  ..' */
3708                 line = &view->line[1];
3709                 break;
3711         case REQ_ENTER:
3712                 break;
3714         default:
3715                 return request;
3716         }
3718         /* Cleanup the stack if the tree view is at a different tree. */
3719         while (!*opt_path && tree_stack)
3720                 pop_tree_stack_entry();
3722         switch (line->type) {
3723         case LINE_TREE_DIR:
3724                 /* Depending on whether it is a subdir or parent (updir?) link
3725                  * mangle the path buffer. */
3726                 if (line == &view->line[1] && *opt_path) {
3727                         pop_tree_stack_entry();
3729                 } else {
3730                         const char *basename = tree_path(line);
3732                         push_tree_stack_entry(basename, view->lineno);
3733                 }
3735                 /* Trees and subtrees share the same ID, so they are not not
3736                  * unique like blobs. */
3737                 flags = OPEN_RELOAD;
3738                 request = REQ_VIEW_TREE;
3739                 break;
3741         case LINE_TREE_FILE:
3742                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3743                 request = REQ_VIEW_BLOB;
3744                 break;
3746         default:
3747                 return TRUE;
3748         }
3750         open_view(view, request, flags);
3751         if (request == REQ_VIEW_TREE) {
3752                 view->lineno = tree_lineno;
3753         }
3755         return REQ_NONE;
3758 static void
3759 tree_select(struct view *view, struct line *line)
3761         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3763         if (line->type == LINE_TREE_FILE) {
3764                 string_copy_rev(ref_blob, text);
3765                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3767         } else if (line->type != LINE_TREE_DIR) {
3768                 return;
3769         }
3771         string_copy_rev(view->ref, text);
3774 static const char *tree_argv[SIZEOF_ARG] = {
3775         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3776 };
3778 static struct view_ops tree_ops = {
3779         "file",
3780         tree_argv,
3781         NULL,
3782         tree_read,
3783         pager_draw,
3784         tree_request,
3785         pager_grep,
3786         tree_select,
3787 };
3789 static bool
3790 blob_read(struct view *view, char *line)
3792         if (!line)
3793                 return TRUE;
3794         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3797 static enum request
3798 blob_request(struct view *view, enum request request, struct line *line)
3800         switch (request) {
3801         case REQ_EDIT:
3802                 open_blob_editor();
3803                 return REQ_NONE;
3804         default:
3805                 return pager_request(view, request, line);
3806         }
3809 static const char *blob_argv[SIZEOF_ARG] = {
3810         "git", "cat-file", "blob", "%(blob)", NULL
3811 };
3813 static struct view_ops blob_ops = {
3814         "line",
3815         blob_argv,
3816         NULL,
3817         blob_read,
3818         pager_draw,
3819         blob_request,
3820         pager_grep,
3821         pager_select,
3822 };
3824 /*
3825  * Blame backend
3826  *
3827  * Loading the blame view is a two phase job:
3828  *
3829  *  1. File content is read either using opt_file from the
3830  *     filesystem or using git-cat-file.
3831  *  2. Then blame information is incrementally added by
3832  *     reading output from git-blame.
3833  */
3835 static const char *blame_head_argv[] = {
3836         "git", "blame", "--incremental", "--", "%(file)", NULL
3837 };
3839 static const char *blame_ref_argv[] = {
3840         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3841 };
3843 static const char *blame_cat_file_argv[] = {
3844         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3845 };
3847 struct blame_commit {
3848         char id[SIZEOF_REV];            /* SHA1 ID. */
3849         char title[128];                /* First line of the commit message. */
3850         char author[75];                /* Author of the commit. */
3851         struct tm time;                 /* Date from the author ident. */
3852         char filename[128];             /* Name of file. */
3853 };
3855 struct blame {
3856         struct blame_commit *commit;
3857         char text[1];
3858 };
3860 static bool
3861 blame_open(struct view *view)
3863         if (*opt_ref || !io_open(&view->io, opt_file)) {
3864                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3865                         return FALSE;
3866         }
3868         setup_update(view, opt_file);
3869         string_format(view->ref, "%s ...", opt_file);
3871         return TRUE;
3874 static struct blame_commit *
3875 get_blame_commit(struct view *view, const char *id)
3877         size_t i;
3879         for (i = 0; i < view->lines; i++) {
3880                 struct blame *blame = view->line[i].data;
3882                 if (!blame->commit)
3883                         continue;
3885                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3886                         return blame->commit;
3887         }
3889         {
3890                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3892                 if (commit)
3893                         string_ncopy(commit->id, id, SIZEOF_REV);
3894                 return commit;
3895         }
3898 static bool
3899 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3901         const char *pos = *posref;
3903         *posref = NULL;
3904         pos = strchr(pos + 1, ' ');
3905         if (!pos || !isdigit(pos[1]))
3906                 return FALSE;
3907         *number = atoi(pos + 1);
3908         if (*number < min || *number > max)
3909                 return FALSE;
3911         *posref = pos;
3912         return TRUE;
3915 static struct blame_commit *
3916 parse_blame_commit(struct view *view, const char *text, int *blamed)
3918         struct blame_commit *commit;
3919         struct blame *blame;
3920         const char *pos = text + SIZEOF_REV - 1;
3921         size_t lineno;
3922         size_t group;
3924         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3925                 return NULL;
3927         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3928             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3929                 return NULL;
3931         commit = get_blame_commit(view, text);
3932         if (!commit)
3933                 return NULL;
3935         *blamed += group;
3936         while (group--) {
3937                 struct line *line = &view->line[lineno + group - 1];
3939                 blame = line->data;
3940                 blame->commit = commit;
3941                 line->dirty = 1;
3942         }
3944         return commit;
3947 static bool
3948 blame_read_file(struct view *view, const char *line, bool *read_file)
3950         if (!line) {
3951                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3952                 struct io io = {};
3954                 if (view->lines == 0 && !view->parent)
3955                         die("No blame exist for %s", view->vid);
3957                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3958                         report("Failed to load blame data");
3959                         return TRUE;
3960                 }
3962                 done_io(view->pipe);
3963                 view->io = io;
3964                 *read_file = FALSE;
3965                 return FALSE;
3967         } else {
3968                 size_t linelen = strlen(line);
3969                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3971                 blame->commit = NULL;
3972                 strncpy(blame->text, line, linelen);
3973                 blame->text[linelen] = 0;
3974                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3975         }
3978 static bool
3979 match_blame_header(const char *name, char **line)
3981         size_t namelen = strlen(name);
3982         bool matched = !strncmp(name, *line, namelen);
3984         if (matched)
3985                 *line += namelen;
3987         return matched;
3990 static bool
3991 blame_read(struct view *view, char *line)
3993         static struct blame_commit *commit = NULL;
3994         static int blamed = 0;
3995         static time_t author_time;
3996         static bool read_file = TRUE;
3998         if (read_file)
3999                 return blame_read_file(view, line, &read_file);
4001         if (!line) {
4002                 /* Reset all! */
4003                 commit = NULL;
4004                 blamed = 0;
4005                 read_file = TRUE;
4006                 string_format(view->ref, "%s", view->vid);
4007                 if (view_is_displayed(view)) {
4008                         update_view_title(view);
4009                         redraw_view_from(view, 0);
4010                 }
4011                 return TRUE;
4012         }
4014         if (!commit) {
4015                 commit = parse_blame_commit(view, line, &blamed);
4016                 string_format(view->ref, "%s %2d%%", view->vid,
4017                               blamed * 100 / view->lines);
4019         } else if (match_blame_header("author ", &line)) {
4020                 string_ncopy(commit->author, line, strlen(line));
4022         } else if (match_blame_header("author-time ", &line)) {
4023                 author_time = (time_t) atol(line);
4025         } else if (match_blame_header("author-tz ", &line)) {
4026                 long tz;
4028                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4029                 tz += ('0' - line[2]) * 60 * 60;
4030                 tz += ('0' - line[3]) * 60;
4031                 tz += ('0' - line[4]) * 60;
4033                 if (line[0] == '-')
4034                         tz = -tz;
4036                 author_time -= tz;
4037                 gmtime_r(&author_time, &commit->time);
4039         } else if (match_blame_header("summary ", &line)) {
4040                 string_ncopy(commit->title, line, strlen(line));
4042         } else if (match_blame_header("filename ", &line)) {
4043                 string_ncopy(commit->filename, line, strlen(line));
4044                 commit = NULL;
4045         }
4047         return TRUE;
4050 static bool
4051 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4053         struct blame *blame = line->data;
4054         struct tm *time = NULL;
4055         const char *id = NULL, *author = NULL;
4057         if (blame->commit && *blame->commit->filename) {
4058                 id = blame->commit->id;
4059                 author = blame->commit->author;
4060                 time = &blame->commit->time;
4061         }
4063         if (opt_date && draw_date(view, time))
4064                 return TRUE;
4066         if (opt_author &&
4067             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4068                 return TRUE;
4070         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4071                 return TRUE;
4073         if (draw_lineno(view, lineno))
4074                 return TRUE;
4076         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4077         return TRUE;
4080 static bool
4081 check_blame_commit(struct blame *blame)
4083         if (!blame->commit)
4084                 report("Commit data not loaded yet");
4085         else if (!strcmp(blame->commit->id, NULL_ID))
4086                 report("No commit exist for the selected line");
4087         else
4088                 return TRUE;
4089         return FALSE;
4092 static enum request
4093 blame_request(struct view *view, enum request request, struct line *line)
4095         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4096         struct blame *blame = line->data;
4098         switch (request) {
4099         case REQ_VIEW_BLAME:
4100                 if (check_blame_commit(blame)) {
4101                         string_copy(opt_ref, blame->commit->id);
4102                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4103                 }
4104                 break;
4106         case REQ_ENTER:
4107                 if (!blame->commit) {
4108                         report("No commit loaded yet");
4109                         break;
4110                 }
4112                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4113                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4114                         break;
4116                 if (!strcmp(blame->commit->id, NULL_ID)) {
4117                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4118                         const char *diff_index_argv[] = {
4119                                 "git", "diff-index", "--root", "--cached",
4120                                         "--patch-with-stat", "-C", "-M",
4121                                         "HEAD", "--", view->vid, NULL
4122                         };
4124                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4125                                 report("Failed to allocate diff command");
4126                                 break;
4127                         }
4128                         flags |= OPEN_PREPARED;
4129                 }
4131                 open_view(view, REQ_VIEW_DIFF, flags);
4132                 break;
4134         default:
4135                 return request;
4136         }
4138         return REQ_NONE;
4141 static bool
4142 blame_grep(struct view *view, struct line *line)
4144         struct blame *blame = line->data;
4145         struct blame_commit *commit = blame->commit;
4146         regmatch_t pmatch;
4148 #define MATCH(text, on)                                                 \
4149         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4151         if (commit) {
4152                 char buf[DATE_COLS + 1];
4154                 if (MATCH(commit->title, 1) ||
4155                     MATCH(commit->author, opt_author) ||
4156                     MATCH(commit->id, opt_date))
4157                         return TRUE;
4159                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4160                     MATCH(buf, 1))
4161                         return TRUE;
4162         }
4164         return MATCH(blame->text, 1);
4166 #undef MATCH
4169 static void
4170 blame_select(struct view *view, struct line *line)
4172         struct blame *blame = line->data;
4173         struct blame_commit *commit = blame->commit;
4175         if (!commit)
4176                 return;
4178         if (!strcmp(commit->id, NULL_ID))
4179                 string_ncopy(ref_commit, "HEAD", 4);
4180         else
4181                 string_copy_rev(ref_commit, commit->id);
4184 static struct view_ops blame_ops = {
4185         "line",
4186         NULL,
4187         blame_open,
4188         blame_read,
4189         blame_draw,
4190         blame_request,
4191         blame_grep,
4192         blame_select,
4193 };
4195 /*
4196  * Status backend
4197  */
4199 struct status {
4200         char status;
4201         struct {
4202                 mode_t mode;
4203                 char rev[SIZEOF_REV];
4204                 char name[SIZEOF_STR];
4205         } old;
4206         struct {
4207                 mode_t mode;
4208                 char rev[SIZEOF_REV];
4209                 char name[SIZEOF_STR];
4210         } new;
4211 };
4213 static char status_onbranch[SIZEOF_STR];
4214 static struct status stage_status;
4215 static enum line_type stage_line_type;
4216 static size_t stage_chunks;
4217 static int *stage_chunk;
4219 /* This should work even for the "On branch" line. */
4220 static inline bool
4221 status_has_none(struct view *view, struct line *line)
4223         return line < view->line + view->lines && !line[1].data;
4226 /* Get fields from the diff line:
4227  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4228  */
4229 static inline bool
4230 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4232         const char *old_mode = buf +  1;
4233         const char *new_mode = buf +  8;
4234         const char *old_rev  = buf + 15;
4235         const char *new_rev  = buf + 56;
4236         const char *status   = buf + 97;
4238         if (bufsize < 98 ||
4239             old_mode[-1] != ':' ||
4240             new_mode[-1] != ' ' ||
4241             old_rev[-1]  != ' ' ||
4242             new_rev[-1]  != ' ' ||
4243             status[-1]   != ' ')
4244                 return FALSE;
4246         file->status = *status;
4248         string_copy_rev(file->old.rev, old_rev);
4249         string_copy_rev(file->new.rev, new_rev);
4251         file->old.mode = strtoul(old_mode, NULL, 8);
4252         file->new.mode = strtoul(new_mode, NULL, 8);
4254         file->old.name[0] = file->new.name[0] = 0;
4256         return TRUE;
4259 static bool
4260 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4262         struct status *file = NULL;
4263         struct status *unmerged = NULL;
4264         char *buf;
4265         struct io io = {};
4267         if (!run_io(&io, argv, NULL, IO_RD))
4268                 return FALSE;
4270         add_line_data(view, NULL, type);
4272         while ((buf = io_get(&io, 0, TRUE))) {
4273                 if (!file) {
4274                         file = calloc(1, sizeof(*file));
4275                         if (!file || !add_line_data(view, file, type))
4276                                 goto error_out;
4277                 }
4279                 /* Parse diff info part. */
4280                 if (status) {
4281                         file->status = status;
4282                         if (status == 'A')
4283                                 string_copy(file->old.rev, NULL_ID);
4285                 } else if (!file->status) {
4286                         if (!status_get_diff(file, buf, strlen(buf)))
4287                                 goto error_out;
4289                         buf = io_get(&io, 0, TRUE);
4290                         if (!buf)
4291                                 break;
4293                         /* Collapse all 'M'odified entries that follow a
4294                          * associated 'U'nmerged entry. */
4295                         if (file->status == 'U') {
4296                                 unmerged = file;
4298                         } else if (unmerged) {
4299                                 int collapse = !strcmp(buf, unmerged->new.name);
4301                                 unmerged = NULL;
4302                                 if (collapse) {
4303                                         free(file);
4304                                         view->lines--;
4305                                         continue;
4306                                 }
4307                         }
4308                 }
4310                 /* Grab the old name for rename/copy. */
4311                 if (!*file->old.name &&
4312                     (file->status == 'R' || file->status == 'C')) {
4313                         string_ncopy(file->old.name, buf, strlen(buf));
4315                         buf = io_get(&io, 0, TRUE);
4316                         if (!buf)
4317                                 break;
4318                 }
4320                 /* git-ls-files just delivers a NUL separated list of
4321                  * file names similar to the second half of the
4322                  * git-diff-* output. */
4323                 string_ncopy(file->new.name, buf, strlen(buf));
4324                 if (!*file->old.name)
4325                         string_copy(file->old.name, file->new.name);
4326                 file = NULL;
4327         }
4329         if (io_error(&io)) {
4330 error_out:
4331                 done_io(&io);
4332                 return FALSE;
4333         }
4335         if (!view->line[view->lines - 1].data)
4336                 add_line_data(view, NULL, LINE_STAT_NONE);
4338         done_io(&io);
4339         return TRUE;
4342 /* Don't show unmerged entries in the staged section. */
4343 static const char *status_diff_index_argv[] = {
4344         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4345                              "--cached", "-M", "HEAD", NULL
4346 };
4348 static const char *status_diff_files_argv[] = {
4349         "git", "diff-files", "-z", NULL
4350 };
4352 static const char *status_list_other_argv[] = {
4353         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4354 };
4356 static const char *status_list_no_head_argv[] = {
4357         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4358 };
4360 static const char *update_index_argv[] = {
4361         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4362 };
4364 /* First parse staged info using git-diff-index(1), then parse unstaged
4365  * info using git-diff-files(1), and finally untracked files using
4366  * git-ls-files(1). */
4367 static bool
4368 status_open(struct view *view)
4370         unsigned long prev_lineno = view->lineno;
4372         reset_view(view);
4374         add_line_data(view, NULL, LINE_STAT_HEAD);
4375         if (is_initial_commit())
4376                 string_copy(status_onbranch, "Initial commit");
4377         else if (!*opt_head)
4378                 string_copy(status_onbranch, "Not currently on any branch");
4379         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4380                 return FALSE;
4382         run_io_bg(update_index_argv);
4384         if (is_initial_commit()) {
4385                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4386                         return FALSE;
4387         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4388                 return FALSE;
4389         }
4391         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4392             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4393                 return FALSE;
4395         /* If all went well restore the previous line number to stay in
4396          * the context or select a line with something that can be
4397          * updated. */
4398         if (prev_lineno >= view->lines)
4399                 prev_lineno = view->lines - 1;
4400         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4401                 prev_lineno++;
4402         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4403                 prev_lineno--;
4405         /* If the above fails, always skip the "On branch" line. */
4406         if (prev_lineno < view->lines)
4407                 view->lineno = prev_lineno;
4408         else
4409                 view->lineno = 1;
4411         if (view->lineno < view->offset)
4412                 view->offset = view->lineno;
4413         else if (view->offset + view->height <= view->lineno)
4414                 view->offset = view->lineno - view->height + 1;
4416         return TRUE;
4419 static bool
4420 status_draw(struct view *view, struct line *line, unsigned int lineno)
4422         struct status *status = line->data;
4423         enum line_type type;
4424         const char *text;
4426         if (!status) {
4427                 switch (line->type) {
4428                 case LINE_STAT_STAGED:
4429                         type = LINE_STAT_SECTION;
4430                         text = "Changes to be committed:";
4431                         break;
4433                 case LINE_STAT_UNSTAGED:
4434                         type = LINE_STAT_SECTION;
4435                         text = "Changed but not updated:";
4436                         break;
4438                 case LINE_STAT_UNTRACKED:
4439                         type = LINE_STAT_SECTION;
4440                         text = "Untracked files:";
4441                         break;
4443                 case LINE_STAT_NONE:
4444                         type = LINE_DEFAULT;
4445                         text = "    (no files)";
4446                         break;
4448                 case LINE_STAT_HEAD:
4449                         type = LINE_STAT_HEAD;
4450                         text = status_onbranch;
4451                         break;
4453                 default:
4454                         return FALSE;
4455                 }
4456         } else {
4457                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4459                 buf[0] = status->status;
4460                 if (draw_text(view, line->type, buf, TRUE))
4461                         return TRUE;
4462                 type = LINE_DEFAULT;
4463                 text = status->new.name;
4464         }
4466         draw_text(view, type, text, TRUE);
4467         return TRUE;
4470 static enum request
4471 status_enter(struct view *view, struct line *line)
4473         struct status *status = line->data;
4474         const char *oldpath = status ? status->old.name : NULL;
4475         /* Diffs for unmerged entries are empty when passing the new
4476          * path, so leave it empty. */
4477         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4478         const char *info;
4479         enum open_flags split;
4480         struct view *stage = VIEW(REQ_VIEW_STAGE);
4482         if (line->type == LINE_STAT_NONE ||
4483             (!status && line[1].type == LINE_STAT_NONE)) {
4484                 report("No file to diff");
4485                 return REQ_NONE;
4486         }
4488         switch (line->type) {
4489         case LINE_STAT_STAGED:
4490                 if (is_initial_commit()) {
4491                         const char *no_head_diff_argv[] = {
4492                                 "git", "diff", "--no-color", "--patch-with-stat",
4493                                         "--", "/dev/null", newpath, NULL
4494                         };
4496                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4497                                 return REQ_QUIT;
4498                 } else {
4499                         const char *index_show_argv[] = {
4500                                 "git", "diff-index", "--root", "--patch-with-stat",
4501                                         "-C", "-M", "--cached", "HEAD", "--",
4502                                         oldpath, newpath, NULL
4503                         };
4505                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4506                                 return REQ_QUIT;
4507                 }
4509                 if (status)
4510                         info = "Staged changes to %s";
4511                 else
4512                         info = "Staged changes";
4513                 break;
4515         case LINE_STAT_UNSTAGED:
4516         {
4517                 const char *files_show_argv[] = {
4518                         "git", "diff-files", "--root", "--patch-with-stat",
4519                                 "-C", "-M", "--", oldpath, newpath, NULL
4520                 };
4522                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4523                         return REQ_QUIT;
4524                 if (status)
4525                         info = "Unstaged changes to %s";
4526                 else
4527                         info = "Unstaged changes";
4528                 break;
4529         }
4530         case LINE_STAT_UNTRACKED:
4531                 if (!newpath) {
4532                         report("No file to show");
4533                         return REQ_NONE;
4534                 }
4536                 if (!suffixcmp(status->new.name, -1, "/")) {
4537                         report("Cannot display a directory");
4538                         return REQ_NONE;
4539                 }
4541                 if (!prepare_update_file(stage, newpath))
4542                         return REQ_QUIT;
4543                 info = "Untracked file %s";
4544                 break;
4546         case LINE_STAT_HEAD:
4547                 return REQ_NONE;
4549         default:
4550                 die("line type %d not handled in switch", line->type);
4551         }
4553         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4554         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4555         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4556                 if (status) {
4557                         stage_status = *status;
4558                 } else {
4559                         memset(&stage_status, 0, sizeof(stage_status));
4560                 }
4562                 stage_line_type = line->type;
4563                 stage_chunks = 0;
4564                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4565         }
4567         return REQ_NONE;
4570 static bool
4571 status_exists(struct status *status, enum line_type type)
4573         struct view *view = VIEW(REQ_VIEW_STATUS);
4574         struct line *line;
4576         for (line = view->line; line < view->line + view->lines; line++) {
4577                 struct status *pos = line->data;
4579                 if (line->type != type)
4580                         continue;
4581                 if (!pos && (!status || !status->status))
4582                         return TRUE;
4583                 if (pos && !strcmp(status->new.name, pos->new.name))
4584                         return TRUE;
4585         }
4587         return FALSE;
4591 static bool
4592 status_update_prepare(struct io *io, enum line_type type)
4594         const char *staged_argv[] = {
4595                 "git", "update-index", "-z", "--index-info", NULL
4596         };
4597         const char *others_argv[] = {
4598                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4599         };
4601         switch (type) {
4602         case LINE_STAT_STAGED:
4603                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4605         case LINE_STAT_UNSTAGED:
4606                 return run_io(io, others_argv, opt_cdup, IO_WR);
4608         case LINE_STAT_UNTRACKED:
4609                 return run_io(io, others_argv, NULL, IO_WR);
4611         default:
4612                 die("line type %d not handled in switch", type);
4613                 return FALSE;
4614         }
4617 static bool
4618 status_update_write(struct io *io, struct status *status, enum line_type type)
4620         char buf[SIZEOF_STR];
4621         size_t bufsize = 0;
4623         switch (type) {
4624         case LINE_STAT_STAGED:
4625                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4626                                         status->old.mode,
4627                                         status->old.rev,
4628                                         status->old.name, 0))
4629                         return FALSE;
4630                 break;
4632         case LINE_STAT_UNSTAGED:
4633         case LINE_STAT_UNTRACKED:
4634                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4635                         return FALSE;
4636                 break;
4638         default:
4639                 die("line type %d not handled in switch", type);
4640         }
4642         return io_write(io, buf, bufsize);
4645 static bool
4646 status_update_file(struct status *status, enum line_type type)
4648         struct io io = {};
4649         bool result;
4651         if (!status_update_prepare(&io, type))
4652                 return FALSE;
4654         result = status_update_write(&io, status, type);
4655         done_io(&io);
4656         return result;
4659 static bool
4660 status_update_files(struct view *view, struct line *line)
4662         struct io io = {};
4663         bool result = TRUE;
4664         struct line *pos = view->line + view->lines;
4665         int files = 0;
4666         int file, done;
4668         if (!status_update_prepare(&io, line->type))
4669                 return FALSE;
4671         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4672                 files++;
4674         for (file = 0, done = 0; result && file < files; line++, file++) {
4675                 int almost_done = file * 100 / files;
4677                 if (almost_done > done) {
4678                         done = almost_done;
4679                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4680                                       file, files, done);
4681                         update_view_title(view);
4682                 }
4683                 result = status_update_write(&io, line->data, line->type);
4684         }
4686         done_io(&io);
4687         return result;
4690 static bool
4691 status_update(struct view *view)
4693         struct line *line = &view->line[view->lineno];
4695         assert(view->lines);
4697         if (!line->data) {
4698                 /* This should work even for the "On branch" line. */
4699                 if (line < view->line + view->lines && !line[1].data) {
4700                         report("Nothing to update");
4701                         return FALSE;
4702                 }
4704                 if (!status_update_files(view, line + 1)) {
4705                         report("Failed to update file status");
4706                         return FALSE;
4707                 }
4709         } else if (!status_update_file(line->data, line->type)) {
4710                 report("Failed to update file status");
4711                 return FALSE;
4712         }
4714         return TRUE;
4717 static bool
4718 status_revert(struct status *status, enum line_type type, bool has_none)
4720         if (!status || type != LINE_STAT_UNSTAGED) {
4721                 if (type == LINE_STAT_STAGED) {
4722                         report("Cannot revert changes to staged files");
4723                 } else if (type == LINE_STAT_UNTRACKED) {
4724                         report("Cannot revert changes to untracked files");
4725                 } else if (has_none) {
4726                         report("Nothing to revert");
4727                 } else {
4728                         report("Cannot revert changes to multiple files");
4729                 }
4730                 return FALSE;
4732         } else {
4733                 const char *checkout_argv[] = {
4734                         "git", "checkout", "--", status->old.name, NULL
4735                 };
4737                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4738                         return FALSE;
4739                 return run_io_fg(checkout_argv, opt_cdup);
4740         }
4743 static enum request
4744 status_request(struct view *view, enum request request, struct line *line)
4746         struct status *status = line->data;
4748         switch (request) {
4749         case REQ_STATUS_UPDATE:
4750                 if (!status_update(view))
4751                         return REQ_NONE;
4752                 break;
4754         case REQ_STATUS_REVERT:
4755                 if (!status_revert(status, line->type, status_has_none(view, line)))
4756                         return REQ_NONE;
4757                 break;
4759         case REQ_STATUS_MERGE:
4760                 if (!status || status->status != 'U') {
4761                         report("Merging only possible for files with unmerged status ('U').");
4762                         return REQ_NONE;
4763                 }
4764                 open_mergetool(status->new.name);
4765                 break;
4767         case REQ_EDIT:
4768                 if (!status)
4769                         return request;
4770                 if (status->status == 'D') {
4771                         report("File has been deleted.");
4772                         return REQ_NONE;
4773                 }
4775                 open_editor(status->status != '?', status->new.name);
4776                 break;
4778         case REQ_VIEW_BLAME:
4779                 if (status) {
4780                         string_copy(opt_file, status->new.name);
4781                         opt_ref[0] = 0;
4782                 }
4783                 return request;
4785         case REQ_ENTER:
4786                 /* After returning the status view has been split to
4787                  * show the stage view. No further reloading is
4788                  * necessary. */
4789                 status_enter(view, line);
4790                 return REQ_NONE;
4792         case REQ_REFRESH:
4793                 /* Simply reload the view. */
4794                 break;
4796         default:
4797                 return request;
4798         }
4800         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4802         return REQ_NONE;
4805 static void
4806 status_select(struct view *view, struct line *line)
4808         struct status *status = line->data;
4809         char file[SIZEOF_STR] = "all files";
4810         const char *text;
4811         const char *key;
4813         if (status && !string_format(file, "'%s'", status->new.name))
4814                 return;
4816         if (!status && line[1].type == LINE_STAT_NONE)
4817                 line++;
4819         switch (line->type) {
4820         case LINE_STAT_STAGED:
4821                 text = "Press %s to unstage %s for commit";
4822                 break;
4824         case LINE_STAT_UNSTAGED:
4825                 text = "Press %s to stage %s for commit";
4826                 break;
4828         case LINE_STAT_UNTRACKED:
4829                 text = "Press %s to stage %s for addition";
4830                 break;
4832         case LINE_STAT_HEAD:
4833         case LINE_STAT_NONE:
4834                 text = "Nothing to update";
4835                 break;
4837         default:
4838                 die("line type %d not handled in switch", line->type);
4839         }
4841         if (status && status->status == 'U') {
4842                 text = "Press %s to resolve conflict in %s";
4843                 key = get_key(REQ_STATUS_MERGE);
4845         } else {
4846                 key = get_key(REQ_STATUS_UPDATE);
4847         }
4849         string_format(view->ref, text, key, file);
4852 static bool
4853 status_grep(struct view *view, struct line *line)
4855         struct status *status = line->data;
4856         enum { S_STATUS, S_NAME, S_END } state;
4857         char buf[2] = "?";
4858         regmatch_t pmatch;
4860         if (!status)
4861                 return FALSE;
4863         for (state = S_STATUS; state < S_END; state++) {
4864                 const char *text;
4866                 switch (state) {
4867                 case S_NAME:    text = status->new.name;        break;
4868                 case S_STATUS:
4869                         buf[0] = status->status;
4870                         text = buf;
4871                         break;
4873                 default:
4874                         return FALSE;
4875                 }
4877                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4878                         return TRUE;
4879         }
4881         return FALSE;
4884 static struct view_ops status_ops = {
4885         "file",
4886         NULL,
4887         status_open,
4888         NULL,
4889         status_draw,
4890         status_request,
4891         status_grep,
4892         status_select,
4893 };
4896 static bool
4897 stage_diff_write(struct io *io, struct line *line, struct line *end)
4899         while (line < end) {
4900                 if (!io_write(io, line->data, strlen(line->data)) ||
4901                     !io_write(io, "\n", 1))
4902                         return FALSE;
4903                 line++;
4904                 if (line->type == LINE_DIFF_CHUNK ||
4905                     line->type == LINE_DIFF_HEADER)
4906                         break;
4907         }
4909         return TRUE;
4912 static struct line *
4913 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4915         for (; view->line < line; line--)
4916                 if (line->type == type)
4917                         return line;
4919         return NULL;
4922 static bool
4923 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4925         const char *apply_argv[SIZEOF_ARG] = {
4926                 "git", "apply", "--whitespace=nowarn", NULL
4927         };
4928         struct line *diff_hdr;
4929         struct io io = {};
4930         int argc = 3;
4932         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4933         if (!diff_hdr)
4934                 return FALSE;
4936         if (!revert)
4937                 apply_argv[argc++] = "--cached";
4938         if (revert || stage_line_type == LINE_STAT_STAGED)
4939                 apply_argv[argc++] = "-R";
4940         apply_argv[argc++] = "-";
4941         apply_argv[argc++] = NULL;
4942         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4943                 return FALSE;
4945         if (!stage_diff_write(&io, diff_hdr, chunk) ||
4946             !stage_diff_write(&io, chunk, view->line + view->lines))
4947                 chunk = NULL;
4949         done_io(&io);
4950         run_io_bg(update_index_argv);
4952         return chunk ? TRUE : FALSE;
4955 static bool
4956 stage_update(struct view *view, struct line *line)
4958         struct line *chunk = NULL;
4960         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4961                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4963         if (chunk) {
4964                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4965                         report("Failed to apply chunk");
4966                         return FALSE;
4967                 }
4969         } else if (!stage_status.status) {
4970                 view = VIEW(REQ_VIEW_STATUS);
4972                 for (line = view->line; line < view->line + view->lines; line++)
4973                         if (line->type == stage_line_type)
4974                                 break;
4976                 if (!status_update_files(view, line + 1)) {
4977                         report("Failed to update files");
4978                         return FALSE;
4979                 }
4981         } else if (!status_update_file(&stage_status, stage_line_type)) {
4982                 report("Failed to update file");
4983                 return FALSE;
4984         }
4986         return TRUE;
4989 static bool
4990 stage_revert(struct view *view, struct line *line)
4992         struct line *chunk = NULL;
4994         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4995                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4997         if (chunk) {
4998                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4999                         return FALSE;
5001                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5002                         report("Failed to revert chunk");
5003                         return FALSE;
5004                 }
5005                 return TRUE;
5007         } else {
5008                 return status_revert(stage_status.status ? &stage_status : NULL,
5009                                      stage_line_type, FALSE);
5010         }
5014 static void
5015 stage_next(struct view *view, struct line *line)
5017         int i;
5019         if (!stage_chunks) {
5020                 static size_t alloc = 0;
5021                 int *tmp;
5023                 for (line = view->line; line < view->line + view->lines; line++) {
5024                         if (line->type != LINE_DIFF_CHUNK)
5025                                 continue;
5027                         tmp = realloc_items(stage_chunk, &alloc,
5028                                             stage_chunks, sizeof(*tmp));
5029                         if (!tmp) {
5030                                 report("Allocation failure");
5031                                 return;
5032                         }
5034                         stage_chunk = tmp;
5035                         stage_chunk[stage_chunks++] = line - view->line;
5036                 }
5037         }
5039         for (i = 0; i < stage_chunks; i++) {
5040                 if (stage_chunk[i] > view->lineno) {
5041                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5042                         report("Chunk %d of %d", i + 1, stage_chunks);
5043                         return;
5044                 }
5045         }
5047         report("No next chunk found");
5050 static enum request
5051 stage_request(struct view *view, enum request request, struct line *line)
5053         switch (request) {
5054         case REQ_STATUS_UPDATE:
5055                 if (!stage_update(view, line))
5056                         return REQ_NONE;
5057                 break;
5059         case REQ_STATUS_REVERT:
5060                 if (!stage_revert(view, line))
5061                         return REQ_NONE;
5062                 break;
5064         case REQ_STAGE_NEXT:
5065                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5066                         report("File is untracked; press %s to add",
5067                                get_key(REQ_STATUS_UPDATE));
5068                         return REQ_NONE;
5069                 }
5070                 stage_next(view, line);
5071                 return REQ_NONE;
5073         case REQ_EDIT:
5074                 if (!stage_status.new.name[0])
5075                         return request;
5076                 if (stage_status.status == 'D') {
5077                         report("File has been deleted.");
5078                         return REQ_NONE;
5079                 }
5081                 open_editor(stage_status.status != '?', stage_status.new.name);
5082                 break;
5084         case REQ_REFRESH:
5085                 /* Reload everything ... */
5086                 break;
5088         case REQ_VIEW_BLAME:
5089                 if (stage_status.new.name[0]) {
5090                         string_copy(opt_file, stage_status.new.name);
5091                         opt_ref[0] = 0;
5092                 }
5093                 return request;
5095         case REQ_ENTER:
5096                 return pager_request(view, request, line);
5098         default:
5099                 return request;
5100         }
5102         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5104         /* Check whether the staged entry still exists, and close the
5105          * stage view if it doesn't. */
5106         if (!status_exists(&stage_status, stage_line_type))
5107                 return REQ_VIEW_CLOSE;
5109         if (stage_line_type == LINE_STAT_UNTRACKED) {
5110                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5111                         report("Cannot display a directory");
5112                         return REQ_NONE;
5113                 }
5115                 if (!prepare_update_file(view, stage_status.new.name)) {
5116                         report("Failed to open file: %s", strerror(errno));
5117                         return REQ_NONE;
5118                 }
5119         }
5120         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5122         return REQ_NONE;
5125 static struct view_ops stage_ops = {
5126         "line",
5127         NULL,
5128         NULL,
5129         pager_read,
5130         pager_draw,
5131         stage_request,
5132         pager_grep,
5133         pager_select,
5134 };
5137 /*
5138  * Revision graph
5139  */
5141 struct commit {
5142         char id[SIZEOF_REV];            /* SHA1 ID. */
5143         char title[128];                /* First line of the commit message. */
5144         char author[75];                /* Author of the commit. */
5145         struct tm time;                 /* Date from the author ident. */
5146         struct ref **refs;              /* Repository references. */
5147         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5148         size_t graph_size;              /* The width of the graph array. */
5149         bool has_parents;               /* Rewritten --parents seen. */
5150 };
5152 /* Size of rev graph with no  "padding" columns */
5153 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5155 struct rev_graph {
5156         struct rev_graph *prev, *next, *parents;
5157         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5158         size_t size;
5159         struct commit *commit;
5160         size_t pos;
5161         unsigned int boundary:1;
5162 };
5164 /* Parents of the commit being visualized. */
5165 static struct rev_graph graph_parents[4];
5167 /* The current stack of revisions on the graph. */
5168 static struct rev_graph graph_stacks[4] = {
5169         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5170         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5171         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5172         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5173 };
5175 static inline bool
5176 graph_parent_is_merge(struct rev_graph *graph)
5178         return graph->parents->size > 1;
5181 static inline void
5182 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5184         struct commit *commit = graph->commit;
5186         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5187                 commit->graph[commit->graph_size++] = symbol;
5190 static void
5191 clear_rev_graph(struct rev_graph *graph)
5193         graph->boundary = 0;
5194         graph->size = graph->pos = 0;
5195         graph->commit = NULL;
5196         memset(graph->parents, 0, sizeof(*graph->parents));
5199 static void
5200 done_rev_graph(struct rev_graph *graph)
5202         if (graph_parent_is_merge(graph) &&
5203             graph->pos < graph->size - 1 &&
5204             graph->next->size == graph->size + graph->parents->size - 1) {
5205                 size_t i = graph->pos + graph->parents->size - 1;
5207                 graph->commit->graph_size = i * 2;
5208                 while (i < graph->next->size - 1) {
5209                         append_to_rev_graph(graph, ' ');
5210                         append_to_rev_graph(graph, '\\');
5211                         i++;
5212                 }
5213         }
5215         clear_rev_graph(graph);
5218 static void
5219 push_rev_graph(struct rev_graph *graph, const char *parent)
5221         int i;
5223         /* "Collapse" duplicate parents lines.
5224          *
5225          * FIXME: This needs to also update update the drawn graph but
5226          * for now it just serves as a method for pruning graph lines. */
5227         for (i = 0; i < graph->size; i++)
5228                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5229                         return;
5231         if (graph->size < SIZEOF_REVITEMS) {
5232                 string_copy_rev(graph->rev[graph->size++], parent);
5233         }
5236 static chtype
5237 get_rev_graph_symbol(struct rev_graph *graph)
5239         chtype symbol;
5241         if (graph->boundary)
5242                 symbol = REVGRAPH_BOUND;
5243         else if (graph->parents->size == 0)
5244                 symbol = REVGRAPH_INIT;
5245         else if (graph_parent_is_merge(graph))
5246                 symbol = REVGRAPH_MERGE;
5247         else if (graph->pos >= graph->size)
5248                 symbol = REVGRAPH_BRANCH;
5249         else
5250                 symbol = REVGRAPH_COMMIT;
5252         return symbol;
5255 static void
5256 draw_rev_graph(struct rev_graph *graph)
5258         struct rev_filler {
5259                 chtype separator, line;
5260         };
5261         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5262         static struct rev_filler fillers[] = {
5263                 { ' ',  '|' },
5264                 { '`',  '.' },
5265                 { '\'', ' ' },
5266                 { '/',  ' ' },
5267         };
5268         chtype symbol = get_rev_graph_symbol(graph);
5269         struct rev_filler *filler;
5270         size_t i;
5272         if (opt_line_graphics)
5273                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5275         filler = &fillers[DEFAULT];
5277         for (i = 0; i < graph->pos; i++) {
5278                 append_to_rev_graph(graph, filler->line);
5279                 if (graph_parent_is_merge(graph->prev) &&
5280                     graph->prev->pos == i)
5281                         filler = &fillers[RSHARP];
5283                 append_to_rev_graph(graph, filler->separator);
5284         }
5286         /* Place the symbol for this revision. */
5287         append_to_rev_graph(graph, symbol);
5289         if (graph->prev->size > graph->size)
5290                 filler = &fillers[RDIAG];
5291         else
5292                 filler = &fillers[DEFAULT];
5294         i++;
5296         for (; i < graph->size; i++) {
5297                 append_to_rev_graph(graph, filler->separator);
5298                 append_to_rev_graph(graph, filler->line);
5299                 if (graph_parent_is_merge(graph->prev) &&
5300                     i < graph->prev->pos + graph->parents->size)
5301                         filler = &fillers[RSHARP];
5302                 if (graph->prev->size > graph->size)
5303                         filler = &fillers[LDIAG];
5304         }
5306         if (graph->prev->size > graph->size) {
5307                 append_to_rev_graph(graph, filler->separator);
5308                 if (filler->line != ' ')
5309                         append_to_rev_graph(graph, filler->line);
5310         }
5313 /* Prepare the next rev graph */
5314 static void
5315 prepare_rev_graph(struct rev_graph *graph)
5317         size_t i;
5319         /* First, traverse all lines of revisions up to the active one. */
5320         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5321                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5322                         break;
5324                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5325         }
5327         /* Interleave the new revision parent(s). */
5328         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5329                 push_rev_graph(graph->next, graph->parents->rev[i]);
5331         /* Lastly, put any remaining revisions. */
5332         for (i = graph->pos + 1; i < graph->size; i++)
5333                 push_rev_graph(graph->next, graph->rev[i]);
5336 static void
5337 update_rev_graph(struct view *view, struct rev_graph *graph)
5339         /* If this is the finalizing update ... */
5340         if (graph->commit)
5341                 prepare_rev_graph(graph);
5343         /* Graph visualization needs a one rev look-ahead,
5344          * so the first update doesn't visualize anything. */
5345         if (!graph->prev->commit)
5346                 return;
5348         if (view->lines > 2)
5349                 view->line[view->lines - 3].dirty = 1;
5350         if (view->lines > 1)
5351                 view->line[view->lines - 2].dirty = 1;
5352         draw_rev_graph(graph->prev);
5353         done_rev_graph(graph->prev->prev);
5357 /*
5358  * Main view backend
5359  */
5361 static const char *main_argv[SIZEOF_ARG] = {
5362         "git", "log", "--no-color", "--pretty=raw", "--parents",
5363                       "--topo-order", "%(head)", NULL
5364 };
5366 static bool
5367 main_draw(struct view *view, struct line *line, unsigned int lineno)
5369         struct commit *commit = line->data;
5371         if (!*commit->author)
5372                 return FALSE;
5374         if (opt_date && draw_date(view, &commit->time))
5375                 return TRUE;
5377         if (opt_author &&
5378             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5379                 return TRUE;
5381         if (opt_rev_graph && commit->graph_size &&
5382             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5383                 return TRUE;
5385         if (opt_show_refs && commit->refs) {
5386                 size_t i = 0;
5388                 do {
5389                         enum line_type type;
5391                         if (commit->refs[i]->head)
5392                                 type = LINE_MAIN_HEAD;
5393                         else if (commit->refs[i]->ltag)
5394                                 type = LINE_MAIN_LOCAL_TAG;
5395                         else if (commit->refs[i]->tag)
5396                                 type = LINE_MAIN_TAG;
5397                         else if (commit->refs[i]->tracked)
5398                                 type = LINE_MAIN_TRACKED;
5399                         else if (commit->refs[i]->remote)
5400                                 type = LINE_MAIN_REMOTE;
5401                         else
5402                                 type = LINE_MAIN_REF;
5404                         if (draw_text(view, type, "[", TRUE) ||
5405                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5406                             draw_text(view, type, "]", TRUE))
5407                                 return TRUE;
5409                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5410                                 return TRUE;
5411                 } while (commit->refs[i++]->next);
5412         }
5414         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5415         return TRUE;
5418 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5419 static bool
5420 main_read(struct view *view, char *line)
5422         static struct rev_graph *graph = graph_stacks;
5423         enum line_type type;
5424         struct commit *commit;
5426         if (!line) {
5427                 int i;
5429                 if (!view->lines && !view->parent)
5430                         die("No revisions match the given arguments.");
5431                 if (view->lines > 0) {
5432                         commit = view->line[view->lines - 1].data;
5433                         view->line[view->lines - 1].dirty = 1;
5434                         if (!*commit->author) {
5435                                 view->lines--;
5436                                 free(commit);
5437                                 graph->commit = NULL;
5438                         }
5439                 }
5440                 update_rev_graph(view, graph);
5442                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5443                         clear_rev_graph(&graph_stacks[i]);
5444                 return TRUE;
5445         }
5447         type = get_line_type(line);
5448         if (type == LINE_COMMIT) {
5449                 commit = calloc(1, sizeof(struct commit));
5450                 if (!commit)
5451                         return FALSE;
5453                 line += STRING_SIZE("commit ");
5454                 if (*line == '-') {
5455                         graph->boundary = 1;
5456                         line++;
5457                 }
5459                 string_copy_rev(commit->id, line);
5460                 commit->refs = get_refs(commit->id);
5461                 graph->commit = commit;
5462                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5464                 while ((line = strchr(line, ' '))) {
5465                         line++;
5466                         push_rev_graph(graph->parents, line);
5467                         commit->has_parents = TRUE;
5468                 }
5469                 return TRUE;
5470         }
5472         if (!view->lines)
5473                 return TRUE;
5474         commit = view->line[view->lines - 1].data;
5476         switch (type) {
5477         case LINE_PARENT:
5478                 if (commit->has_parents)
5479                         break;
5480                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5481                 break;
5483         case LINE_AUTHOR:
5484         {
5485                 /* Parse author lines where the name may be empty:
5486                  *      author  <email@address.tld> 1138474660 +0100
5487                  */
5488                 char *ident = line + STRING_SIZE("author ");
5489                 char *nameend = strchr(ident, '<');
5490                 char *emailend = strchr(ident, '>');
5492                 if (!nameend || !emailend)
5493                         break;
5495                 update_rev_graph(view, graph);
5496                 graph = graph->next;
5498                 *nameend = *emailend = 0;
5499                 ident = chomp_string(ident);
5500                 if (!*ident) {
5501                         ident = chomp_string(nameend + 1);
5502                         if (!*ident)
5503                                 ident = "Unknown";
5504                 }
5506                 string_ncopy(commit->author, ident, strlen(ident));
5507                 view->line[view->lines - 1].dirty = 1;
5509                 /* Parse epoch and timezone */
5510                 if (emailend[1] == ' ') {
5511                         char *secs = emailend + 2;
5512                         char *zone = strchr(secs, ' ');
5513                         time_t time = (time_t) atol(secs);
5515                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5516                                 long tz;
5518                                 zone++;
5519                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5520                                 tz += ('0' - zone[2]) * 60 * 60;
5521                                 tz += ('0' - zone[3]) * 60;
5522                                 tz += ('0' - zone[4]) * 60;
5524                                 if (zone[0] == '-')
5525                                         tz = -tz;
5527                                 time -= tz;
5528                         }
5530                         gmtime_r(&time, &commit->time);
5531                 }
5532                 break;
5533         }
5534         default:
5535                 /* Fill in the commit title if it has not already been set. */
5536                 if (commit->title[0])
5537                         break;
5539                 /* Require titles to start with a non-space character at the
5540                  * offset used by git log. */
5541                 if (strncmp(line, "    ", 4))
5542                         break;
5543                 line += 4;
5544                 /* Well, if the title starts with a whitespace character,
5545                  * try to be forgiving.  Otherwise we end up with no title. */
5546                 while (isspace(*line))
5547                         line++;
5548                 if (*line == '\0')
5549                         break;
5550                 /* FIXME: More graceful handling of titles; append "..." to
5551                  * shortened titles, etc. */
5553                 string_ncopy(commit->title, line, strlen(line));
5554                 view->line[view->lines - 1].dirty = 1;
5555         }
5557         return TRUE;
5560 static enum request
5561 main_request(struct view *view, enum request request, struct line *line)
5563         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5565         switch (request) {
5566         case REQ_ENTER:
5567                 open_view(view, REQ_VIEW_DIFF, flags);
5568                 break;
5569         case REQ_REFRESH:
5570                 load_refs();
5571                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5572                 break;
5573         default:
5574                 return request;
5575         }
5577         return REQ_NONE;
5580 static bool
5581 grep_refs(struct ref **refs, regex_t *regex)
5583         regmatch_t pmatch;
5584         size_t i = 0;
5586         if (!refs)
5587                 return FALSE;
5588         do {
5589                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5590                         return TRUE;
5591         } while (refs[i++]->next);
5593         return FALSE;
5596 static bool
5597 main_grep(struct view *view, struct line *line)
5599         struct commit *commit = line->data;
5600         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5601         char buf[DATE_COLS + 1];
5602         regmatch_t pmatch;
5604         for (state = S_TITLE; state < S_END; state++) {
5605                 char *text;
5607                 switch (state) {
5608                 case S_TITLE:   text = commit->title;   break;
5609                 case S_AUTHOR:
5610                         if (!opt_author)
5611                                 continue;
5612                         text = commit->author;
5613                         break;
5614                 case S_DATE:
5615                         if (!opt_date)
5616                                 continue;
5617                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5618                                 continue;
5619                         text = buf;
5620                         break;
5621                 case S_REFS:
5622                         if (!opt_show_refs)
5623                                 continue;
5624                         if (grep_refs(commit->refs, view->regex) == TRUE)
5625                                 return TRUE;
5626                         continue;
5627                 default:
5628                         return FALSE;
5629                 }
5631                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5632                         return TRUE;
5633         }
5635         return FALSE;
5638 static void
5639 main_select(struct view *view, struct line *line)
5641         struct commit *commit = line->data;
5643         string_copy_rev(view->ref, commit->id);
5644         string_copy_rev(ref_commit, view->ref);
5647 static struct view_ops main_ops = {
5648         "commit",
5649         main_argv,
5650         NULL,
5651         main_read,
5652         main_draw,
5653         main_request,
5654         main_grep,
5655         main_select,
5656 };
5659 /*
5660  * Unicode / UTF-8 handling
5661  *
5662  * NOTE: Much of the following code for dealing with unicode is derived from
5663  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5664  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5665  */
5667 /* I've (over)annotated a lot of code snippets because I am not entirely
5668  * confident that the approach taken by this small UTF-8 interface is correct.
5669  * --jonas */
5671 static inline int
5672 unicode_width(unsigned long c)
5674         if (c >= 0x1100 &&
5675            (c <= 0x115f                         /* Hangul Jamo */
5676             || c == 0x2329
5677             || c == 0x232a
5678             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5679                                                 /* CJK ... Yi */
5680             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5681             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5682             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5683             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5684             || (c >= 0xffe0  && c <= 0xffe6)
5685             || (c >= 0x20000 && c <= 0x2fffd)
5686             || (c >= 0x30000 && c <= 0x3fffd)))
5687                 return 2;
5689         if (c == '\t')
5690                 return opt_tab_size;
5692         return 1;
5695 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5696  * Illegal bytes are set one. */
5697 static const unsigned char utf8_bytes[256] = {
5698         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,
5699         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,
5700         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,
5701         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,
5702         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,
5703         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,
5704         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,
5705         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,
5706 };
5708 /* Decode UTF-8 multi-byte representation into a unicode character. */
5709 static inline unsigned long
5710 utf8_to_unicode(const char *string, size_t length)
5712         unsigned long unicode;
5714         switch (length) {
5715         case 1:
5716                 unicode  =   string[0];
5717                 break;
5718         case 2:
5719                 unicode  =  (string[0] & 0x1f) << 6;
5720                 unicode +=  (string[1] & 0x3f);
5721                 break;
5722         case 3:
5723                 unicode  =  (string[0] & 0x0f) << 12;
5724                 unicode += ((string[1] & 0x3f) << 6);
5725                 unicode +=  (string[2] & 0x3f);
5726                 break;
5727         case 4:
5728                 unicode  =  (string[0] & 0x0f) << 18;
5729                 unicode += ((string[1] & 0x3f) << 12);
5730                 unicode += ((string[2] & 0x3f) << 6);
5731                 unicode +=  (string[3] & 0x3f);
5732                 break;
5733         case 5:
5734                 unicode  =  (string[0] & 0x0f) << 24;
5735                 unicode += ((string[1] & 0x3f) << 18);
5736                 unicode += ((string[2] & 0x3f) << 12);
5737                 unicode += ((string[3] & 0x3f) << 6);
5738                 unicode +=  (string[4] & 0x3f);
5739                 break;
5740         case 6:
5741                 unicode  =  (string[0] & 0x01) << 30;
5742                 unicode += ((string[1] & 0x3f) << 24);
5743                 unicode += ((string[2] & 0x3f) << 18);
5744                 unicode += ((string[3] & 0x3f) << 12);
5745                 unicode += ((string[4] & 0x3f) << 6);
5746                 unicode +=  (string[5] & 0x3f);
5747                 break;
5748         default:
5749                 die("Invalid unicode length");
5750         }
5752         /* Invalid characters could return the special 0xfffd value but NUL
5753          * should be just as good. */
5754         return unicode > 0xffff ? 0 : unicode;
5757 /* Calculates how much of string can be shown within the given maximum width
5758  * and sets trimmed parameter to non-zero value if all of string could not be
5759  * shown. If the reserve flag is TRUE, it will reserve at least one
5760  * trailing character, which can be useful when drawing a delimiter.
5761  *
5762  * Returns the number of bytes to output from string to satisfy max_width. */
5763 static size_t
5764 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5766         const char *start = string;
5767         const char *end = strchr(string, '\0');
5768         unsigned char last_bytes = 0;
5769         size_t last_ucwidth = 0;
5771         *width = 0;
5772         *trimmed = 0;
5774         while (string < end) {
5775                 int c = *(unsigned char *) string;
5776                 unsigned char bytes = utf8_bytes[c];
5777                 size_t ucwidth;
5778                 unsigned long unicode;
5780                 if (string + bytes > end)
5781                         break;
5783                 /* Change representation to figure out whether
5784                  * it is a single- or double-width character. */
5786                 unicode = utf8_to_unicode(string, bytes);
5787                 /* FIXME: Graceful handling of invalid unicode character. */
5788                 if (!unicode)
5789                         break;
5791                 ucwidth = unicode_width(unicode);
5792                 *width  += ucwidth;
5793                 if (*width > max_width) {
5794                         *trimmed = 1;
5795                         *width -= ucwidth;
5796                         if (reserve && *width == max_width) {
5797                                 string -= last_bytes;
5798                                 *width -= last_ucwidth;
5799                         }
5800                         break;
5801                 }
5803                 string  += bytes;
5804                 last_bytes = bytes;
5805                 last_ucwidth = ucwidth;
5806         }
5808         return string - start;
5812 /*
5813  * Status management
5814  */
5816 /* Whether or not the curses interface has been initialized. */
5817 static bool cursed = FALSE;
5819 /* The status window is used for polling keystrokes. */
5820 static WINDOW *status_win;
5822 static bool status_empty = TRUE;
5824 /* Update status and title window. */
5825 static void
5826 report(const char *msg, ...)
5828         struct view *view = display[current_view];
5830         if (input_mode)
5831                 return;
5833         if (!view) {
5834                 char buf[SIZEOF_STR];
5835                 va_list args;
5837                 va_start(args, msg);
5838                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5839                         buf[sizeof(buf) - 1] = 0;
5840                         buf[sizeof(buf) - 2] = '.';
5841                         buf[sizeof(buf) - 3] = '.';
5842                         buf[sizeof(buf) - 4] = '.';
5843                 }
5844                 va_end(args);
5845                 die("%s", buf);
5846         }
5848         if (!status_empty || *msg) {
5849                 va_list args;
5851                 va_start(args, msg);
5853                 wmove(status_win, 0, 0);
5854                 if (*msg) {
5855                         vwprintw(status_win, msg, args);
5856                         status_empty = FALSE;
5857                 } else {
5858                         status_empty = TRUE;
5859                 }
5860                 wclrtoeol(status_win);
5861                 wrefresh(status_win);
5863                 va_end(args);
5864         }
5866         update_view_title(view);
5867         update_display_cursor(view);
5870 /* Controls when nodelay should be in effect when polling user input. */
5871 static void
5872 set_nonblocking_input(bool loading)
5874         static unsigned int loading_views;
5876         if ((loading == FALSE && loading_views-- == 1) ||
5877             (loading == TRUE  && loading_views++ == 0))
5878                 nodelay(status_win, loading);
5881 static void
5882 init_display(void)
5884         int x, y;
5886         /* Initialize the curses library */
5887         if (isatty(STDIN_FILENO)) {
5888                 cursed = !!initscr();
5889                 opt_tty = stdin;
5890         } else {
5891                 /* Leave stdin and stdout alone when acting as a pager. */
5892                 opt_tty = fopen("/dev/tty", "r+");
5893                 if (!opt_tty)
5894                         die("Failed to open /dev/tty");
5895                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5896         }
5898         if (!cursed)
5899                 die("Failed to initialize curses");
5901         nonl();         /* Tell curses not to do NL->CR/NL on output */
5902         cbreak();       /* Take input chars one at a time, no wait for \n */
5903         noecho();       /* Don't echo input */
5904         leaveok(stdscr, TRUE);
5906         if (has_colors())
5907                 init_colors();
5909         getmaxyx(stdscr, y, x);
5910         status_win = newwin(1, 0, y - 1, 0);
5911         if (!status_win)
5912                 die("Failed to create status window");
5914         /* Enable keyboard mapping */
5915         keypad(status_win, TRUE);
5916         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5918         TABSIZE = opt_tab_size;
5919         if (opt_line_graphics) {
5920                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5921         }
5924 static int
5925 get_input(bool prompting)
5927         struct view *view;
5928         int i, key;
5930         if (prompting)
5931                 input_mode = TRUE;
5933         while (true) {
5934                 foreach_view (view, i)
5935                         update_view(view);
5937                 /* Refresh, accept single keystroke of input */
5938                 key = wgetch(status_win);
5940                 /* wgetch() with nodelay() enabled returns ERR when
5941                  * there's no input. */
5942                 if (key == ERR) {
5943                         doupdate();
5945                 } else if (key == KEY_RESIZE) {
5946                         int height, width;
5948                         getmaxyx(stdscr, height, width);
5950                         /* Resize the status view and let the view driver take
5951                          * care of resizing the displayed views. */
5952                         resize_display();
5953                         redraw_display(TRUE);
5954                         wresize(status_win, 1, width);
5955                         mvwin(status_win, height - 1, 0);
5956                         wrefresh(status_win);
5958                 } else {
5959                         input_mode = FALSE;
5960                         return key;
5961                 }
5962         }
5965 static bool
5966 prompt_yesno(const char *prompt)
5968         enum { WAIT, STOP, CANCEL  } status = WAIT;
5969         bool answer = FALSE;
5971         while (status == WAIT) {
5972                 int key;
5974                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5975                 wclrtoeol(status_win);
5977                 key = get_input(TRUE);
5978                 switch (key) {
5979                 case 'y':
5980                 case 'Y':
5981                         answer = TRUE;
5982                         status = STOP;
5983                         break;
5985                 case KEY_ESC:
5986                 case KEY_RETURN:
5987                 case KEY_ENTER:
5988                 case KEY_BACKSPACE:
5989                 case 'n':
5990                 case 'N':
5991                 case '\n':
5992                 default:
5993                         answer = FALSE;
5994                         status = CANCEL;
5995                 }
5996         }
5998         /* Clear the status window */
5999         status_empty = FALSE;
6000         report("");
6002         return answer;
6005 static char *
6006 read_prompt(const char *prompt)
6008         enum { READING, STOP, CANCEL } status = READING;
6009         static char buf[SIZEOF_STR];
6010         int pos = 0;
6012         while (status == READING) {
6013                 int key;
6015                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6016                 wclrtoeol(status_win);
6018                 key = get_input(TRUE);
6019                 switch (key) {
6020                 case KEY_RETURN:
6021                 case KEY_ENTER:
6022                 case '\n':
6023                         status = pos ? STOP : CANCEL;
6024                         break;
6026                 case KEY_BACKSPACE:
6027                         if (pos > 0)
6028                                 pos--;
6029                         else
6030                                 status = CANCEL;
6031                         break;
6033                 case KEY_ESC:
6034                         status = CANCEL;
6035                         break;
6037                 default:
6038                         if (pos >= sizeof(buf)) {
6039                                 report("Input string too long");
6040                                 return NULL;
6041                         }
6043                         if (isprint(key))
6044                                 buf[pos++] = (char) key;
6045                 }
6046         }
6048         /* Clear the status window */
6049         status_empty = FALSE;
6050         report("");
6052         if (status == CANCEL)
6053                 return NULL;
6055         buf[pos++] = 0;
6057         return buf;
6060 /*
6061  * Repository properties
6062  */
6064 static int
6065 git_properties(const char **argv, const char *separators,
6066                int (*read_property)(char *, size_t, char *, size_t))
6068         struct io io = {};
6070         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6071                 return read_properties(&io, separators, read_property);
6072         return ERR;
6075 static struct ref *refs = NULL;
6076 static size_t refs_alloc = 0;
6077 static size_t refs_size = 0;
6079 /* Id <-> ref store */
6080 static struct ref ***id_refs = NULL;
6081 static size_t id_refs_alloc = 0;
6082 static size_t id_refs_size = 0;
6084 static int
6085 compare_refs(const void *ref1_, const void *ref2_)
6087         const struct ref *ref1 = *(const struct ref **)ref1_;
6088         const struct ref *ref2 = *(const struct ref **)ref2_;
6090         if (ref1->tag != ref2->tag)
6091                 return ref2->tag - ref1->tag;
6092         if (ref1->ltag != ref2->ltag)
6093                 return ref2->ltag - ref2->ltag;
6094         if (ref1->head != ref2->head)
6095                 return ref2->head - ref1->head;
6096         if (ref1->tracked != ref2->tracked)
6097                 return ref2->tracked - ref1->tracked;
6098         if (ref1->remote != ref2->remote)
6099                 return ref2->remote - ref1->remote;
6100         return strcmp(ref1->name, ref2->name);
6103 static struct ref **
6104 get_refs(const char *id)
6106         struct ref ***tmp_id_refs;
6107         struct ref **ref_list = NULL;
6108         size_t ref_list_alloc = 0;
6109         size_t ref_list_size = 0;
6110         size_t i;
6112         for (i = 0; i < id_refs_size; i++)
6113                 if (!strcmp(id, id_refs[i][0]->id))
6114                         return id_refs[i];
6116         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6117                                     sizeof(*id_refs));
6118         if (!tmp_id_refs)
6119                 return NULL;
6121         id_refs = tmp_id_refs;
6123         for (i = 0; i < refs_size; i++) {
6124                 struct ref **tmp;
6126                 if (strcmp(id, refs[i].id))
6127                         continue;
6129                 tmp = realloc_items(ref_list, &ref_list_alloc,
6130                                     ref_list_size + 1, sizeof(*ref_list));
6131                 if (!tmp) {
6132                         if (ref_list)
6133                                 free(ref_list);
6134                         return NULL;
6135                 }
6137                 ref_list = tmp;
6138                 ref_list[ref_list_size] = &refs[i];
6139                 /* XXX: The properties of the commit chains ensures that we can
6140                  * safely modify the shared ref. The repo references will
6141                  * always be similar for the same id. */
6142                 ref_list[ref_list_size]->next = 1;
6144                 ref_list_size++;
6145         }
6147         if (ref_list) {
6148                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6149                 ref_list[ref_list_size - 1]->next = 0;
6150                 id_refs[id_refs_size++] = ref_list;
6151         }
6153         return ref_list;
6156 static int
6157 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6159         struct ref *ref;
6160         bool tag = FALSE;
6161         bool ltag = FALSE;
6162         bool remote = FALSE;
6163         bool tracked = FALSE;
6164         bool check_replace = FALSE;
6165         bool head = FALSE;
6167         if (!prefixcmp(name, "refs/tags/")) {
6168                 if (!suffixcmp(name, namelen, "^{}")) {
6169                         namelen -= 3;
6170                         name[namelen] = 0;
6171                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6172                                 check_replace = TRUE;
6173                 } else {
6174                         ltag = TRUE;
6175                 }
6177                 tag = TRUE;
6178                 namelen -= STRING_SIZE("refs/tags/");
6179                 name    += STRING_SIZE("refs/tags/");
6181         } else if (!prefixcmp(name, "refs/remotes/")) {
6182                 remote = TRUE;
6183                 namelen -= STRING_SIZE("refs/remotes/");
6184                 name    += STRING_SIZE("refs/remotes/");
6185                 tracked  = !strcmp(opt_remote, name);
6187         } else if (!prefixcmp(name, "refs/heads/")) {
6188                 namelen -= STRING_SIZE("refs/heads/");
6189                 name    += STRING_SIZE("refs/heads/");
6190                 head     = !strncmp(opt_head, name, namelen);
6192         } else if (!strcmp(name, "HEAD")) {
6193                 string_ncopy(opt_head_rev, id, idlen);
6194                 return OK;
6195         }
6197         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6198                 /* it's an annotated tag, replace the previous sha1 with the
6199                  * resolved commit id; relies on the fact git-ls-remote lists
6200                  * the commit id of an annotated tag right before the commit id
6201                  * it points to. */
6202                 refs[refs_size - 1].ltag = ltag;
6203                 string_copy_rev(refs[refs_size - 1].id, id);
6205                 return OK;
6206         }
6207         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6208         if (!refs)
6209                 return ERR;
6211         ref = &refs[refs_size++];
6212         ref->name = malloc(namelen + 1);
6213         if (!ref->name)
6214                 return ERR;
6216         strncpy(ref->name, name, namelen);
6217         ref->name[namelen] = 0;
6218         ref->head = head;
6219         ref->tag = tag;
6220         ref->ltag = ltag;
6221         ref->remote = remote;
6222         ref->tracked = tracked;
6223         string_copy_rev(ref->id, id);
6225         return OK;
6228 static int
6229 load_refs(void)
6231         static const char *ls_remote_argv[SIZEOF_ARG] = {
6232                 "git", "ls-remote", ".", NULL
6233         };
6234         static bool init = FALSE;
6236         if (!init) {
6237                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6238                 init = TRUE;
6239         }
6241         if (!*opt_git_dir)
6242                 return OK;
6244         while (refs_size > 0)
6245                 free(refs[--refs_size].name);
6246         while (id_refs_size > 0)
6247                 free(id_refs[--id_refs_size]);
6249         return git_properties(ls_remote_argv, "\t", read_ref);
6252 static int
6253 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6255         if (!strcmp(name, "i18n.commitencoding"))
6256                 string_ncopy(opt_encoding, value, valuelen);
6258         if (!strcmp(name, "core.editor"))
6259                 string_ncopy(opt_editor, value, valuelen);
6261         /* branch.<head>.remote */
6262         if (*opt_head &&
6263             !strncmp(name, "branch.", 7) &&
6264             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6265             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6266                 string_ncopy(opt_remote, value, valuelen);
6268         if (*opt_head && *opt_remote &&
6269             !strncmp(name, "branch.", 7) &&
6270             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6271             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6272                 size_t from = strlen(opt_remote);
6274                 if (!prefixcmp(value, "refs/heads/")) {
6275                         value += STRING_SIZE("refs/heads/");
6276                         valuelen -= STRING_SIZE("refs/heads/");
6277                 }
6279                 if (!string_format_from(opt_remote, &from, "/%s", value))
6280                         opt_remote[0] = 0;
6281         }
6283         return OK;
6286 static int
6287 load_git_config(void)
6289         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6291         return git_properties(config_list_argv, "=", read_repo_config_option);
6294 static int
6295 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6297         if (!opt_git_dir[0]) {
6298                 string_ncopy(opt_git_dir, name, namelen);
6300         } else if (opt_is_inside_work_tree == -1) {
6301                 /* This can be 3 different values depending on the
6302                  * version of git being used. If git-rev-parse does not
6303                  * understand --is-inside-work-tree it will simply echo
6304                  * the option else either "true" or "false" is printed.
6305                  * Default to true for the unknown case. */
6306                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6307         } else {
6308                 string_ncopy(opt_cdup, name, namelen);
6309         }
6311         return OK;
6314 static int
6315 load_repo_info(void)
6317         const char *head_argv[] = {
6318                 "git", "symbolic-ref", "HEAD", NULL
6319         };
6320         const char *rev_parse_argv[] = {
6321                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6322                         "--show-cdup", NULL
6323         };
6325         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6326                 chomp_string(opt_head);
6327                 if (!prefixcmp(opt_head, "refs/heads/")) {
6328                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6330                         memmove(opt_head, offset, strlen(offset) + 1);
6331                 }
6332         }
6334         return git_properties(rev_parse_argv, "=", read_repo_info);
6337 static int
6338 read_properties(struct io *io, const char *separators,
6339                 int (*read_property)(char *, size_t, char *, size_t))
6341         char *name;
6342         int state = OK;
6344         if (!start_io(io))
6345                 return ERR;
6347         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6348                 char *value;
6349                 size_t namelen;
6350                 size_t valuelen;
6352                 name = chomp_string(name);
6353                 namelen = strcspn(name, separators);
6355                 if (name[namelen]) {
6356                         name[namelen] = 0;
6357                         value = chomp_string(name + namelen + 1);
6358                         valuelen = strlen(value);
6360                 } else {
6361                         value = "";
6362                         valuelen = 0;
6363                 }
6365                 state = read_property(name, namelen, value, valuelen);
6366         }
6368         if (state != ERR && io_error(io))
6369                 state = ERR;
6370         done_io(io);
6372         return state;
6376 /*
6377  * Main
6378  */
6380 static void __NORETURN
6381 quit(int sig)
6383         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6384         if (cursed)
6385                 endwin();
6386         exit(0);
6389 static void __NORETURN
6390 die(const char *err, ...)
6392         va_list args;
6394         endwin();
6396         va_start(args, err);
6397         fputs("tig: ", stderr);
6398         vfprintf(stderr, err, args);
6399         fputs("\n", stderr);
6400         va_end(args);
6402         exit(1);
6405 static void
6406 warn(const char *msg, ...)
6408         va_list args;
6410         va_start(args, msg);
6411         fputs("tig warning: ", stderr);
6412         vfprintf(stderr, msg, args);
6413         fputs("\n", stderr);
6414         va_end(args);
6417 int
6418 main(int argc, const char *argv[])
6420         const char **run_argv = NULL;
6421         struct view *view;
6422         enum request request;
6423         size_t i;
6425         signal(SIGINT, quit);
6427         if (setlocale(LC_ALL, "")) {
6428                 char *codeset = nl_langinfo(CODESET);
6430                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6431         }
6433         if (load_repo_info() == ERR)
6434                 die("Failed to load repo info.");
6436         if (load_options() == ERR)
6437                 die("Failed to load user config.");
6439         if (load_git_config() == ERR)
6440                 die("Failed to load repo config.");
6442         request = parse_options(argc, argv, &run_argv);
6443         if (request == REQ_NONE)
6444                 return 0;
6446         /* Require a git repository unless when running in pager mode. */
6447         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6448                 die("Not a git repository");
6450         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6451                 opt_utf8 = FALSE;
6453         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6454                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6455                 if (opt_iconv == ICONV_NONE)
6456                         die("Failed to initialize character set conversion");
6457         }
6459         if (load_refs() == ERR)
6460                 die("Failed to load refs.");
6462         foreach_view (view, i)
6463                 argv_from_env(view->ops->argv, view->cmd_env);
6465         init_display();
6467         if (request == REQ_VIEW_PAGER || run_argv) {
6468                 if (request == REQ_VIEW_PAGER)
6469                         io_open(&VIEW(request)->io, "");
6470                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6471                         die("Failed to format arguments");
6472                 open_view(NULL, request, OPEN_PREPARED);
6473                 request = REQ_NONE;
6474         }
6476         while (view_driver(display[current_view], request)) {
6477                 int key = get_input(FALSE);
6479                 view = display[current_view];
6480                 request = get_keybinding(view->keymap, key);
6482                 /* Some low-level request handling. This keeps access to
6483                  * status_win restricted. */
6484                 switch (request) {
6485                 case REQ_PROMPT:
6486                 {
6487                         char *cmd = read_prompt(":");
6489                         if (cmd) {
6490                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6491                                 const char *argv[SIZEOF_ARG] = { "git" };
6492                                 int argc = 1;
6494                                 /* When running random commands, initially show the
6495                                  * command in the title. However, it maybe later be
6496                                  * overwritten if a commit line is selected. */
6497                                 string_ncopy(next->ref, cmd, strlen(cmd));
6499                                 if (!argv_from_string(argv, &argc, cmd)) {
6500                                         report("Too many arguments");
6501                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6502                                         report("Failed to format command");
6503                                 } else {
6504                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6505                                 }
6506                         }
6508                         request = REQ_NONE;
6509                         break;
6510                 }
6511                 case REQ_SEARCH:
6512                 case REQ_SEARCH_BACK:
6513                 {
6514                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6515                         char *search = read_prompt(prompt);
6517                         if (search)
6518                                 string_ncopy(opt_search, search, strlen(search));
6519                         else
6520                                 request = REQ_NONE;
6521                         break;
6522                 }
6523                 default:
6524                         break;
6525                 }
6526         }
6528         quit(0);
6530         return 0;