Code

Fix memory corruption bug in tree_read when sorting the entries
[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 };
318 struct io {
319         enum io_type type;      /* The requested type of pipe. */
320         const char *dir;        /* Directory from which to execute. */
321         pid_t pid;              /* Pipe for reading or writing. */
322         int pipe;               /* Pipe end for reading or writing. */
323         int error;              /* Error status. */
324         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
325         char *buf;              /* Read buffer. */
326         size_t bufalloc;        /* Allocated buffer size. */
327         size_t bufsize;         /* Buffer content size. */
328         char *bufpos;           /* Current buffer position. */
329         unsigned int eof:1;     /* Has end of file been reached. */
330 };
332 static void
333 reset_io(struct io *io)
335         io->pipe = -1;
336         io->pid = 0;
337         io->buf = io->bufpos = NULL;
338         io->bufalloc = io->bufsize = 0;
339         io->error = 0;
340         io->eof = 0;
343 static void
344 init_io(struct io *io, const char *dir, enum io_type type)
346         reset_io(io);
347         io->type = type;
348         io->dir = dir;
351 static bool
352 init_io_rd(struct io *io, const char *argv[], const char *dir,
353                 enum format_flags flags)
355         init_io(io, dir, IO_RD);
356         return format_argv(io->argv, argv, flags);
359 static bool
360 io_open(struct io *io, const char *name)
362         init_io(io, NULL, IO_FD);
363         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
364         return io->pipe != -1;
367 static bool
368 kill_io(struct io *io)
370         return kill(io->pid, SIGKILL) != -1;
373 static bool
374 done_io(struct io *io)
376         pid_t pid = io->pid;
378         if (io->pipe != -1)
379                 close(io->pipe);
380         free(io->buf);
381         reset_io(io);
383         while (pid > 0) {
384                 int status;
385                 pid_t waiting = waitpid(pid, &status, 0);
387                 if (waiting < 0) {
388                         if (errno == EINTR)
389                                 continue;
390                         report("waitpid failed (%s)", strerror(errno));
391                         return FALSE;
392                 }
394                 return waiting == pid &&
395                        !WIFSIGNALED(status) &&
396                        WIFEXITED(status) &&
397                        !WEXITSTATUS(status);
398         }
400         return TRUE;
403 static bool
404 start_io(struct io *io)
406         int pipefds[2] = { -1, -1 };
408         if (io->type == IO_FD)
409                 return TRUE;
411         if ((io->type == IO_RD || io->type == IO_WR) &&
412             pipe(pipefds) < 0)
413                 return FALSE;
415         if ((io->pid = fork())) {
416                 if (pipefds[!(io->type == IO_WR)] != -1)
417                         close(pipefds[!(io->type == IO_WR)]);
418                 if (io->pid != -1) {
419                         io->pipe = pipefds[!!(io->type == IO_WR)];
420                         return TRUE;
421                 }
423         } else {
424                 if (io->type != IO_FG) {
425                         int devnull = open("/dev/null", O_RDWR);
426                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
427                         int writefd = io->type == IO_RD ? pipefds[1] : devnull;
429                         dup2(readfd,  STDIN_FILENO);
430                         dup2(writefd, STDOUT_FILENO);
431                         dup2(devnull, STDERR_FILENO);
433                         close(devnull);
434                         if (pipefds[0] != -1)
435                                 close(pipefds[0]);
436                         if (pipefds[1] != -1)
437                                 close(pipefds[1]);
438                 }
440                 if (io->dir && *io->dir && chdir(io->dir) == -1)
441                         die("Failed to change directory: %s", strerror(errno));
443                 execvp(io->argv[0], (char *const*) io->argv);
444                 die("Failed to execute program: %s", strerror(errno));
445         }
447         if (pipefds[!!(io->type == IO_WR)] != -1)
448                 close(pipefds[!!(io->type == IO_WR)]);
449         return FALSE;
452 static bool
453 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
455         init_io(io, dir, type);
456         if (!format_argv(io->argv, argv, FORMAT_NONE))
457                 return FALSE;
458         return start_io(io);
461 static int
462 run_io_do(struct io *io)
464         return start_io(io) && done_io(io);
467 static int
468 run_io_bg(const char **argv)
470         struct io io = {};
472         init_io(&io, NULL, IO_BG);
473         if (!format_argv(io.argv, argv, FORMAT_NONE))
474                 return FALSE;
475         return run_io_do(&io);
478 static bool
479 run_io_fg(const char **argv, const char *dir)
481         struct io io = {};
483         init_io(&io, dir, IO_FG);
484         if (!format_argv(io.argv, argv, FORMAT_NONE))
485                 return FALSE;
486         return run_io_do(&io);
489 static bool
490 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
492         return init_io_rd(io, argv, NULL, flags) && start_io(io);
495 static bool
496 io_eof(struct io *io)
498         return io->eof;
501 static int
502 io_error(struct io *io)
504         return io->error;
507 static bool
508 io_strerror(struct io *io)
510         return strerror(io->error);
513 static bool
514 io_can_read(struct io *io)
516         struct timeval tv = { 0, 500 };
517         fd_set fds;
519         FD_ZERO(&fds);
520         FD_SET(io->pipe, &fds);
522         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
525 static ssize_t
526 io_read(struct io *io, void *buf, size_t bufsize)
528         do {
529                 ssize_t readsize = read(io->pipe, buf, bufsize);
531                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
532                         continue;
533                 else if (readsize == -1)
534                         io->error = errno;
535                 else if (readsize == 0)
536                         io->eof = 1;
537                 return readsize;
538         } while (1);
541 static char *
542 io_get(struct io *io, int c, bool can_read)
544         char *eol;
545         ssize_t readsize;
547         if (!io->buf) {
548                 io->buf = io->bufpos = malloc(BUFSIZ);
549                 if (!io->buf)
550                         return NULL;
551                 io->bufalloc = BUFSIZ;
552                 io->bufsize = 0;
553         }
555         while (TRUE) {
556                 if (io->bufsize > 0) {
557                         eol = memchr(io->bufpos, c, io->bufsize);
558                         if (eol) {
559                                 char *line = io->bufpos;
561                                 *eol = 0;
562                                 io->bufpos = eol + 1;
563                                 io->bufsize -= io->bufpos - line;
564                                 return line;
565                         }
566                 }
568                 if (io_eof(io)) {
569                         if (io->bufsize) {
570                                 io->bufpos[io->bufsize] = 0;
571                                 io->bufsize = 0;
572                                 return io->bufpos;
573                         }
574                         return NULL;
575                 }
577                 if (!can_read)
578                         return NULL;
580                 if (io->bufsize > 0 && io->bufpos > io->buf)
581                         memmove(io->buf, io->bufpos, io->bufsize);
583                 io->bufpos = io->buf;
584                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
585                 if (io_error(io))
586                         return NULL;
587                 io->bufsize += readsize;
588         }
591 static bool
592 io_write(struct io *io, const void *buf, size_t bufsize)
594         size_t written = 0;
596         while (!io_error(io) && written < bufsize) {
597                 ssize_t size;
599                 size = write(io->pipe, buf + written, bufsize - written);
600                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
601                         continue;
602                 else if (size == -1)
603                         io->error = errno;
604                 else
605                         written += size;
606         }
608         return written == bufsize;
611 static bool
612 run_io_buf(const char **argv, char buf[], size_t bufsize)
614         struct io io = {};
615         bool error;
617         if (!run_io_rd(&io, argv, FORMAT_NONE))
618                 return FALSE;
620         io.buf = io.bufpos = buf;
621         io.bufalloc = bufsize;
622         error = !io_get(&io, '\n', TRUE) && io_error(&io);
623         io.buf = NULL;
625         return done_io(&io) || error;
628 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
630 /*
631  * User requests
632  */
634 #define REQ_INFO \
635         /* XXX: Keep the view request first and in sync with views[]. */ \
636         REQ_GROUP("View switching") \
637         REQ_(VIEW_MAIN,         "Show main view"), \
638         REQ_(VIEW_DIFF,         "Show diff view"), \
639         REQ_(VIEW_LOG,          "Show log view"), \
640         REQ_(VIEW_TREE,         "Show tree view"), \
641         REQ_(VIEW_BLOB,         "Show blob view"), \
642         REQ_(VIEW_BLAME,        "Show blame view"), \
643         REQ_(VIEW_HELP,         "Show help page"), \
644         REQ_(VIEW_PAGER,        "Show pager view"), \
645         REQ_(VIEW_STATUS,       "Show status view"), \
646         REQ_(VIEW_STAGE,        "Show stage view"), \
647         \
648         REQ_GROUP("View manipulation") \
649         REQ_(ENTER,             "Enter current line and scroll"), \
650         REQ_(NEXT,              "Move to next"), \
651         REQ_(PREVIOUS,          "Move to previous"), \
652         REQ_(VIEW_NEXT,         "Move focus to next view"), \
653         REQ_(REFRESH,           "Reload and refresh"), \
654         REQ_(MAXIMIZE,          "Maximize the current view"), \
655         REQ_(VIEW_CLOSE,        "Close the current view"), \
656         REQ_(QUIT,              "Close all views and quit"), \
657         \
658         REQ_GROUP("View specific requests") \
659         REQ_(STATUS_UPDATE,     "Update file status"), \
660         REQ_(STATUS_REVERT,     "Revert file changes"), \
661         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
662         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
663         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
664         \
665         REQ_GROUP("Cursor navigation") \
666         REQ_(MOVE_UP,           "Move cursor one line up"), \
667         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
668         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
669         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
670         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
671         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
672         \
673         REQ_GROUP("Scrolling") \
674         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
675         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
676         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
677         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
678         \
679         REQ_GROUP("Searching") \
680         REQ_(SEARCH,            "Search the view"), \
681         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
682         REQ_(FIND_NEXT,         "Find next search match"), \
683         REQ_(FIND_PREV,         "Find previous search match"), \
684         \
685         REQ_GROUP("Option manipulation") \
686         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
687         REQ_(TOGGLE_DATE,       "Toggle date display"), \
688         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
689         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
690         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
691         \
692         REQ_GROUP("Misc") \
693         REQ_(PROMPT,            "Bring up the prompt"), \
694         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
695         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
696         REQ_(SHOW_VERSION,      "Show version information"), \
697         REQ_(STOP_LOADING,      "Stop all loading views"), \
698         REQ_(EDIT,              "Open in editor"), \
699         REQ_(NONE,              "Do nothing")
702 /* User action requests. */
703 enum request {
704 #define REQ_GROUP(help)
705 #define REQ_(req, help) REQ_##req
707         /* Offset all requests to avoid conflicts with ncurses getch values. */
708         REQ_OFFSET = KEY_MAX + 1,
709         REQ_INFO
711 #undef  REQ_GROUP
712 #undef  REQ_
713 };
715 struct request_info {
716         enum request request;
717         const char *name;
718         int namelen;
719         const char *help;
720 };
722 static struct request_info req_info[] = {
723 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
724 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
725         REQ_INFO
726 #undef  REQ_GROUP
727 #undef  REQ_
728 };
730 static enum request
731 get_request(const char *name)
733         int namelen = strlen(name);
734         int i;
736         for (i = 0; i < ARRAY_SIZE(req_info); i++)
737                 if (req_info[i].namelen == namelen &&
738                     !string_enum_compare(req_info[i].name, name, namelen))
739                         return req_info[i].request;
741         return REQ_NONE;
745 /*
746  * Options
747  */
749 static const char usage[] =
750 "tig " TIG_VERSION " (" __DATE__ ")\n"
751 "\n"
752 "Usage: tig        [options] [revs] [--] [paths]\n"
753 "   or: tig show   [options] [revs] [--] [paths]\n"
754 "   or: tig blame  [rev] path\n"
755 "   or: tig status\n"
756 "   or: tig <      [git command output]\n"
757 "\n"
758 "Options:\n"
759 "  -v, --version   Show version and exit\n"
760 "  -h, --help      Show help message and exit";
762 /* Option and state variables. */
763 static bool opt_date                    = TRUE;
764 static bool opt_author                  = TRUE;
765 static bool opt_line_number             = FALSE;
766 static bool opt_line_graphics           = TRUE;
767 static bool opt_rev_graph               = FALSE;
768 static bool opt_show_refs               = TRUE;
769 static int opt_num_interval             = NUMBER_INTERVAL;
770 static int opt_tab_size                 = TAB_SIZE;
771 static int opt_author_cols              = AUTHOR_COLS-1;
772 static char opt_path[SIZEOF_STR]        = "";
773 static char opt_file[SIZEOF_STR]        = "";
774 static char opt_ref[SIZEOF_REF]         = "";
775 static char opt_head[SIZEOF_REF]        = "";
776 static char opt_head_rev[SIZEOF_REV]    = "";
777 static char opt_remote[SIZEOF_REF]      = "";
778 static char opt_encoding[20]            = "UTF-8";
779 static bool opt_utf8                    = TRUE;
780 static char opt_codeset[20]             = "UTF-8";
781 static iconv_t opt_iconv                = ICONV_NONE;
782 static char opt_search[SIZEOF_STR]      = "";
783 static char opt_cdup[SIZEOF_STR]        = "";
784 static char opt_git_dir[SIZEOF_STR]     = "";
785 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
786 static char opt_editor[SIZEOF_STR]      = "";
787 static FILE *opt_tty                    = NULL;
789 #define is_initial_commit()     (!*opt_head_rev)
790 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
792 static enum request
793 parse_options(int argc, const char *argv[], const char ***run_argv)
795         enum request request = REQ_VIEW_MAIN;
796         const char *subcommand;
797         bool seen_dashdash = FALSE;
798         /* XXX: This is vulnerable to the user overriding options
799          * required for the main view parser. */
800         const char *custom_argv[SIZEOF_ARG] = {
801                 "git", "log", "--no-color", "--pretty=raw", "--parents",
802                         "--topo-order", NULL
803         };
804         int i, j = 6;
806         if (!isatty(STDIN_FILENO))
807                 return REQ_VIEW_PAGER;
809         if (argc <= 1)
810                 return REQ_VIEW_MAIN;
812         subcommand = argv[1];
813         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
814                 if (!strcmp(subcommand, "-S"))
815                         warn("`-S' has been deprecated; use `tig status' instead");
816                 if (argc > 2)
817                         warn("ignoring arguments after `%s'", subcommand);
818                 return REQ_VIEW_STATUS;
820         } else if (!strcmp(subcommand, "blame")) {
821                 if (argc <= 2 || argc > 4)
822                         die("invalid number of options to blame\n\n%s", usage);
824                 i = 2;
825                 if (argc == 4) {
826                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
827                         i++;
828                 }
830                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
831                 return REQ_VIEW_BLAME;
833         } else if (!strcmp(subcommand, "show")) {
834                 request = REQ_VIEW_DIFF;
836         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
837                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
838                 warn("`tig %s' has been deprecated", subcommand);
840         } else {
841                 subcommand = NULL;
842         }
844         if (subcommand) {
845                 custom_argv[1] = subcommand;
846                 j = 2;
847         }
849         for (i = 1 + !!subcommand; i < argc; i++) {
850                 const char *opt = argv[i];
852                 if (seen_dashdash || !strcmp(opt, "--")) {
853                         seen_dashdash = TRUE;
855                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
856                         printf("tig version %s\n", TIG_VERSION);
857                         return REQ_NONE;
859                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
860                         printf("%s\n", usage);
861                         return REQ_NONE;
862                 }
864                 custom_argv[j++] = opt;
865                 if (j >= ARRAY_SIZE(custom_argv))
866                         die("command too long");
867         }
869         custom_argv[j] = NULL;
870         *run_argv = custom_argv;
872         return request;
876 /*
877  * Line-oriented content detection.
878  */
880 #define LINE_INFO \
881 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
882 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
883 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
884 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
885 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
886 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
887 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
888 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
889 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
890 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
891 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
892 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
893 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
894 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
895 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
896 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
897 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
898 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
899 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
900 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
901 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
902 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
903 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
904 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
905 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
906 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
907 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
908 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
910 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
911 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
912 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
913 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
914 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
915 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
916 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
917 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
918 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
919 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
920 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
921 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
922 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
923 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
924 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
925 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
926 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
927 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
928 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
929 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
930 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
931 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
932 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
933 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
934 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
936 enum line_type {
937 #define LINE(type, line, fg, bg, attr) \
938         LINE_##type
939         LINE_INFO,
940         LINE_NONE
941 #undef  LINE
942 };
944 struct line_info {
945         const char *name;       /* Option name. */
946         int namelen;            /* Size of option name. */
947         const char *line;       /* The start of line to match. */
948         int linelen;            /* Size of string to match. */
949         int fg, bg, attr;       /* Color and text attributes for the lines. */
950 };
952 static struct line_info line_info[] = {
953 #define LINE(type, line, fg, bg, attr) \
954         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
955         LINE_INFO
956 #undef  LINE
957 };
959 static enum line_type
960 get_line_type(const char *line)
962         int linelen = strlen(line);
963         enum line_type type;
965         for (type = 0; type < ARRAY_SIZE(line_info); type++)
966                 /* Case insensitive search matches Signed-off-by lines better. */
967                 if (linelen >= line_info[type].linelen &&
968                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
969                         return type;
971         return LINE_DEFAULT;
974 static inline int
975 get_line_attr(enum line_type type)
977         assert(type < ARRAY_SIZE(line_info));
978         return COLOR_PAIR(type) | line_info[type].attr;
981 static struct line_info *
982 get_line_info(const char *name)
984         size_t namelen = strlen(name);
985         enum line_type type;
987         for (type = 0; type < ARRAY_SIZE(line_info); type++)
988                 if (namelen == line_info[type].namelen &&
989                     !string_enum_compare(line_info[type].name, name, namelen))
990                         return &line_info[type];
992         return NULL;
995 static void
996 init_colors(void)
998         int default_bg = line_info[LINE_DEFAULT].bg;
999         int default_fg = line_info[LINE_DEFAULT].fg;
1000         enum line_type type;
1002         start_color();
1004         if (assume_default_colors(default_fg, default_bg) == ERR) {
1005                 default_bg = COLOR_BLACK;
1006                 default_fg = COLOR_WHITE;
1007         }
1009         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1010                 struct line_info *info = &line_info[type];
1011                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1012                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1014                 init_pair(type, fg, bg);
1015         }
1018 struct line {
1019         enum line_type type;
1021         /* State flags */
1022         unsigned int selected:1;
1023         unsigned int dirty:1;
1025         void *data;             /* User data */
1026 };
1029 /*
1030  * Keys
1031  */
1033 struct keybinding {
1034         int alias;
1035         enum request request;
1036 };
1038 static struct keybinding default_keybindings[] = {
1039         /* View switching */
1040         { 'm',          REQ_VIEW_MAIN },
1041         { 'd',          REQ_VIEW_DIFF },
1042         { 'l',          REQ_VIEW_LOG },
1043         { 't',          REQ_VIEW_TREE },
1044         { 'f',          REQ_VIEW_BLOB },
1045         { 'B',          REQ_VIEW_BLAME },
1046         { 'p',          REQ_VIEW_PAGER },
1047         { 'h',          REQ_VIEW_HELP },
1048         { 'S',          REQ_VIEW_STATUS },
1049         { 'c',          REQ_VIEW_STAGE },
1051         /* View manipulation */
1052         { 'q',          REQ_VIEW_CLOSE },
1053         { KEY_TAB,      REQ_VIEW_NEXT },
1054         { KEY_RETURN,   REQ_ENTER },
1055         { KEY_UP,       REQ_PREVIOUS },
1056         { KEY_DOWN,     REQ_NEXT },
1057         { 'R',          REQ_REFRESH },
1058         { KEY_F(5),     REQ_REFRESH },
1059         { 'O',          REQ_MAXIMIZE },
1061         /* Cursor navigation */
1062         { 'k',          REQ_MOVE_UP },
1063         { 'j',          REQ_MOVE_DOWN },
1064         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1065         { KEY_END,      REQ_MOVE_LAST_LINE },
1066         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1067         { ' ',          REQ_MOVE_PAGE_DOWN },
1068         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1069         { 'b',          REQ_MOVE_PAGE_UP },
1070         { '-',          REQ_MOVE_PAGE_UP },
1072         /* Scrolling */
1073         { KEY_IC,       REQ_SCROLL_LINE_UP },
1074         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1075         { 'w',          REQ_SCROLL_PAGE_UP },
1076         { 's',          REQ_SCROLL_PAGE_DOWN },
1078         /* Searching */
1079         { '/',          REQ_SEARCH },
1080         { '?',          REQ_SEARCH_BACK },
1081         { 'n',          REQ_FIND_NEXT },
1082         { 'N',          REQ_FIND_PREV },
1084         /* Misc */
1085         { 'Q',          REQ_QUIT },
1086         { 'z',          REQ_STOP_LOADING },
1087         { 'v',          REQ_SHOW_VERSION },
1088         { 'r',          REQ_SCREEN_REDRAW },
1089         { '.',          REQ_TOGGLE_LINENO },
1090         { 'D',          REQ_TOGGLE_DATE },
1091         { 'A',          REQ_TOGGLE_AUTHOR },
1092         { 'g',          REQ_TOGGLE_REV_GRAPH },
1093         { 'F',          REQ_TOGGLE_REFS },
1094         { ':',          REQ_PROMPT },
1095         { 'u',          REQ_STATUS_UPDATE },
1096         { '!',          REQ_STATUS_REVERT },
1097         { 'M',          REQ_STATUS_MERGE },
1098         { '@',          REQ_STAGE_NEXT },
1099         { ',',          REQ_TREE_PARENT },
1100         { 'e',          REQ_EDIT },
1102         /* Using the ncurses SIGWINCH handler. */
1103         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1104 };
1106 #define KEYMAP_INFO \
1107         KEYMAP_(GENERIC), \
1108         KEYMAP_(MAIN), \
1109         KEYMAP_(DIFF), \
1110         KEYMAP_(LOG), \
1111         KEYMAP_(TREE), \
1112         KEYMAP_(BLOB), \
1113         KEYMAP_(BLAME), \
1114         KEYMAP_(PAGER), \
1115         KEYMAP_(HELP), \
1116         KEYMAP_(STATUS), \
1117         KEYMAP_(STAGE)
1119 enum keymap {
1120 #define KEYMAP_(name) KEYMAP_##name
1121         KEYMAP_INFO
1122 #undef  KEYMAP_
1123 };
1125 static struct int_map keymap_table[] = {
1126 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1127         KEYMAP_INFO
1128 #undef  KEYMAP_
1129 };
1131 #define set_keymap(map, name) \
1132         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1134 struct keybinding_table {
1135         struct keybinding *data;
1136         size_t size;
1137 };
1139 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1141 static void
1142 add_keybinding(enum keymap keymap, enum request request, int key)
1144         struct keybinding_table *table = &keybindings[keymap];
1146         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1147         if (!table->data)
1148                 die("Failed to allocate keybinding");
1149         table->data[table->size].alias = key;
1150         table->data[table->size++].request = request;
1153 /* Looks for a key binding first in the given map, then in the generic map, and
1154  * lastly in the default keybindings. */
1155 static enum request
1156 get_keybinding(enum keymap keymap, int key)
1158         size_t i;
1160         for (i = 0; i < keybindings[keymap].size; i++)
1161                 if (keybindings[keymap].data[i].alias == key)
1162                         return keybindings[keymap].data[i].request;
1164         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1165                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1166                         return keybindings[KEYMAP_GENERIC].data[i].request;
1168         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1169                 if (default_keybindings[i].alias == key)
1170                         return default_keybindings[i].request;
1172         return (enum request) key;
1176 struct key {
1177         const char *name;
1178         int value;
1179 };
1181 static struct key key_table[] = {
1182         { "Enter",      KEY_RETURN },
1183         { "Space",      ' ' },
1184         { "Backspace",  KEY_BACKSPACE },
1185         { "Tab",        KEY_TAB },
1186         { "Escape",     KEY_ESC },
1187         { "Left",       KEY_LEFT },
1188         { "Right",      KEY_RIGHT },
1189         { "Up",         KEY_UP },
1190         { "Down",       KEY_DOWN },
1191         { "Insert",     KEY_IC },
1192         { "Delete",     KEY_DC },
1193         { "Hash",       '#' },
1194         { "Home",       KEY_HOME },
1195         { "End",        KEY_END },
1196         { "PageUp",     KEY_PPAGE },
1197         { "PageDown",   KEY_NPAGE },
1198         { "F1",         KEY_F(1) },
1199         { "F2",         KEY_F(2) },
1200         { "F3",         KEY_F(3) },
1201         { "F4",         KEY_F(4) },
1202         { "F5",         KEY_F(5) },
1203         { "F6",         KEY_F(6) },
1204         { "F7",         KEY_F(7) },
1205         { "F8",         KEY_F(8) },
1206         { "F9",         KEY_F(9) },
1207         { "F10",        KEY_F(10) },
1208         { "F11",        KEY_F(11) },
1209         { "F12",        KEY_F(12) },
1210 };
1212 static int
1213 get_key_value(const char *name)
1215         int i;
1217         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1218                 if (!strcasecmp(key_table[i].name, name))
1219                         return key_table[i].value;
1221         if (strlen(name) == 1 && isprint(*name))
1222                 return (int) *name;
1224         return ERR;
1227 static const char *
1228 get_key_name(int key_value)
1230         static char key_char[] = "'X'";
1231         const char *seq = NULL;
1232         int key;
1234         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1235                 if (key_table[key].value == key_value)
1236                         seq = key_table[key].name;
1238         if (seq == NULL &&
1239             key_value < 127 &&
1240             isprint(key_value)) {
1241                 key_char[1] = (char) key_value;
1242                 seq = key_char;
1243         }
1245         return seq ? seq : "(no key)";
1248 static const char *
1249 get_key(enum request request)
1251         static char buf[BUFSIZ];
1252         size_t pos = 0;
1253         char *sep = "";
1254         int i;
1256         buf[pos] = 0;
1258         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1259                 struct keybinding *keybinding = &default_keybindings[i];
1261                 if (keybinding->request != request)
1262                         continue;
1264                 if (!string_format_from(buf, &pos, "%s%s", sep,
1265                                         get_key_name(keybinding->alias)))
1266                         return "Too many keybindings!";
1267                 sep = ", ";
1268         }
1270         return buf;
1273 struct run_request {
1274         enum keymap keymap;
1275         int key;
1276         const char *argv[SIZEOF_ARG];
1277 };
1279 static struct run_request *run_request;
1280 static size_t run_requests;
1282 static enum request
1283 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1285         struct run_request *req;
1287         if (argc >= ARRAY_SIZE(req->argv) - 1)
1288                 return REQ_NONE;
1290         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1291         if (!req)
1292                 return REQ_NONE;
1294         run_request = req;
1295         req = &run_request[run_requests];
1296         req->keymap = keymap;
1297         req->key = key;
1298         req->argv[0] = NULL;
1300         if (!format_argv(req->argv, argv, FORMAT_NONE))
1301                 return REQ_NONE;
1303         return REQ_NONE + ++run_requests;
1306 static struct run_request *
1307 get_run_request(enum request request)
1309         if (request <= REQ_NONE)
1310                 return NULL;
1311         return &run_request[request - REQ_NONE - 1];
1314 static void
1315 add_builtin_run_requests(void)
1317         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1318         const char *gc[] = { "git", "gc", NULL };
1319         struct {
1320                 enum keymap keymap;
1321                 int key;
1322                 int argc;
1323                 const char **argv;
1324         } reqs[] = {
1325                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1326                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1327         };
1328         int i;
1330         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1331                 enum request req;
1333                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1334                 if (req != REQ_NONE)
1335                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1336         }
1339 /*
1340  * User config file handling.
1341  */
1343 static struct int_map color_map[] = {
1344 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1345         COLOR_MAP(DEFAULT),
1346         COLOR_MAP(BLACK),
1347         COLOR_MAP(BLUE),
1348         COLOR_MAP(CYAN),
1349         COLOR_MAP(GREEN),
1350         COLOR_MAP(MAGENTA),
1351         COLOR_MAP(RED),
1352         COLOR_MAP(WHITE),
1353         COLOR_MAP(YELLOW),
1354 };
1356 #define set_color(color, name) \
1357         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1359 static struct int_map attr_map[] = {
1360 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1361         ATTR_MAP(NORMAL),
1362         ATTR_MAP(BLINK),
1363         ATTR_MAP(BOLD),
1364         ATTR_MAP(DIM),
1365         ATTR_MAP(REVERSE),
1366         ATTR_MAP(STANDOUT),
1367         ATTR_MAP(UNDERLINE),
1368 };
1370 #define set_attribute(attr, name) \
1371         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1373 static int   config_lineno;
1374 static bool  config_errors;
1375 static const char *config_msg;
1377 /* Wants: object fgcolor bgcolor [attr] */
1378 static int
1379 option_color_command(int argc, const char *argv[])
1381         struct line_info *info;
1383         if (argc != 3 && argc != 4) {
1384                 config_msg = "Wrong number of arguments given to color command";
1385                 return ERR;
1386         }
1388         info = get_line_info(argv[0]);
1389         if (!info) {
1390                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1391                         info = get_line_info("delimiter");
1393                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1394                         info = get_line_info("date");
1396                 } else {
1397                         config_msg = "Unknown color name";
1398                         return ERR;
1399                 }
1400         }
1402         if (set_color(&info->fg, argv[1]) == ERR ||
1403             set_color(&info->bg, argv[2]) == ERR) {
1404                 config_msg = "Unknown color";
1405                 return ERR;
1406         }
1408         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1409                 config_msg = "Unknown attribute";
1410                 return ERR;
1411         }
1413         return OK;
1416 static bool parse_bool(const char *s)
1418         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1419                 !strcmp(s, "yes")) ? TRUE : FALSE;
1422 static int
1423 parse_int(const char *s, int default_value, int min, int max)
1425         int value = atoi(s);
1427         return (value < min || value > max) ? default_value : value;
1430 /* Wants: name = value */
1431 static int
1432 option_set_command(int argc, const char *argv[])
1434         if (argc != 3) {
1435                 config_msg = "Wrong number of arguments given to set command";
1436                 return ERR;
1437         }
1439         if (strcmp(argv[1], "=")) {
1440                 config_msg = "No value assigned";
1441                 return ERR;
1442         }
1444         if (!strcmp(argv[0], "show-author")) {
1445                 opt_author = parse_bool(argv[2]);
1446                 return OK;
1447         }
1449         if (!strcmp(argv[0], "show-date")) {
1450                 opt_date = parse_bool(argv[2]);
1451                 return OK;
1452         }
1454         if (!strcmp(argv[0], "show-rev-graph")) {
1455                 opt_rev_graph = parse_bool(argv[2]);
1456                 return OK;
1457         }
1459         if (!strcmp(argv[0], "show-refs")) {
1460                 opt_show_refs = parse_bool(argv[2]);
1461                 return OK;
1462         }
1464         if (!strcmp(argv[0], "show-line-numbers")) {
1465                 opt_line_number = parse_bool(argv[2]);
1466                 return OK;
1467         }
1469         if (!strcmp(argv[0], "line-graphics")) {
1470                 opt_line_graphics = parse_bool(argv[2]);
1471                 return OK;
1472         }
1474         if (!strcmp(argv[0], "line-number-interval")) {
1475                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1476                 return OK;
1477         }
1479         if (!strcmp(argv[0], "author-width")) {
1480                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1481                 return OK;
1482         }
1484         if (!strcmp(argv[0], "tab-size")) {
1485                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1486                 return OK;
1487         }
1489         if (!strcmp(argv[0], "commit-encoding")) {
1490                 const char *arg = argv[2];
1491                 int arglen = strlen(arg);
1493                 switch (arg[0]) {
1494                 case '"':
1495                 case '\'':
1496                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1497                                 config_msg = "Unmatched quotation";
1498                                 return ERR;
1499                         }
1500                         arg += 1; arglen -= 2;
1501                 default:
1502                         string_ncopy(opt_encoding, arg, strlen(arg));
1503                         return OK;
1504                 }
1505         }
1507         config_msg = "Unknown variable name";
1508         return ERR;
1511 /* Wants: mode request key */
1512 static int
1513 option_bind_command(int argc, const char *argv[])
1515         enum request request;
1516         int keymap;
1517         int key;
1519         if (argc < 3) {
1520                 config_msg = "Wrong number of arguments given to bind command";
1521                 return ERR;
1522         }
1524         if (set_keymap(&keymap, argv[0]) == ERR) {
1525                 config_msg = "Unknown key map";
1526                 return ERR;
1527         }
1529         key = get_key_value(argv[1]);
1530         if (key == ERR) {
1531                 config_msg = "Unknown key";
1532                 return ERR;
1533         }
1535         request = get_request(argv[2]);
1536         if (request == REQ_NONE) {
1537                 const char *obsolete[] = { "cherry-pick" };
1538                 size_t namelen = strlen(argv[2]);
1539                 int i;
1541                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1542                         if (namelen == strlen(obsolete[i]) &&
1543                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1544                                 config_msg = "Obsolete request name";
1545                                 return ERR;
1546                         }
1547                 }
1548         }
1549         if (request == REQ_NONE && *argv[2]++ == '!')
1550                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1551         if (request == REQ_NONE) {
1552                 config_msg = "Unknown request name";
1553                 return ERR;
1554         }
1556         add_keybinding(keymap, request, key);
1558         return OK;
1561 static int
1562 set_option(const char *opt, char *value)
1564         const char *argv[SIZEOF_ARG];
1565         int argc = 0;
1567         if (!argv_from_string(argv, &argc, value)) {
1568                 config_msg = "Too many option arguments";
1569                 return ERR;
1570         }
1572         if (!strcmp(opt, "color"))
1573                 return option_color_command(argc, argv);
1575         if (!strcmp(opt, "set"))
1576                 return option_set_command(argc, argv);
1578         if (!strcmp(opt, "bind"))
1579                 return option_bind_command(argc, argv);
1581         config_msg = "Unknown option command";
1582         return ERR;
1585 static int
1586 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1588         int status = OK;
1590         config_lineno++;
1591         config_msg = "Internal error";
1593         /* Check for comment markers, since read_properties() will
1594          * only ensure opt and value are split at first " \t". */
1595         optlen = strcspn(opt, "#");
1596         if (optlen == 0)
1597                 return OK;
1599         if (opt[optlen] != 0) {
1600                 config_msg = "No option value";
1601                 status = ERR;
1603         }  else {
1604                 /* Look for comment endings in the value. */
1605                 size_t len = strcspn(value, "#");
1607                 if (len < valuelen) {
1608                         valuelen = len;
1609                         value[valuelen] = 0;
1610                 }
1612                 status = set_option(opt, value);
1613         }
1615         if (status == ERR) {
1616                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1617                         config_lineno, (int) optlen, opt, config_msg);
1618                 config_errors = TRUE;
1619         }
1621         /* Always keep going if errors are encountered. */
1622         return OK;
1625 static void
1626 load_option_file(const char *path)
1628         struct io io = {};
1630         /* It's ok that the file doesn't exist. */
1631         if (!io_open(&io, path))
1632                 return;
1634         config_lineno = 0;
1635         config_errors = FALSE;
1637         if (read_properties(&io, " \t", read_option) == ERR ||
1638             config_errors == TRUE)
1639                 fprintf(stderr, "Errors while loading %s.\n", path);
1642 static int
1643 load_options(void)
1645         const char *home = getenv("HOME");
1646         const char *tigrc_user = getenv("TIGRC_USER");
1647         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1648         char buf[SIZEOF_STR];
1650         add_builtin_run_requests();
1652         if (!tigrc_system) {
1653                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1654                         return ERR;
1655                 tigrc_system = buf;
1656         }
1657         load_option_file(tigrc_system);
1659         if (!tigrc_user) {
1660                 if (!home || !string_format(buf, "%s/.tigrc", home))
1661                         return ERR;
1662                 tigrc_user = buf;
1663         }
1664         load_option_file(tigrc_user);
1666         return OK;
1670 /*
1671  * The viewer
1672  */
1674 struct view;
1675 struct view_ops;
1677 /* The display array of active views and the index of the current view. */
1678 static struct view *display[2];
1679 static unsigned int current_view;
1681 /* Reading from the prompt? */
1682 static bool input_mode = FALSE;
1684 #define foreach_displayed_view(view, i) \
1685         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1687 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1689 /* Current head and commit ID */
1690 static char ref_blob[SIZEOF_REF]        = "";
1691 static char ref_commit[SIZEOF_REF]      = "HEAD";
1692 static char ref_head[SIZEOF_REF]        = "HEAD";
1694 struct view {
1695         const char *name;       /* View name */
1696         const char *cmd_env;    /* Command line set via environment */
1697         const char *id;         /* Points to either of ref_{head,commit,blob} */
1699         struct view_ops *ops;   /* View operations */
1701         enum keymap keymap;     /* What keymap does this view have */
1702         bool git_dir;           /* Whether the view requires a git directory. */
1704         char ref[SIZEOF_REF];   /* Hovered commit reference */
1705         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1707         int height, width;      /* The width and height of the main window */
1708         WINDOW *win;            /* The main window */
1709         WINDOW *title;          /* The title window living below the main window */
1711         /* Navigation */
1712         unsigned long offset;   /* Offset of the window top */
1713         unsigned long lineno;   /* Current line number */
1715         /* Searching */
1716         char grep[SIZEOF_STR];  /* Search string */
1717         regex_t *regex;         /* Pre-compiled regex */
1719         /* If non-NULL, points to the view that opened this view. If this view
1720          * is closed tig will switch back to the parent view. */
1721         struct view *parent;
1723         /* Buffering */
1724         size_t lines;           /* Total number of lines */
1725         struct line *line;      /* Line index */
1726         size_t line_alloc;      /* Total number of allocated lines */
1727         unsigned int digits;    /* Number of digits in the lines member. */
1729         /* Drawing */
1730         struct line *curline;   /* Line currently being drawn. */
1731         enum line_type curtype; /* Attribute currently used for drawing. */
1732         unsigned long col;      /* Column when drawing. */
1734         /* Loading */
1735         struct io io;
1736         struct io *pipe;
1737         time_t start_time;
1738         time_t update_secs;
1739 };
1741 struct view_ops {
1742         /* What type of content being displayed. Used in the title bar. */
1743         const char *type;
1744         /* Default command arguments. */
1745         const char **argv;
1746         /* Open and reads in all view content. */
1747         bool (*open)(struct view *view);
1748         /* Read one line; updates view->line. */
1749         bool (*read)(struct view *view, char *data);
1750         /* Draw one line; @lineno must be < view->height. */
1751         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1752         /* Depending on view handle a special requests. */
1753         enum request (*request)(struct view *view, enum request request, struct line *line);
1754         /* Search for regex in a line. */
1755         bool (*grep)(struct view *view, struct line *line);
1756         /* Select line */
1757         void (*select)(struct view *view, struct line *line);
1758 };
1760 static struct view_ops blame_ops;
1761 static struct view_ops blob_ops;
1762 static struct view_ops diff_ops;
1763 static struct view_ops help_ops;
1764 static struct view_ops log_ops;
1765 static struct view_ops main_ops;
1766 static struct view_ops pager_ops;
1767 static struct view_ops stage_ops;
1768 static struct view_ops status_ops;
1769 static struct view_ops tree_ops;
1771 #define VIEW_STR(name, env, ref, ops, map, git) \
1772         { name, #env, ref, ops, map, git }
1774 #define VIEW_(id, name, ops, git, ref) \
1775         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1778 static struct view views[] = {
1779         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1780         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1781         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1782         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1783         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1784         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1785         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1786         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1787         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1788         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1789 };
1791 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1792 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1794 #define foreach_view(view, i) \
1795         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1797 #define view_is_displayed(view) \
1798         (view == display[0] || view == display[1])
1801 enum line_graphic {
1802         LINE_GRAPHIC_VLINE
1803 };
1805 static int line_graphics[] = {
1806         /* LINE_GRAPHIC_VLINE: */ '|'
1807 };
1809 static inline void
1810 set_view_attr(struct view *view, enum line_type type)
1812         if (!view->curline->selected && view->curtype != type) {
1813                 wattrset(view->win, get_line_attr(type));
1814                 wchgat(view->win, -1, 0, type, NULL);
1815                 view->curtype = type;
1816         }
1819 static int
1820 draw_chars(struct view *view, enum line_type type, const char *string,
1821            int max_len, bool use_tilde)
1823         int len = 0;
1824         int col = 0;
1825         int trimmed = FALSE;
1827         if (max_len <= 0)
1828                 return 0;
1830         if (opt_utf8) {
1831                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1832         } else {
1833                 col = len = strlen(string);
1834                 if (len > max_len) {
1835                         if (use_tilde) {
1836                                 max_len -= 1;
1837                         }
1838                         col = len = max_len;
1839                         trimmed = TRUE;
1840                 }
1841         }
1843         set_view_attr(view, type);
1844         waddnstr(view->win, string, len);
1845         if (trimmed && use_tilde) {
1846                 set_view_attr(view, LINE_DELIMITER);
1847                 waddch(view->win, '~');
1848                 col++;
1849         }
1851         return col;
1854 static int
1855 draw_space(struct view *view, enum line_type type, int max, int spaces)
1857         static char space[] = "                    ";
1858         int col = 0;
1860         spaces = MIN(max, spaces);
1862         while (spaces > 0) {
1863                 int len = MIN(spaces, sizeof(space) - 1);
1865                 col += draw_chars(view, type, space, spaces, FALSE);
1866                 spaces -= len;
1867         }
1869         return col;
1872 static bool
1873 draw_lineno(struct view *view, unsigned int lineno)
1875         char number[10];
1876         int digits3 = view->digits < 3 ? 3 : view->digits;
1877         int max_number = MIN(digits3, STRING_SIZE(number));
1878         int max = view->width - view->col;
1879         int col;
1881         if (max < max_number)
1882                 max_number = max;
1884         lineno += view->offset + 1;
1885         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1886                 static char fmt[] = "%1ld";
1888                 if (view->digits <= 9)
1889                         fmt[1] = '0' + digits3;
1891                 if (!string_format(number, fmt, lineno))
1892                         number[0] = 0;
1893                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1894         } else {
1895                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1896         }
1898         if (col < max) {
1899                 set_view_attr(view, LINE_DEFAULT);
1900                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1901                 col++;
1902         }
1904         if (col < max)
1905                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1906         view->col += col;
1908         return view->width - view->col <= 0;
1911 static bool
1912 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1914         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1915         return view->width - view->col <= 0;
1918 static bool
1919 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1921         int max = view->width - view->col;
1922         int i;
1924         if (max < size)
1925                 size = max;
1927         set_view_attr(view, type);
1928         /* Using waddch() instead of waddnstr() ensures that
1929          * they'll be rendered correctly for the cursor line. */
1930         for (i = 0; i < size; i++)
1931                 waddch(view->win, graphic[i]);
1933         view->col += size;
1934         if (size < max) {
1935                 waddch(view->win, ' ');
1936                 view->col++;
1937         }
1939         return view->width - view->col <= 0;
1942 static bool
1943 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1945         int max = MIN(view->width - view->col, len);
1946         int col;
1948         if (text)
1949                 col = draw_chars(view, type, text, max - 1, trim);
1950         else
1951                 col = draw_space(view, type, max - 1, max - 1);
1953         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1954         return view->width - view->col <= 0;
1957 static bool
1958 draw_date(struct view *view, struct tm *time)
1960         char buf[DATE_COLS];
1961         char *date;
1962         int timelen = 0;
1964         if (time)
1965                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1966         date = timelen ? buf : NULL;
1968         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1971 static bool
1972 draw_view_line(struct view *view, unsigned int lineno)
1974         struct line *line;
1975         bool selected = (view->offset + lineno == view->lineno);
1976         bool draw_ok;
1978         assert(view_is_displayed(view));
1980         if (view->offset + lineno >= view->lines)
1981                 return FALSE;
1983         line = &view->line[view->offset + lineno];
1985         wmove(view->win, lineno, 0);
1986         view->col = 0;
1987         view->curline = line;
1988         view->curtype = LINE_NONE;
1989         line->selected = FALSE;
1991         if (selected) {
1992                 set_view_attr(view, LINE_CURSOR);
1993                 line->selected = TRUE;
1994                 view->ops->select(view, line);
1995         } else if (line->selected) {
1996                 wclrtoeol(view->win);
1997         }
1999         scrollok(view->win, FALSE);
2000         draw_ok = view->ops->draw(view, line, lineno);
2001         scrollok(view->win, TRUE);
2003         return draw_ok;
2006 static void
2007 redraw_view_dirty(struct view *view)
2009         bool dirty = FALSE;
2010         int lineno;
2012         for (lineno = 0; lineno < view->height; lineno++) {
2013                 struct line *line = &view->line[view->offset + lineno];
2015                 if (!line->dirty)
2016                         continue;
2017                 line->dirty = 0;
2018                 dirty = TRUE;
2019                 if (!draw_view_line(view, lineno))
2020                         break;
2021         }
2023         if (!dirty)
2024                 return;
2025         redrawwin(view->win);
2026         if (input_mode)
2027                 wnoutrefresh(view->win);
2028         else
2029                 wrefresh(view->win);
2032 static void
2033 redraw_view_from(struct view *view, int lineno)
2035         assert(0 <= lineno && lineno < view->height);
2037         for (; lineno < view->height; lineno++) {
2038                 if (!draw_view_line(view, lineno))
2039                         break;
2040         }
2042         redrawwin(view->win);
2043         if (input_mode)
2044                 wnoutrefresh(view->win);
2045         else
2046                 wrefresh(view->win);
2049 static void
2050 redraw_view(struct view *view)
2052         wclear(view->win);
2053         redraw_view_from(view, 0);
2057 static void
2058 update_view_title(struct view *view)
2060         char buf[SIZEOF_STR];
2061         char state[SIZEOF_STR];
2062         size_t bufpos = 0, statelen = 0;
2064         assert(view_is_displayed(view));
2066         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2067                 unsigned int view_lines = view->offset + view->height;
2068                 unsigned int lines = view->lines
2069                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2070                                    : 0;
2072                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2073                                    view->ops->type,
2074                                    view->lineno + 1,
2075                                    view->lines,
2076                                    lines);
2078         }
2080         if (view->pipe) {
2081                 time_t secs = time(NULL) - view->start_time;
2083                 /* Three git seconds are a long time ... */
2084                 if (secs > 2)
2085                         string_format_from(state, &statelen, " loading %lds", secs);
2086         }
2088         string_format_from(buf, &bufpos, "[%s]", view->name);
2089         if (*view->ref && bufpos < view->width) {
2090                 size_t refsize = strlen(view->ref);
2091                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2093                 if (minsize < view->width)
2094                         refsize = view->width - minsize + 7;
2095                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2096         }
2098         if (statelen && bufpos < view->width) {
2099                 string_format_from(buf, &bufpos, "%s", state);
2100         }
2102         if (view == display[current_view])
2103                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2104         else
2105                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2107         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2108         wclrtoeol(view->title);
2109         wmove(view->title, 0, view->width - 1);
2111         if (input_mode)
2112                 wnoutrefresh(view->title);
2113         else
2114                 wrefresh(view->title);
2117 static void
2118 resize_display(void)
2120         int offset, i;
2121         struct view *base = display[0];
2122         struct view *view = display[1] ? display[1] : display[0];
2124         /* Setup window dimensions */
2126         getmaxyx(stdscr, base->height, base->width);
2128         /* Make room for the status window. */
2129         base->height -= 1;
2131         if (view != base) {
2132                 /* Horizontal split. */
2133                 view->width   = base->width;
2134                 view->height  = SCALE_SPLIT_VIEW(base->height);
2135                 base->height -= view->height;
2137                 /* Make room for the title bar. */
2138                 view->height -= 1;
2139         }
2141         /* Make room for the title bar. */
2142         base->height -= 1;
2144         offset = 0;
2146         foreach_displayed_view (view, i) {
2147                 if (!view->win) {
2148                         view->win = newwin(view->height, 0, offset, 0);
2149                         if (!view->win)
2150                                 die("Failed to create %s view", view->name);
2152                         scrollok(view->win, TRUE);
2154                         view->title = newwin(1, 0, offset + view->height, 0);
2155                         if (!view->title)
2156                                 die("Failed to create title window");
2158                 } else {
2159                         wresize(view->win, view->height, view->width);
2160                         mvwin(view->win,   offset, 0);
2161                         mvwin(view->title, offset + view->height, 0);
2162                 }
2164                 offset += view->height + 1;
2165         }
2168 static void
2169 redraw_display(void)
2171         struct view *view;
2172         int i;
2174         foreach_displayed_view (view, i) {
2175                 redraw_view(view);
2176                 update_view_title(view);
2177         }
2180 static void
2181 update_display_cursor(struct view *view)
2183         /* Move the cursor to the right-most column of the cursor line.
2184          *
2185          * XXX: This could turn out to be a bit expensive, but it ensures that
2186          * the cursor does not jump around. */
2187         if (view->lines) {
2188                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2189                 wrefresh(view->win);
2190         }
2193 /*
2194  * Navigation
2195  */
2197 /* Scrolling backend */
2198 static void
2199 do_scroll_view(struct view *view, int lines)
2201         bool redraw_current_line = FALSE;
2203         /* The rendering expects the new offset. */
2204         view->offset += lines;
2206         assert(0 <= view->offset && view->offset < view->lines);
2207         assert(lines);
2209         /* Move current line into the view. */
2210         if (view->lineno < view->offset) {
2211                 view->lineno = view->offset;
2212                 redraw_current_line = TRUE;
2213         } else if (view->lineno >= view->offset + view->height) {
2214                 view->lineno = view->offset + view->height - 1;
2215                 redraw_current_line = TRUE;
2216         }
2218         assert(view->offset <= view->lineno && view->lineno < view->lines);
2220         /* Redraw the whole screen if scrolling is pointless. */
2221         if (view->height < ABS(lines)) {
2222                 redraw_view(view);
2224         } else {
2225                 int line = lines > 0 ? view->height - lines : 0;
2226                 int end = line + ABS(lines);
2228                 wscrl(view->win, lines);
2230                 for (; line < end; line++) {
2231                         if (!draw_view_line(view, line))
2232                                 break;
2233                 }
2235                 if (redraw_current_line)
2236                         draw_view_line(view, view->lineno - view->offset);
2237         }
2239         redrawwin(view->win);
2240         wrefresh(view->win);
2241         report("");
2244 /* Scroll frontend */
2245 static void
2246 scroll_view(struct view *view, enum request request)
2248         int lines = 1;
2250         assert(view_is_displayed(view));
2252         switch (request) {
2253         case REQ_SCROLL_PAGE_DOWN:
2254                 lines = view->height;
2255         case REQ_SCROLL_LINE_DOWN:
2256                 if (view->offset + lines > view->lines)
2257                         lines = view->lines - view->offset;
2259                 if (lines == 0 || view->offset + view->height >= view->lines) {
2260                         report("Cannot scroll beyond the last line");
2261                         return;
2262                 }
2263                 break;
2265         case REQ_SCROLL_PAGE_UP:
2266                 lines = view->height;
2267         case REQ_SCROLL_LINE_UP:
2268                 if (lines > view->offset)
2269                         lines = view->offset;
2271                 if (lines == 0) {
2272                         report("Cannot scroll beyond the first line");
2273                         return;
2274                 }
2276                 lines = -lines;
2277                 break;
2279         default:
2280                 die("request %d not handled in switch", request);
2281         }
2283         do_scroll_view(view, lines);
2286 /* Cursor moving */
2287 static void
2288 move_view(struct view *view, enum request request)
2290         int scroll_steps = 0;
2291         int steps;
2293         switch (request) {
2294         case REQ_MOVE_FIRST_LINE:
2295                 steps = -view->lineno;
2296                 break;
2298         case REQ_MOVE_LAST_LINE:
2299                 steps = view->lines - view->lineno - 1;
2300                 break;
2302         case REQ_MOVE_PAGE_UP:
2303                 steps = view->height > view->lineno
2304                       ? -view->lineno : -view->height;
2305                 break;
2307         case REQ_MOVE_PAGE_DOWN:
2308                 steps = view->lineno + view->height >= view->lines
2309                       ? view->lines - view->lineno - 1 : view->height;
2310                 break;
2312         case REQ_MOVE_UP:
2313                 steps = -1;
2314                 break;
2316         case REQ_MOVE_DOWN:
2317                 steps = 1;
2318                 break;
2320         default:
2321                 die("request %d not handled in switch", request);
2322         }
2324         if (steps <= 0 && view->lineno == 0) {
2325                 report("Cannot move beyond the first line");
2326                 return;
2328         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2329                 report("Cannot move beyond the last line");
2330                 return;
2331         }
2333         /* Move the current line */
2334         view->lineno += steps;
2335         assert(0 <= view->lineno && view->lineno < view->lines);
2337         /* Check whether the view needs to be scrolled */
2338         if (view->lineno < view->offset ||
2339             view->lineno >= view->offset + view->height) {
2340                 scroll_steps = steps;
2341                 if (steps < 0 && -steps > view->offset) {
2342                         scroll_steps = -view->offset;
2344                 } else if (steps > 0) {
2345                         if (view->lineno == view->lines - 1 &&
2346                             view->lines > view->height) {
2347                                 scroll_steps = view->lines - view->offset - 1;
2348                                 if (scroll_steps >= view->height)
2349                                         scroll_steps -= view->height - 1;
2350                         }
2351                 }
2352         }
2354         if (!view_is_displayed(view)) {
2355                 view->offset += scroll_steps;
2356                 assert(0 <= view->offset && view->offset < view->lines);
2357                 view->ops->select(view, &view->line[view->lineno]);
2358                 return;
2359         }
2361         /* Repaint the old "current" line if we be scrolling */
2362         if (ABS(steps) < view->height)
2363                 draw_view_line(view, view->lineno - steps - view->offset);
2365         if (scroll_steps) {
2366                 do_scroll_view(view, scroll_steps);
2367                 return;
2368         }
2370         /* Draw the current line */
2371         draw_view_line(view, view->lineno - view->offset);
2373         redrawwin(view->win);
2374         wrefresh(view->win);
2375         report("");
2379 /*
2380  * Searching
2381  */
2383 static void search_view(struct view *view, enum request request);
2385 static bool
2386 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2388         assert(view_is_displayed(view));
2390         if (!view->ops->grep(view, line))
2391                 return FALSE;
2393         if (lineno - view->offset >= view->height) {
2394                 view->offset = lineno;
2395                 view->lineno = lineno;
2396                 redraw_view(view);
2398         } else {
2399                 unsigned long old_lineno = view->lineno - view->offset;
2401                 view->lineno = lineno;
2402                 draw_view_line(view, old_lineno);
2404                 draw_view_line(view, view->lineno - view->offset);
2405                 redrawwin(view->win);
2406                 wrefresh(view->win);
2407         }
2409         report("Line %ld matches '%s'", lineno + 1, view->grep);
2410         return TRUE;
2413 static void
2414 find_next(struct view *view, enum request request)
2416         unsigned long lineno = view->lineno;
2417         int direction;
2419         if (!*view->grep) {
2420                 if (!*opt_search)
2421                         report("No previous search");
2422                 else
2423                         search_view(view, request);
2424                 return;
2425         }
2427         switch (request) {
2428         case REQ_SEARCH:
2429         case REQ_FIND_NEXT:
2430                 direction = 1;
2431                 break;
2433         case REQ_SEARCH_BACK:
2434         case REQ_FIND_PREV:
2435                 direction = -1;
2436                 break;
2438         default:
2439                 return;
2440         }
2442         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2443                 lineno += direction;
2445         /* Note, lineno is unsigned long so will wrap around in which case it
2446          * will become bigger than view->lines. */
2447         for (; lineno < view->lines; lineno += direction) {
2448                 struct line *line = &view->line[lineno];
2450                 if (find_next_line(view, lineno, line))
2451                         return;
2452         }
2454         report("No match found for '%s'", view->grep);
2457 static void
2458 search_view(struct view *view, enum request request)
2460         int regex_err;
2462         if (view->regex) {
2463                 regfree(view->regex);
2464                 *view->grep = 0;
2465         } else {
2466                 view->regex = calloc(1, sizeof(*view->regex));
2467                 if (!view->regex)
2468                         return;
2469         }
2471         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2472         if (regex_err != 0) {
2473                 char buf[SIZEOF_STR] = "unknown error";
2475                 regerror(regex_err, view->regex, buf, sizeof(buf));
2476                 report("Search failed: %s", buf);
2477                 return;
2478         }
2480         string_copy(view->grep, opt_search);
2482         find_next(view, request);
2485 /*
2486  * Incremental updating
2487  */
2489 static void
2490 reset_view(struct view *view)
2492         int i;
2494         for (i = 0; i < view->lines; i++)
2495                 free(view->line[i].data);
2496         free(view->line);
2498         view->line = NULL;
2499         view->offset = 0;
2500         view->lines  = 0;
2501         view->lineno = 0;
2502         view->line_alloc = 0;
2503         view->vid[0] = 0;
2504         view->update_secs = 0;
2507 static void
2508 free_argv(const char *argv[])
2510         int argc;
2512         for (argc = 0; argv[argc]; argc++)
2513                 free((void *) argv[argc]);
2516 static bool
2517 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2519         char buf[SIZEOF_STR];
2520         int argc;
2521         bool noreplace = flags == FORMAT_NONE;
2523         free_argv(dst_argv);
2525         for (argc = 0; src_argv[argc]; argc++) {
2526                 const char *arg = src_argv[argc];
2527                 size_t bufpos = 0;
2529                 while (arg) {
2530                         char *next = strstr(arg, "%(");
2531                         int len = next - arg;
2532                         const char *value;
2534                         if (!next || noreplace) {
2535                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2536                                         noreplace = TRUE;
2537                                 len = strlen(arg);
2538                                 value = "";
2540                         } else if (!prefixcmp(next, "%(directory)")) {
2541                                 value = opt_path;
2543                         } else if (!prefixcmp(next, "%(file)")) {
2544                                 value = opt_file;
2546                         } else if (!prefixcmp(next, "%(ref)")) {
2547                                 value = *opt_ref ? opt_ref : "HEAD";
2549                         } else if (!prefixcmp(next, "%(head)")) {
2550                                 value = ref_head;
2552                         } else if (!prefixcmp(next, "%(commit)")) {
2553                                 value = ref_commit;
2555                         } else if (!prefixcmp(next, "%(blob)")) {
2556                                 value = ref_blob;
2558                         } else {
2559                                 report("Unknown replacement: `%s`", next);
2560                                 return FALSE;
2561                         }
2563                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2564                                 return FALSE;
2566                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2567                 }
2569                 dst_argv[argc] = strdup(buf);
2570                 if (!dst_argv[argc])
2571                         break;
2572         }
2574         dst_argv[argc] = NULL;
2576         return src_argv[argc] == NULL;
2579 static void
2580 end_update(struct view *view, bool force)
2582         if (!view->pipe)
2583                 return;
2584         while (!view->ops->read(view, NULL))
2585                 if (!force)
2586                         return;
2587         set_nonblocking_input(FALSE);
2588         if (force)
2589                 kill_io(view->pipe);
2590         done_io(view->pipe);
2591         view->pipe = NULL;
2594 static void
2595 setup_update(struct view *view, const char *vid)
2597         set_nonblocking_input(TRUE);
2598         reset_view(view);
2599         string_copy_rev(view->vid, vid);
2600         view->pipe = &view->io;
2601         view->start_time = time(NULL);
2604 static bool
2605 prepare_update(struct view *view, const char *argv[], const char *dir,
2606                enum format_flags flags)
2608         if (view->pipe)
2609                 end_update(view, TRUE);
2610         return init_io_rd(&view->io, argv, dir, flags);
2613 static bool
2614 prepare_update_file(struct view *view, const char *name)
2616         if (view->pipe)
2617                 end_update(view, TRUE);
2618         return io_open(&view->io, name);
2621 static bool
2622 begin_update(struct view *view, bool refresh)
2624         if (refresh) {
2625                 if (!start_io(&view->io))
2626                         return FALSE;
2628         } else {
2629                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2630                         opt_path[0] = 0;
2632                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2633                         return FALSE;
2635                 /* Put the current ref_* value to the view title ref
2636                  * member. This is needed by the blob view. Most other
2637                  * views sets it automatically after loading because the
2638                  * first line is a commit line. */
2639                 string_copy_rev(view->ref, view->id);
2640         }
2642         setup_update(view, view->id);
2644         return TRUE;
2647 #define ITEM_CHUNK_SIZE 256
2648 static void *
2649 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2651         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2652         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2654         if (mem == NULL || num_chunks != num_chunks_new) {
2655                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2656                 mem = realloc(mem, *size * item_size);
2657         }
2659         return mem;
2662 static struct line *
2663 realloc_lines(struct view *view, size_t line_size)
2665         size_t alloc = view->line_alloc;
2666         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2667                                          sizeof(*view->line));
2669         if (!tmp)
2670                 return NULL;
2672         view->line = tmp;
2673         view->line_alloc = alloc;
2674         return view->line;
2677 static bool
2678 update_view(struct view *view)
2680         char out_buffer[BUFSIZ * 2];
2681         char *line;
2682         int redraw_from = -1;
2683         bool can_read = TRUE;
2685         if (!view->pipe)
2686                 return TRUE;
2688         if (!io_can_read(view->pipe)) {
2689                 if (view->lines == 0) {
2690                         time_t secs = time(NULL) - view->start_time;
2692                         if (secs > view->update_secs) {
2693                                 if (view->update_secs == 0)
2694                                         redraw_view(view);
2695                                 update_view_title(view);
2696                                 view->update_secs = secs;
2697                         }
2698                 }
2699                 return TRUE;
2700         }
2702         /* Only redraw if lines are visible. */
2703         if (view->offset + view->height >= view->lines)
2704                 redraw_from = view->lines - view->offset;
2706         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2707                 size_t linelen = strlen(line);
2709                 if (opt_iconv != ICONV_NONE) {
2710                         ICONV_CONST char *inbuf = line;
2711                         size_t inlen = linelen;
2713                         char *outbuf = out_buffer;
2714                         size_t outlen = sizeof(out_buffer);
2716                         size_t ret;
2718                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2719                         if (ret != (size_t) -1) {
2720                                 line = out_buffer;
2721                                 linelen = strlen(out_buffer);
2722                         }
2723                 }
2725                 if (!view->ops->read(view, line))
2726                         goto alloc_error;
2727         }
2729         {
2730                 unsigned long lines = view->lines;
2731                 int digits;
2733                 for (digits = 0; lines; digits++)
2734                         lines /= 10;
2736                 /* Keep the displayed view in sync with line number scaling. */
2737                 if (digits != view->digits) {
2738                         view->digits = digits;
2739                         redraw_from = 0;
2740                 }
2741         }
2743         if (io_error(view->pipe)) {
2744                 report("Failed to read: %s", io_strerror(view->pipe));
2745                 end_update(view, TRUE);
2747         } else if (io_eof(view->pipe)) {
2748                 report("");
2749                 end_update(view, FALSE);
2750         }
2752         if (!view_is_displayed(view))
2753                 return TRUE;
2755         if (view == VIEW(REQ_VIEW_TREE)) {
2756                 /* Clear the view and redraw everything since the tree sorting
2757                  * might have rearranged things. */
2758                 redraw_view(view);
2760         } else if (redraw_from >= 0) {
2761                 /* If this is an incremental update, redraw the previous line
2762                  * since for commits some members could have changed when
2763                  * loading the main view. */
2764                 if (redraw_from > 0)
2765                         redraw_from--;
2767                 /* Since revision graph visualization requires knowledge
2768                  * about the parent commit, it causes a further one-off
2769                  * needed to be redrawn for incremental updates. */
2770                 if (redraw_from > 0 && opt_rev_graph)
2771                         redraw_from--;
2773                 /* Incrementally draw avoids flickering. */
2774                 redraw_view_from(view, redraw_from);
2775         }
2777         if (view == VIEW(REQ_VIEW_BLAME))
2778                 redraw_view_dirty(view);
2780         /* Update the title _after_ the redraw so that if the redraw picks up a
2781          * commit reference in view->ref it'll be available here. */
2782         update_view_title(view);
2783         return TRUE;
2785 alloc_error:
2786         report("Allocation failure");
2787         end_update(view, TRUE);
2788         return FALSE;
2791 static struct line *
2792 add_line_data(struct view *view, void *data, enum line_type type)
2794         struct line *line;
2796         if (!realloc_lines(view, view->lines + 1))
2797                 return NULL;
2799         line = &view->line[view->lines++];
2800         memset(line, 0, sizeof(*line));
2801         line->type = type;
2802         line->data = data;
2803         line->dirty = 1;
2805         return line;
2808 static struct line *
2809 add_line_text(struct view *view, const char *text, enum line_type type)
2811         char *data = text ? strdup(text) : NULL;
2813         return data ? add_line_data(view, data, type) : NULL;
2816 static struct line *
2817 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2819         char buf[SIZEOF_STR];
2820         va_list args;
2822         va_start(args, fmt);
2823         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2824                 buf[0] = 0;
2825         va_end(args);
2827         return buf[0] ? add_line_text(view, buf, type) : NULL;
2830 /*
2831  * View opening
2832  */
2834 enum open_flags {
2835         OPEN_DEFAULT = 0,       /* Use default view switching. */
2836         OPEN_SPLIT = 1,         /* Split current view. */
2837         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2838         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2839         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2840         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2841         OPEN_PREPARED = 32,     /* Open already prepared command. */
2842 };
2844 static void
2845 open_view(struct view *prev, enum request request, enum open_flags flags)
2847         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2848         bool split = !!(flags & OPEN_SPLIT);
2849         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2850         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2851         struct view *view = VIEW(request);
2852         int nviews = displayed_views();
2853         struct view *base_view = display[0];
2855         if (view == prev && nviews == 1 && !reload) {
2856                 report("Already in %s view", view->name);
2857                 return;
2858         }
2860         if (view->git_dir && !opt_git_dir[0]) {
2861                 report("The %s view is disabled in pager view", view->name);
2862                 return;
2863         }
2865         if (split) {
2866                 display[1] = view;
2867                 if (!backgrounded)
2868                         current_view = 1;
2869         } else if (!nomaximize) {
2870                 /* Maximize the current view. */
2871                 memset(display, 0, sizeof(display));
2872                 current_view = 0;
2873                 display[current_view] = view;
2874         }
2876         /* Resize the view when switching between split- and full-screen,
2877          * or when switching between two different full-screen views. */
2878         if (nviews != displayed_views() ||
2879             (nviews == 1 && base_view != display[0]))
2880                 resize_display();
2882         if (view->pipe)
2883                 end_update(view, TRUE);
2885         if (view->ops->open) {
2886                 if (!view->ops->open(view)) {
2887                         report("Failed to load %s view", view->name);
2888                         return;
2889                 }
2891         } else if ((reload || strcmp(view->vid, view->id)) &&
2892                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2893                 report("Failed to load %s view", view->name);
2894                 return;
2895         }
2897         if (split && prev->lineno - prev->offset >= prev->height) {
2898                 /* Take the title line into account. */
2899                 int lines = prev->lineno - prev->offset - prev->height + 1;
2901                 /* Scroll the view that was split if the current line is
2902                  * outside the new limited view. */
2903                 do_scroll_view(prev, lines);
2904         }
2906         if (prev && view != prev) {
2907                 if (split && !backgrounded) {
2908                         /* "Blur" the previous view. */
2909                         update_view_title(prev);
2910                 }
2912                 view->parent = prev;
2913         }
2915         if (view->pipe && view->lines == 0) {
2916                 /* Clear the old view and let the incremental updating refill
2917                  * the screen. */
2918                 werase(view->win);
2919                 report("");
2920         } else if (view_is_displayed(view)) {
2921                 redraw_view(view);
2922                 report("");
2923         }
2925         /* If the view is backgrounded the above calls to report()
2926          * won't redraw the view title. */
2927         if (backgrounded)
2928                 update_view_title(view);
2931 static void
2932 open_external_viewer(const char *argv[], const char *dir)
2934         def_prog_mode();           /* save current tty modes */
2935         endwin();                  /* restore original tty modes */
2936         run_io_fg(argv, dir);
2937         fprintf(stderr, "Press Enter to continue");
2938         getc(opt_tty);
2939         reset_prog_mode();
2940         redraw_display();
2943 static void
2944 open_mergetool(const char *file)
2946         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2948         open_external_viewer(mergetool_argv, opt_cdup);
2951 static void
2952 open_editor(bool from_root, const char *file)
2954         const char *editor_argv[] = { "vi", file, NULL };
2955         const char *editor;
2957         editor = getenv("GIT_EDITOR");
2958         if (!editor && *opt_editor)
2959                 editor = opt_editor;
2960         if (!editor)
2961                 editor = getenv("VISUAL");
2962         if (!editor)
2963                 editor = getenv("EDITOR");
2964         if (!editor)
2965                 editor = "vi";
2967         editor_argv[0] = editor;
2968         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2971 static void
2972 open_run_request(enum request request)
2974         struct run_request *req = get_run_request(request);
2975         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2977         if (!req) {
2978                 report("Unknown run request");
2979                 return;
2980         }
2982         if (format_argv(argv, req->argv, FORMAT_ALL))
2983                 open_external_viewer(argv, NULL);
2984         free_argv(argv);
2987 /*
2988  * User request switch noodle
2989  */
2991 static int
2992 view_driver(struct view *view, enum request request)
2994         int i;
2996         if (request == REQ_NONE) {
2997                 doupdate();
2998                 return TRUE;
2999         }
3001         if (request > REQ_NONE) {
3002                 open_run_request(request);
3003                 /* FIXME: When all views can refresh always do this. */
3004                 if (view == VIEW(REQ_VIEW_STATUS) ||
3005                     view == VIEW(REQ_VIEW_MAIN) ||
3006                     view == VIEW(REQ_VIEW_LOG) ||
3007                     view == VIEW(REQ_VIEW_STAGE))
3008                         request = REQ_REFRESH;
3009                 else
3010                         return TRUE;
3011         }
3013         if (view && view->lines) {
3014                 request = view->ops->request(view, request, &view->line[view->lineno]);
3015                 if (request == REQ_NONE)
3016                         return TRUE;
3017         }
3019         switch (request) {
3020         case REQ_MOVE_UP:
3021         case REQ_MOVE_DOWN:
3022         case REQ_MOVE_PAGE_UP:
3023         case REQ_MOVE_PAGE_DOWN:
3024         case REQ_MOVE_FIRST_LINE:
3025         case REQ_MOVE_LAST_LINE:
3026                 move_view(view, request);
3027                 break;
3029         case REQ_SCROLL_LINE_DOWN:
3030         case REQ_SCROLL_LINE_UP:
3031         case REQ_SCROLL_PAGE_DOWN:
3032         case REQ_SCROLL_PAGE_UP:
3033                 scroll_view(view, request);
3034                 break;
3036         case REQ_VIEW_BLAME:
3037                 if (!opt_file[0]) {
3038                         report("No file chosen, press %s to open tree view",
3039                                get_key(REQ_VIEW_TREE));
3040                         break;
3041                 }
3042                 open_view(view, request, OPEN_DEFAULT);
3043                 break;
3045         case REQ_VIEW_BLOB:
3046                 if (!ref_blob[0]) {
3047                         report("No file chosen, press %s to open tree view",
3048                                get_key(REQ_VIEW_TREE));
3049                         break;
3050                 }
3051                 open_view(view, request, OPEN_DEFAULT);
3052                 break;
3054         case REQ_VIEW_PAGER:
3055                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3056                         report("No pager content, press %s to run command from prompt",
3057                                get_key(REQ_PROMPT));
3058                         break;
3059                 }
3060                 open_view(view, request, OPEN_DEFAULT);
3061                 break;
3063         case REQ_VIEW_STAGE:
3064                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3065                         report("No stage content, press %s to open the status view and choose file",
3066                                get_key(REQ_VIEW_STATUS));
3067                         break;
3068                 }
3069                 open_view(view, request, OPEN_DEFAULT);
3070                 break;
3072         case REQ_VIEW_STATUS:
3073                 if (opt_is_inside_work_tree == FALSE) {
3074                         report("The status view requires a working tree");
3075                         break;
3076                 }
3077                 open_view(view, request, OPEN_DEFAULT);
3078                 break;
3080         case REQ_VIEW_MAIN:
3081         case REQ_VIEW_DIFF:
3082         case REQ_VIEW_LOG:
3083         case REQ_VIEW_TREE:
3084         case REQ_VIEW_HELP:
3085                 open_view(view, request, OPEN_DEFAULT);
3086                 break;
3088         case REQ_NEXT:
3089         case REQ_PREVIOUS:
3090                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3092                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3093                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3094                    (view == VIEW(REQ_VIEW_DIFF) &&
3095                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3096                    (view == VIEW(REQ_VIEW_STAGE) &&
3097                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3098                    (view == VIEW(REQ_VIEW_BLOB) &&
3099                      view->parent == VIEW(REQ_VIEW_TREE))) {
3100                         int line;
3102                         view = view->parent;
3103                         line = view->lineno;
3104                         move_view(view, request);
3105                         if (view_is_displayed(view))
3106                                 update_view_title(view);
3107                         if (line != view->lineno)
3108                                 view->ops->request(view, REQ_ENTER,
3109                                                    &view->line[view->lineno]);
3111                 } else {
3112                         move_view(view, request);
3113                 }
3114                 break;
3116         case REQ_VIEW_NEXT:
3117         {
3118                 int nviews = displayed_views();
3119                 int next_view = (current_view + 1) % nviews;
3121                 if (next_view == current_view) {
3122                         report("Only one view is displayed");
3123                         break;
3124                 }
3126                 current_view = next_view;
3127                 /* Blur out the title of the previous view. */
3128                 update_view_title(view);
3129                 report("");
3130                 break;
3131         }
3132         case REQ_REFRESH:
3133                 report("Refreshing is not yet supported for the %s view", view->name);
3134                 break;
3136         case REQ_MAXIMIZE:
3137                 if (displayed_views() == 2)
3138                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3139                 break;
3141         case REQ_TOGGLE_LINENO:
3142                 opt_line_number = !opt_line_number;
3143                 redraw_display();
3144                 break;
3146         case REQ_TOGGLE_DATE:
3147                 opt_date = !opt_date;
3148                 redraw_display();
3149                 break;
3151         case REQ_TOGGLE_AUTHOR:
3152                 opt_author = !opt_author;
3153                 redraw_display();
3154                 break;
3156         case REQ_TOGGLE_REV_GRAPH:
3157                 opt_rev_graph = !opt_rev_graph;
3158                 redraw_display();
3159                 break;
3161         case REQ_TOGGLE_REFS:
3162                 opt_show_refs = !opt_show_refs;
3163                 redraw_display();
3164                 break;
3166         case REQ_SEARCH:
3167         case REQ_SEARCH_BACK:
3168                 search_view(view, request);
3169                 break;
3171         case REQ_FIND_NEXT:
3172         case REQ_FIND_PREV:
3173                 find_next(view, request);
3174                 break;
3176         case REQ_STOP_LOADING:
3177                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3178                         view = &views[i];
3179                         if (view->pipe)
3180                                 report("Stopped loading the %s view", view->name),
3181                         end_update(view, TRUE);
3182                 }
3183                 break;
3185         case REQ_SHOW_VERSION:
3186                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3187                 return TRUE;
3189         case REQ_SCREEN_RESIZE:
3190                 resize_display();
3191                 /* Fall-through */
3192         case REQ_SCREEN_REDRAW:
3193                 redraw_display();
3194                 break;
3196         case REQ_EDIT:
3197                 report("Nothing to edit");
3198                 break;
3200         case REQ_ENTER:
3201                 report("Nothing to enter");
3202                 break;
3204         case REQ_VIEW_CLOSE:
3205                 /* XXX: Mark closed views by letting view->parent point to the
3206                  * view itself. Parents to closed view should never be
3207                  * followed. */
3208                 if (view->parent &&
3209                     view->parent->parent != view->parent) {
3210                         memset(display, 0, sizeof(display));
3211                         current_view = 0;
3212                         display[current_view] = view->parent;
3213                         view->parent = view;
3214                         resize_display();
3215                         redraw_display();
3216                         report("");
3217                         break;
3218                 }
3219                 /* Fall-through */
3220         case REQ_QUIT:
3221                 return FALSE;
3223         default:
3224                 report("Unknown key, press 'h' for help");
3225                 return TRUE;
3226         }
3228         return TRUE;
3232 /*
3233  * Pager backend
3234  */
3236 static bool
3237 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3239         char *text = line->data;
3241         if (opt_line_number && draw_lineno(view, lineno))
3242                 return TRUE;
3244         draw_text(view, line->type, text, TRUE);
3245         return TRUE;
3248 static bool
3249 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3251         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3252         char refbuf[SIZEOF_STR];
3253         char *ref = NULL;
3255         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3256                 ref = chomp_string(refbuf);
3258         if (!ref || !*ref)
3259                 return TRUE;
3261         /* This is the only fatal call, since it can "corrupt" the buffer. */
3262         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3263                 return FALSE;
3265         return TRUE;
3268 static void
3269 add_pager_refs(struct view *view, struct line *line)
3271         char buf[SIZEOF_STR];
3272         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3273         struct ref **refs;
3274         size_t bufpos = 0, refpos = 0;
3275         const char *sep = "Refs: ";
3276         bool is_tag = FALSE;
3278         assert(line->type == LINE_COMMIT);
3280         refs = get_refs(commit_id);
3281         if (!refs) {
3282                 if (view == VIEW(REQ_VIEW_DIFF))
3283                         goto try_add_describe_ref;
3284                 return;
3285         }
3287         do {
3288                 struct ref *ref = refs[refpos];
3289                 const char *fmt = ref->tag    ? "%s[%s]" :
3290                                   ref->remote ? "%s<%s>" : "%s%s";
3292                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3293                         return;
3294                 sep = ", ";
3295                 if (ref->tag)
3296                         is_tag = TRUE;
3297         } while (refs[refpos++]->next);
3299         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3300 try_add_describe_ref:
3301                 /* Add <tag>-g<commit_id> "fake" reference. */
3302                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3303                         return;
3304         }
3306         if (bufpos == 0)
3307                 return;
3309         add_line_text(view, buf, LINE_PP_REFS);
3312 static bool
3313 pager_read(struct view *view, char *data)
3315         struct line *line;
3317         if (!data)
3318                 return TRUE;
3320         line = add_line_text(view, data, get_line_type(data));
3321         if (!line)
3322                 return FALSE;
3324         if (line->type == LINE_COMMIT &&
3325             (view == VIEW(REQ_VIEW_DIFF) ||
3326              view == VIEW(REQ_VIEW_LOG)))
3327                 add_pager_refs(view, line);
3329         return TRUE;
3332 static enum request
3333 pager_request(struct view *view, enum request request, struct line *line)
3335         int split = 0;
3337         if (request != REQ_ENTER)
3338                 return request;
3340         if (line->type == LINE_COMMIT &&
3341            (view == VIEW(REQ_VIEW_LOG) ||
3342             view == VIEW(REQ_VIEW_PAGER))) {
3343                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3344                 split = 1;
3345         }
3347         /* Always scroll the view even if it was split. That way
3348          * you can use Enter to scroll through the log view and
3349          * split open each commit diff. */
3350         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3352         /* FIXME: A minor workaround. Scrolling the view will call report("")
3353          * but if we are scrolling a non-current view this won't properly
3354          * update the view title. */
3355         if (split)
3356                 update_view_title(view);
3358         return REQ_NONE;
3361 static bool
3362 pager_grep(struct view *view, struct line *line)
3364         regmatch_t pmatch;
3365         char *text = line->data;
3367         if (!*text)
3368                 return FALSE;
3370         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3371                 return FALSE;
3373         return TRUE;
3376 static void
3377 pager_select(struct view *view, struct line *line)
3379         if (line->type == LINE_COMMIT) {
3380                 char *text = (char *)line->data + STRING_SIZE("commit ");
3382                 if (view != VIEW(REQ_VIEW_PAGER))
3383                         string_copy_rev(view->ref, text);
3384                 string_copy_rev(ref_commit, text);
3385         }
3388 static struct view_ops pager_ops = {
3389         "line",
3390         NULL,
3391         NULL,
3392         pager_read,
3393         pager_draw,
3394         pager_request,
3395         pager_grep,
3396         pager_select,
3397 };
3399 static const char *log_argv[SIZEOF_ARG] = {
3400         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3401 };
3403 static enum request
3404 log_request(struct view *view, enum request request, struct line *line)
3406         switch (request) {
3407         case REQ_REFRESH:
3408                 load_refs();
3409                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3410                 return REQ_NONE;
3411         default:
3412                 return pager_request(view, request, line);
3413         }
3416 static struct view_ops log_ops = {
3417         "line",
3418         log_argv,
3419         NULL,
3420         pager_read,
3421         pager_draw,
3422         log_request,
3423         pager_grep,
3424         pager_select,
3425 };
3427 static const char *diff_argv[SIZEOF_ARG] = {
3428         "git", "show", "--pretty=fuller", "--no-color", "--root",
3429                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3430 };
3432 static struct view_ops diff_ops = {
3433         "line",
3434         diff_argv,
3435         NULL,
3436         pager_read,
3437         pager_draw,
3438         pager_request,
3439         pager_grep,
3440         pager_select,
3441 };
3443 /*
3444  * Help backend
3445  */
3447 static bool
3448 help_open(struct view *view)
3450         int lines = ARRAY_SIZE(req_info) + 2;
3451         int i;
3453         if (view->lines > 0)
3454                 return TRUE;
3456         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3457                 if (!req_info[i].request)
3458                         lines++;
3460         lines += run_requests + 1;
3462         view->line = calloc(lines, sizeof(*view->line));
3463         if (!view->line)
3464                 return FALSE;
3466         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3468         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3469                 const char *key;
3471                 if (req_info[i].request == REQ_NONE)
3472                         continue;
3474                 if (!req_info[i].request) {
3475                         add_line_text(view, "", LINE_DEFAULT);
3476                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3477                         continue;
3478                 }
3480                 key = get_key(req_info[i].request);
3481                 if (!*key)
3482                         key = "(no key defined)";
3484                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3485                                 key, req_info[i].help);
3486         }
3488         if (run_requests) {
3489                 add_line_text(view, "", LINE_DEFAULT);
3490                 add_line_text(view, "External commands:", LINE_DEFAULT);
3491         }
3493         for (i = 0; i < run_requests; i++) {
3494                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3495                 const char *key;
3496                 char cmd[SIZEOF_STR];
3497                 size_t bufpos;
3498                 int argc;
3500                 if (!req)
3501                         continue;
3503                 key = get_key_name(req->key);
3504                 if (!*key)
3505                         key = "(no key defined)";
3507                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3508                         if (!string_format_from(cmd, &bufpos, "%s%s",
3509                                                 argc ? " " : "", req->argv[argc]))
3510                                 return REQ_NONE;
3512                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3513                                 keymap_table[req->keymap].name, key, cmd);
3514         }
3516         return TRUE;
3519 static struct view_ops help_ops = {
3520         "line",
3521         NULL,
3522         help_open,
3523         NULL,
3524         pager_draw,
3525         pager_request,
3526         pager_grep,
3527         pager_select,
3528 };
3531 /*
3532  * Tree backend
3533  */
3535 struct tree_stack_entry {
3536         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3537         unsigned long lineno;           /* Line number to restore */
3538         char *name;                     /* Position of name in opt_path */
3539 };
3541 /* The top of the path stack. */
3542 static struct tree_stack_entry *tree_stack = NULL;
3543 unsigned long tree_lineno = 0;
3545 static void
3546 pop_tree_stack_entry(void)
3548         struct tree_stack_entry *entry = tree_stack;
3550         tree_lineno = entry->lineno;
3551         entry->name[0] = 0;
3552         tree_stack = entry->prev;
3553         free(entry);
3556 static void
3557 push_tree_stack_entry(const char *name, unsigned long lineno)
3559         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3560         size_t pathlen = strlen(opt_path);
3562         if (!entry)
3563                 return;
3565         entry->prev = tree_stack;
3566         entry->name = opt_path + pathlen;
3567         tree_stack = entry;
3569         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3570                 pop_tree_stack_entry();
3571                 return;
3572         }
3574         /* Move the current line to the first tree entry. */
3575         tree_lineno = 1;
3576         entry->lineno = lineno;
3579 /* Parse output from git-ls-tree(1):
3580  *
3581  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3582  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3583  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3584  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3585  */
3587 #define SIZEOF_TREE_ATTR \
3588         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3590 #define TREE_UP_FORMAT "040000 tree %s\t.."
3592 static int
3593 tree_compare_entry(enum line_type type1, const char *name1,
3594                    enum line_type type2, const char *name2)
3596         if (type1 != type2) {
3597                 if (type1 == LINE_TREE_DIR)
3598                         return -1;
3599                 return 1;
3600         }
3602         return strcmp(name1, name2);
3605 static const char *
3606 tree_path(struct line *line)
3608         const char *path = line->data;
3610         return path + SIZEOF_TREE_ATTR;
3613 static bool
3614 tree_read(struct view *view, char *text)
3616         size_t textlen = text ? strlen(text) : 0;
3617         size_t pos;
3618         enum line_type type;
3620         if (!text)
3621                 return TRUE;
3622         if (textlen <= SIZEOF_TREE_ATTR)
3623                 return FALSE;
3625         type = text[STRING_SIZE("100644 ")] == 't'
3626              ? LINE_TREE_DIR : LINE_TREE_FILE;
3628         if (view->lines == 0 &&
3629             !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
3630                 return FALSE;
3632         /* Strip the path part ... */
3633         if (*opt_path) {
3634                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3635                 size_t striplen = strlen(opt_path);
3636                 char *path = text + SIZEOF_TREE_ATTR;
3638                 if (pathlen > striplen)
3639                         memmove(path, path + striplen,
3640                                 pathlen - striplen + 1);
3642                 /* Insert "link" to parent directory. */
3643                 if (view->lines == 1 &&
3644                     !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
3645                         return FALSE;
3646         }
3648         if (!add_line_text(view, text, type))
3649                 return FALSE;
3650         text = view->line[view->lines - 1].data;
3652         /* Skip "Directory ..." and ".." line. */
3653         for (pos = 1 + !!*opt_path; pos < view->lines - 1; pos++) {
3654                 struct line *line = &view->line[pos];
3655                 const char *path1 = tree_path(line);
3656                 char *path2 = text + SIZEOF_TREE_ATTR;
3657                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3659                 if (cmp <= 0)
3660                         continue;
3662                 if (view->lines - 1 > pos)
3663                         memmove(&view->line[pos + 1], &view->line[pos],
3664                                 (view->lines - 1 - pos) * sizeof(*line));
3666                 line = &view->line[pos];
3667                 line->data = text;
3668                 line->type = type;
3669                 return TRUE;
3670         }
3672         if (tree_lineno > view->lineno) {
3673                 view->lineno = tree_lineno;
3674                 tree_lineno = 0;
3675         }
3677         return TRUE;
3680 static enum request
3681 tree_request(struct view *view, enum request request, struct line *line)
3683         enum open_flags flags;
3685         switch (request) {
3686         case REQ_VIEW_BLAME:
3687                 if (line->type != LINE_TREE_FILE) {
3688                         report("Blame only supported for files");
3689                         return REQ_NONE;
3690                 }
3692                 string_copy(opt_ref, view->vid);
3693                 return request;
3695         case REQ_EDIT:
3696                 if (line->type != LINE_TREE_FILE) {
3697                         report("Edit only supported for files");
3698                 } else if (!is_head_commit(view->vid)) {
3699                         report("Edit only supported for files in the current work tree");
3700                 } else {
3701                         open_editor(TRUE, opt_file);
3702                 }
3703                 return REQ_NONE;
3705         case REQ_TREE_PARENT:
3706                 if (!*opt_path) {
3707                         /* quit view if at top of tree */
3708                         return REQ_VIEW_CLOSE;
3709                 }
3710                 /* fake 'cd  ..' */
3711                 line = &view->line[1];
3712                 break;
3714         case REQ_ENTER:
3715                 break;
3717         default:
3718                 return request;
3719         }
3721         /* Cleanup the stack if the tree view is at a different tree. */
3722         while (!*opt_path && tree_stack)
3723                 pop_tree_stack_entry();
3725         switch (line->type) {
3726         case LINE_TREE_DIR:
3727                 /* Depending on whether it is a subdir or parent (updir?) link
3728                  * mangle the path buffer. */
3729                 if (line == &view->line[1] && *opt_path) {
3730                         pop_tree_stack_entry();
3732                 } else {
3733                         const char *basename = tree_path(line);
3735                         push_tree_stack_entry(basename, view->lineno);
3736                 }
3738                 /* Trees and subtrees share the same ID, so they are not not
3739                  * unique like blobs. */
3740                 flags = OPEN_RELOAD;
3741                 request = REQ_VIEW_TREE;
3742                 break;
3744         case LINE_TREE_FILE:
3745                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3746                 request = REQ_VIEW_BLOB;
3747                 break;
3749         default:
3750                 return TRUE;
3751         }
3753         open_view(view, request, flags);
3754         if (request == REQ_VIEW_TREE) {
3755                 view->lineno = tree_lineno;
3756         }
3758         return REQ_NONE;
3761 static void
3762 tree_select(struct view *view, struct line *line)
3764         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3766         if (line->type == LINE_TREE_FILE) {
3767                 string_copy_rev(ref_blob, text);
3768                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3770         } else if (line->type != LINE_TREE_DIR) {
3771                 return;
3772         }
3774         string_copy_rev(view->ref, text);
3777 static const char *tree_argv[SIZEOF_ARG] = {
3778         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3779 };
3781 static struct view_ops tree_ops = {
3782         "file",
3783         tree_argv,
3784         NULL,
3785         tree_read,
3786         pager_draw,
3787         tree_request,
3788         pager_grep,
3789         tree_select,
3790 };
3792 static bool
3793 blob_read(struct view *view, char *line)
3795         if (!line)
3796                 return TRUE;
3797         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3800 static const char *blob_argv[SIZEOF_ARG] = {
3801         "git", "cat-file", "blob", "%(blob)", NULL
3802 };
3804 static struct view_ops blob_ops = {
3805         "line",
3806         blob_argv,
3807         NULL,
3808         blob_read,
3809         pager_draw,
3810         pager_request,
3811         pager_grep,
3812         pager_select,
3813 };
3815 /*
3816  * Blame backend
3817  *
3818  * Loading the blame view is a two phase job:
3819  *
3820  *  1. File content is read either using opt_file from the
3821  *     filesystem or using git-cat-file.
3822  *  2. Then blame information is incrementally added by
3823  *     reading output from git-blame.
3824  */
3826 static const char *blame_head_argv[] = {
3827         "git", "blame", "--incremental", "--", "%(file)", NULL
3828 };
3830 static const char *blame_ref_argv[] = {
3831         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3832 };
3834 static const char *blame_cat_file_argv[] = {
3835         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3836 };
3838 struct blame_commit {
3839         char id[SIZEOF_REV];            /* SHA1 ID. */
3840         char title[128];                /* First line of the commit message. */
3841         char author[75];                /* Author of the commit. */
3842         struct tm time;                 /* Date from the author ident. */
3843         char filename[128];             /* Name of file. */
3844 };
3846 struct blame {
3847         struct blame_commit *commit;
3848         char text[1];
3849 };
3851 static bool
3852 blame_open(struct view *view)
3854         if (*opt_ref || !io_open(&view->io, opt_file)) {
3855                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3856                         return FALSE;
3857         }
3859         setup_update(view, opt_file);
3860         string_format(view->ref, "%s ...", opt_file);
3862         return TRUE;
3865 static struct blame_commit *
3866 get_blame_commit(struct view *view, const char *id)
3868         size_t i;
3870         for (i = 0; i < view->lines; i++) {
3871                 struct blame *blame = view->line[i].data;
3873                 if (!blame->commit)
3874                         continue;
3876                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3877                         return blame->commit;
3878         }
3880         {
3881                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3883                 if (commit)
3884                         string_ncopy(commit->id, id, SIZEOF_REV);
3885                 return commit;
3886         }
3889 static bool
3890 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3892         const char *pos = *posref;
3894         *posref = NULL;
3895         pos = strchr(pos + 1, ' ');
3896         if (!pos || !isdigit(pos[1]))
3897                 return FALSE;
3898         *number = atoi(pos + 1);
3899         if (*number < min || *number > max)
3900                 return FALSE;
3902         *posref = pos;
3903         return TRUE;
3906 static struct blame_commit *
3907 parse_blame_commit(struct view *view, const char *text, int *blamed)
3909         struct blame_commit *commit;
3910         struct blame *blame;
3911         const char *pos = text + SIZEOF_REV - 1;
3912         size_t lineno;
3913         size_t group;
3915         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3916                 return NULL;
3918         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3919             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3920                 return NULL;
3922         commit = get_blame_commit(view, text);
3923         if (!commit)
3924                 return NULL;
3926         *blamed += group;
3927         while (group--) {
3928                 struct line *line = &view->line[lineno + group - 1];
3930                 blame = line->data;
3931                 blame->commit = commit;
3932                 line->dirty = 1;
3933         }
3935         return commit;
3938 static bool
3939 blame_read_file(struct view *view, const char *line, bool *read_file)
3941         if (!line) {
3942                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3943                 struct io io = {};
3945                 if (view->lines == 0 && !view->parent)
3946                         die("No blame exist for %s", view->vid);
3948                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3949                         report("Failed to load blame data");
3950                         return TRUE;
3951                 }
3953                 done_io(view->pipe);
3954                 view->io = io;
3955                 *read_file = FALSE;
3956                 return FALSE;
3958         } else {
3959                 size_t linelen = strlen(line);
3960                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3962                 blame->commit = NULL;
3963                 strncpy(blame->text, line, linelen);
3964                 blame->text[linelen] = 0;
3965                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3966         }
3969 static bool
3970 match_blame_header(const char *name, char **line)
3972         size_t namelen = strlen(name);
3973         bool matched = !strncmp(name, *line, namelen);
3975         if (matched)
3976                 *line += namelen;
3978         return matched;
3981 static bool
3982 blame_read(struct view *view, char *line)
3984         static struct blame_commit *commit = NULL;
3985         static int blamed = 0;
3986         static time_t author_time;
3987         static bool read_file = TRUE;
3989         if (read_file)
3990                 return blame_read_file(view, line, &read_file);
3992         if (!line) {
3993                 /* Reset all! */
3994                 commit = NULL;
3995                 blamed = 0;
3996                 read_file = TRUE;
3997                 string_format(view->ref, "%s", view->vid);
3998                 if (view_is_displayed(view)) {
3999                         update_view_title(view);
4000                         redraw_view_from(view, 0);
4001                 }
4002                 return TRUE;
4003         }
4005         if (!commit) {
4006                 commit = parse_blame_commit(view, line, &blamed);
4007                 string_format(view->ref, "%s %2d%%", view->vid,
4008                               blamed * 100 / view->lines);
4010         } else if (match_blame_header("author ", &line)) {
4011                 string_ncopy(commit->author, line, strlen(line));
4013         } else if (match_blame_header("author-time ", &line)) {
4014                 author_time = (time_t) atol(line);
4016         } else if (match_blame_header("author-tz ", &line)) {
4017                 long tz;
4019                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4020                 tz += ('0' - line[2]) * 60 * 60;
4021                 tz += ('0' - line[3]) * 60;
4022                 tz += ('0' - line[4]) * 60;
4024                 if (line[0] == '-')
4025                         tz = -tz;
4027                 author_time -= tz;
4028                 gmtime_r(&author_time, &commit->time);
4030         } else if (match_blame_header("summary ", &line)) {
4031                 string_ncopy(commit->title, line, strlen(line));
4033         } else if (match_blame_header("filename ", &line)) {
4034                 string_ncopy(commit->filename, line, strlen(line));
4035                 commit = NULL;
4036         }
4038         return TRUE;
4041 static bool
4042 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4044         struct blame *blame = line->data;
4045         struct tm *time = NULL;
4046         const char *id = NULL, *author = NULL;
4048         if (blame->commit && *blame->commit->filename) {
4049                 id = blame->commit->id;
4050                 author = blame->commit->author;
4051                 time = &blame->commit->time;
4052         }
4054         if (opt_date && draw_date(view, time))
4055                 return TRUE;
4057         if (opt_author &&
4058             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4059                 return TRUE;
4061         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4062                 return TRUE;
4064         if (draw_lineno(view, lineno))
4065                 return TRUE;
4067         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4068         return TRUE;
4071 static enum request
4072 blame_request(struct view *view, enum request request, struct line *line)
4074         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4075         struct blame *blame = line->data;
4077         switch (request) {
4078         case REQ_VIEW_BLAME:
4079                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4080                         report("Commit ID unknown");
4081                         break;
4082                 }
4083                 string_copy(opt_ref, blame->commit->id);
4084                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4085                 return request;
4087         case REQ_ENTER:
4088                 if (!blame->commit) {
4089                         report("No commit loaded yet");
4090                         break;
4091                 }
4093                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4094                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4095                         break;
4097                 if (!strcmp(blame->commit->id, NULL_ID)) {
4098                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4099                         const char *diff_index_argv[] = {
4100                                 "git", "diff-index", "--root", "--cached",
4101                                         "--patch-with-stat", "-C", "-M",
4102                                         "HEAD", "--", view->vid, NULL
4103                         };
4105                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4106                                 report("Failed to allocate diff command");
4107                                 break;
4108                         }
4109                         flags |= OPEN_PREPARED;
4110                 }
4112                 open_view(view, REQ_VIEW_DIFF, flags);
4113                 break;
4115         default:
4116                 return request;
4117         }
4119         return REQ_NONE;
4122 static bool
4123 blame_grep(struct view *view, struct line *line)
4125         struct blame *blame = line->data;
4126         struct blame_commit *commit = blame->commit;
4127         regmatch_t pmatch;
4129 #define MATCH(text, on)                                                 \
4130         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4132         if (commit) {
4133                 char buf[DATE_COLS + 1];
4135                 if (MATCH(commit->title, 1) ||
4136                     MATCH(commit->author, opt_author) ||
4137                     MATCH(commit->id, opt_date))
4138                         return TRUE;
4140                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4141                     MATCH(buf, 1))
4142                         return TRUE;
4143         }
4145         return MATCH(blame->text, 1);
4147 #undef MATCH
4150 static void
4151 blame_select(struct view *view, struct line *line)
4153         struct blame *blame = line->data;
4154         struct blame_commit *commit = blame->commit;
4156         if (!commit)
4157                 return;
4159         if (!strcmp(commit->id, NULL_ID))
4160                 string_ncopy(ref_commit, "HEAD", 4);
4161         else
4162                 string_copy_rev(ref_commit, commit->id);
4165 static struct view_ops blame_ops = {
4166         "line",
4167         NULL,
4168         blame_open,
4169         blame_read,
4170         blame_draw,
4171         blame_request,
4172         blame_grep,
4173         blame_select,
4174 };
4176 /*
4177  * Status backend
4178  */
4180 struct status {
4181         char status;
4182         struct {
4183                 mode_t mode;
4184                 char rev[SIZEOF_REV];
4185                 char name[SIZEOF_STR];
4186         } old;
4187         struct {
4188                 mode_t mode;
4189                 char rev[SIZEOF_REV];
4190                 char name[SIZEOF_STR];
4191         } new;
4192 };
4194 static char status_onbranch[SIZEOF_STR];
4195 static struct status stage_status;
4196 static enum line_type stage_line_type;
4197 static size_t stage_chunks;
4198 static int *stage_chunk;
4200 /* This should work even for the "On branch" line. */
4201 static inline bool
4202 status_has_none(struct view *view, struct line *line)
4204         return line < view->line + view->lines && !line[1].data;
4207 /* Get fields from the diff line:
4208  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4209  */
4210 static inline bool
4211 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4213         const char *old_mode = buf +  1;
4214         const char *new_mode = buf +  8;
4215         const char *old_rev  = buf + 15;
4216         const char *new_rev  = buf + 56;
4217         const char *status   = buf + 97;
4219         if (bufsize < 98 ||
4220             old_mode[-1] != ':' ||
4221             new_mode[-1] != ' ' ||
4222             old_rev[-1]  != ' ' ||
4223             new_rev[-1]  != ' ' ||
4224             status[-1]   != ' ')
4225                 return FALSE;
4227         file->status = *status;
4229         string_copy_rev(file->old.rev, old_rev);
4230         string_copy_rev(file->new.rev, new_rev);
4232         file->old.mode = strtoul(old_mode, NULL, 8);
4233         file->new.mode = strtoul(new_mode, NULL, 8);
4235         file->old.name[0] = file->new.name[0] = 0;
4237         return TRUE;
4240 static bool
4241 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4243         struct status *file = NULL;
4244         struct status *unmerged = NULL;
4245         char *buf;
4246         struct io io = {};
4248         if (!run_io(&io, argv, NULL, IO_RD))
4249                 return FALSE;
4251         add_line_data(view, NULL, type);
4253         while ((buf = io_get(&io, 0, TRUE))) {
4254                 if (!file) {
4255                         file = calloc(1, sizeof(*file));
4256                         if (!file || !add_line_data(view, file, type))
4257                                 goto error_out;
4258                 }
4260                 /* Parse diff info part. */
4261                 if (status) {
4262                         file->status = status;
4263                         if (status == 'A')
4264                                 string_copy(file->old.rev, NULL_ID);
4266                 } else if (!file->status) {
4267                         if (!status_get_diff(file, buf, strlen(buf)))
4268                                 goto error_out;
4270                         buf = io_get(&io, 0, TRUE);
4271                         if (!buf)
4272                                 break;
4274                         /* Collapse all 'M'odified entries that follow a
4275                          * associated 'U'nmerged entry. */
4276                         if (file->status == 'U') {
4277                                 unmerged = file;
4279                         } else if (unmerged) {
4280                                 int collapse = !strcmp(buf, unmerged->new.name);
4282                                 unmerged = NULL;
4283                                 if (collapse) {
4284                                         free(file);
4285                                         view->lines--;
4286                                         continue;
4287                                 }
4288                         }
4289                 }
4291                 /* Grab the old name for rename/copy. */
4292                 if (!*file->old.name &&
4293                     (file->status == 'R' || file->status == 'C')) {
4294                         string_ncopy(file->old.name, buf, strlen(buf));
4296                         buf = io_get(&io, 0, TRUE);
4297                         if (!buf)
4298                                 break;
4299                 }
4301                 /* git-ls-files just delivers a NUL separated list of
4302                  * file names similar to the second half of the
4303                  * git-diff-* output. */
4304                 string_ncopy(file->new.name, buf, strlen(buf));
4305                 if (!*file->old.name)
4306                         string_copy(file->old.name, file->new.name);
4307                 file = NULL;
4308         }
4310         if (io_error(&io)) {
4311 error_out:
4312                 done_io(&io);
4313                 return FALSE;
4314         }
4316         if (!view->line[view->lines - 1].data)
4317                 add_line_data(view, NULL, LINE_STAT_NONE);
4319         done_io(&io);
4320         return TRUE;
4323 /* Don't show unmerged entries in the staged section. */
4324 static const char *status_diff_index_argv[] = {
4325         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4326                              "--cached", "-M", "HEAD", NULL
4327 };
4329 static const char *status_diff_files_argv[] = {
4330         "git", "diff-files", "-z", NULL
4331 };
4333 static const char *status_list_other_argv[] = {
4334         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4335 };
4337 static const char *status_list_no_head_argv[] = {
4338         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4339 };
4341 static const char *update_index_argv[] = {
4342         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4343 };
4345 /* First parse staged info using git-diff-index(1), then parse unstaged
4346  * info using git-diff-files(1), and finally untracked files using
4347  * git-ls-files(1). */
4348 static bool
4349 status_open(struct view *view)
4351         unsigned long prev_lineno = view->lineno;
4353         reset_view(view);
4355         add_line_data(view, NULL, LINE_STAT_HEAD);
4356         if (is_initial_commit())
4357                 string_copy(status_onbranch, "Initial commit");
4358         else if (!*opt_head)
4359                 string_copy(status_onbranch, "Not currently on any branch");
4360         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4361                 return FALSE;
4363         run_io_bg(update_index_argv);
4365         if (is_initial_commit()) {
4366                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4367                         return FALSE;
4368         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4369                 return FALSE;
4370         }
4372         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4373             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4374                 return FALSE;
4376         /* If all went well restore the previous line number to stay in
4377          * the context or select a line with something that can be
4378          * updated. */
4379         if (prev_lineno >= view->lines)
4380                 prev_lineno = view->lines - 1;
4381         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4382                 prev_lineno++;
4383         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4384                 prev_lineno--;
4386         /* If the above fails, always skip the "On branch" line. */
4387         if (prev_lineno < view->lines)
4388                 view->lineno = prev_lineno;
4389         else
4390                 view->lineno = 1;
4392         if (view->lineno < view->offset)
4393                 view->offset = view->lineno;
4394         else if (view->offset + view->height <= view->lineno)
4395                 view->offset = view->lineno - view->height + 1;
4397         return TRUE;
4400 static bool
4401 status_draw(struct view *view, struct line *line, unsigned int lineno)
4403         struct status *status = line->data;
4404         enum line_type type;
4405         const char *text;
4407         if (!status) {
4408                 switch (line->type) {
4409                 case LINE_STAT_STAGED:
4410                         type = LINE_STAT_SECTION;
4411                         text = "Changes to be committed:";
4412                         break;
4414                 case LINE_STAT_UNSTAGED:
4415                         type = LINE_STAT_SECTION;
4416                         text = "Changed but not updated:";
4417                         break;
4419                 case LINE_STAT_UNTRACKED:
4420                         type = LINE_STAT_SECTION;
4421                         text = "Untracked files:";
4422                         break;
4424                 case LINE_STAT_NONE:
4425                         type = LINE_DEFAULT;
4426                         text = "    (no files)";
4427                         break;
4429                 case LINE_STAT_HEAD:
4430                         type = LINE_STAT_HEAD;
4431                         text = status_onbranch;
4432                         break;
4434                 default:
4435                         return FALSE;
4436                 }
4437         } else {
4438                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4440                 buf[0] = status->status;
4441                 if (draw_text(view, line->type, buf, TRUE))
4442                         return TRUE;
4443                 type = LINE_DEFAULT;
4444                 text = status->new.name;
4445         }
4447         draw_text(view, type, text, TRUE);
4448         return TRUE;
4451 static enum request
4452 status_enter(struct view *view, struct line *line)
4454         struct status *status = line->data;
4455         const char *oldpath = status ? status->old.name : NULL;
4456         /* Diffs for unmerged entries are empty when passing the new
4457          * path, so leave it empty. */
4458         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4459         const char *info;
4460         enum open_flags split;
4461         struct view *stage = VIEW(REQ_VIEW_STAGE);
4463         if (line->type == LINE_STAT_NONE ||
4464             (!status && line[1].type == LINE_STAT_NONE)) {
4465                 report("No file to diff");
4466                 return REQ_NONE;
4467         }
4469         switch (line->type) {
4470         case LINE_STAT_STAGED:
4471                 if (is_initial_commit()) {
4472                         const char *no_head_diff_argv[] = {
4473                                 "git", "diff", "--no-color", "--patch-with-stat",
4474                                         "--", "/dev/null", newpath, NULL
4475                         };
4477                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4478                                 return REQ_QUIT;
4479                 } else {
4480                         const char *index_show_argv[] = {
4481                                 "git", "diff-index", "--root", "--patch-with-stat",
4482                                         "-C", "-M", "--cached", "HEAD", "--",
4483                                         oldpath, newpath, NULL
4484                         };
4486                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4487                                 return REQ_QUIT;
4488                 }
4490                 if (status)
4491                         info = "Staged changes to %s";
4492                 else
4493                         info = "Staged changes";
4494                 break;
4496         case LINE_STAT_UNSTAGED:
4497         {
4498                 const char *files_show_argv[] = {
4499                         "git", "diff-files", "--root", "--patch-with-stat",
4500                                 "-C", "-M", "--", oldpath, newpath, NULL
4501                 };
4503                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4504                         return REQ_QUIT;
4505                 if (status)
4506                         info = "Unstaged changes to %s";
4507                 else
4508                         info = "Unstaged changes";
4509                 break;
4510         }
4511         case LINE_STAT_UNTRACKED:
4512                 if (!newpath) {
4513                         report("No file to show");
4514                         return REQ_NONE;
4515                 }
4517                 if (!suffixcmp(status->new.name, -1, "/")) {
4518                         report("Cannot display a directory");
4519                         return REQ_NONE;
4520                 }
4522                 if (!prepare_update_file(stage, newpath))
4523                         return REQ_QUIT;
4524                 info = "Untracked file %s";
4525                 break;
4527         case LINE_STAT_HEAD:
4528                 return REQ_NONE;
4530         default:
4531                 die("line type %d not handled in switch", line->type);
4532         }
4534         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4535         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4536         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4537                 if (status) {
4538                         stage_status = *status;
4539                 } else {
4540                         memset(&stage_status, 0, sizeof(stage_status));
4541                 }
4543                 stage_line_type = line->type;
4544                 stage_chunks = 0;
4545                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4546         }
4548         return REQ_NONE;
4551 static bool
4552 status_exists(struct status *status, enum line_type type)
4554         struct view *view = VIEW(REQ_VIEW_STATUS);
4555         struct line *line;
4557         for (line = view->line; line < view->line + view->lines; line++) {
4558                 struct status *pos = line->data;
4560                 if (line->type == type && pos &&
4561                     !strcmp(status->new.name, pos->new.name))
4562                         return TRUE;
4563         }
4565         return FALSE;
4569 static bool
4570 status_update_prepare(struct io *io, enum line_type type)
4572         const char *staged_argv[] = {
4573                 "git", "update-index", "-z", "--index-info", NULL
4574         };
4575         const char *others_argv[] = {
4576                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4577         };
4579         switch (type) {
4580         case LINE_STAT_STAGED:
4581                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4583         case LINE_STAT_UNSTAGED:
4584                 return run_io(io, others_argv, opt_cdup, IO_WR);
4586         case LINE_STAT_UNTRACKED:
4587                 return run_io(io, others_argv, NULL, IO_WR);
4589         default:
4590                 die("line type %d not handled in switch", type);
4591                 return FALSE;
4592         }
4595 static bool
4596 status_update_write(struct io *io, struct status *status, enum line_type type)
4598         char buf[SIZEOF_STR];
4599         size_t bufsize = 0;
4601         switch (type) {
4602         case LINE_STAT_STAGED:
4603                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4604                                         status->old.mode,
4605                                         status->old.rev,
4606                                         status->old.name, 0))
4607                         return FALSE;
4608                 break;
4610         case LINE_STAT_UNSTAGED:
4611         case LINE_STAT_UNTRACKED:
4612                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4613                         return FALSE;
4614                 break;
4616         default:
4617                 die("line type %d not handled in switch", type);
4618         }
4620         return io_write(io, buf, bufsize);
4623 static bool
4624 status_update_file(struct status *status, enum line_type type)
4626         struct io io = {};
4627         bool result;
4629         if (!status_update_prepare(&io, type))
4630                 return FALSE;
4632         result = status_update_write(&io, status, type);
4633         done_io(&io);
4634         return result;
4637 static bool
4638 status_update_files(struct view *view, struct line *line)
4640         struct io io = {};
4641         bool result = TRUE;
4642         struct line *pos = view->line + view->lines;
4643         int files = 0;
4644         int file, done;
4646         if (!status_update_prepare(&io, line->type))
4647                 return FALSE;
4649         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4650                 files++;
4652         for (file = 0, done = 0; result && file < files; line++, file++) {
4653                 int almost_done = file * 100 / files;
4655                 if (almost_done > done) {
4656                         done = almost_done;
4657                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4658                                       file, files, done);
4659                         update_view_title(view);
4660                 }
4661                 result = status_update_write(&io, line->data, line->type);
4662         }
4664         done_io(&io);
4665         return result;
4668 static bool
4669 status_update(struct view *view)
4671         struct line *line = &view->line[view->lineno];
4673         assert(view->lines);
4675         if (!line->data) {
4676                 /* This should work even for the "On branch" line. */
4677                 if (line < view->line + view->lines && !line[1].data) {
4678                         report("Nothing to update");
4679                         return FALSE;
4680                 }
4682                 if (!status_update_files(view, line + 1)) {
4683                         report("Failed to update file status");
4684                         return FALSE;
4685                 }
4687         } else if (!status_update_file(line->data, line->type)) {
4688                 report("Failed to update file status");
4689                 return FALSE;
4690         }
4692         return TRUE;
4695 static bool
4696 status_revert(struct status *status, enum line_type type, bool has_none)
4698         if (!status || type != LINE_STAT_UNSTAGED) {
4699                 if (type == LINE_STAT_STAGED) {
4700                         report("Cannot revert changes to staged files");
4701                 } else if (type == LINE_STAT_UNTRACKED) {
4702                         report("Cannot revert changes to untracked files");
4703                 } else if (has_none) {
4704                         report("Nothing to revert");
4705                 } else {
4706                         report("Cannot revert changes to multiple files");
4707                 }
4708                 return FALSE;
4710         } else {
4711                 const char *checkout_argv[] = {
4712                         "git", "checkout", "--", status->old.name, NULL
4713                 };
4715                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4716                         return FALSE;
4717                 return run_io_fg(checkout_argv, opt_cdup);
4718         }
4721 static enum request
4722 status_request(struct view *view, enum request request, struct line *line)
4724         struct status *status = line->data;
4726         switch (request) {
4727         case REQ_STATUS_UPDATE:
4728                 if (!status_update(view))
4729                         return REQ_NONE;
4730                 break;
4732         case REQ_STATUS_REVERT:
4733                 if (!status_revert(status, line->type, status_has_none(view, line)))
4734                         return REQ_NONE;
4735                 break;
4737         case REQ_STATUS_MERGE:
4738                 if (!status || status->status != 'U') {
4739                         report("Merging only possible for files with unmerged status ('U').");
4740                         return REQ_NONE;
4741                 }
4742                 open_mergetool(status->new.name);
4743                 break;
4745         case REQ_EDIT:
4746                 if (!status)
4747                         return request;
4748                 if (status->status == 'D') {
4749                         report("File has been deleted.");
4750                         return REQ_NONE;
4751                 }
4753                 open_editor(status->status != '?', status->new.name);
4754                 break;
4756         case REQ_VIEW_BLAME:
4757                 if (status) {
4758                         string_copy(opt_file, status->new.name);
4759                         opt_ref[0] = 0;
4760                 }
4761                 return request;
4763         case REQ_ENTER:
4764                 /* After returning the status view has been split to
4765                  * show the stage view. No further reloading is
4766                  * necessary. */
4767                 status_enter(view, line);
4768                 return REQ_NONE;
4770         case REQ_REFRESH:
4771                 /* Simply reload the view. */
4772                 break;
4774         default:
4775                 return request;
4776         }
4778         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4780         return REQ_NONE;
4783 static void
4784 status_select(struct view *view, struct line *line)
4786         struct status *status = line->data;
4787         char file[SIZEOF_STR] = "all files";
4788         const char *text;
4789         const char *key;
4791         if (status && !string_format(file, "'%s'", status->new.name))
4792                 return;
4794         if (!status && line[1].type == LINE_STAT_NONE)
4795                 line++;
4797         switch (line->type) {
4798         case LINE_STAT_STAGED:
4799                 text = "Press %s to unstage %s for commit";
4800                 break;
4802         case LINE_STAT_UNSTAGED:
4803                 text = "Press %s to stage %s for commit";
4804                 break;
4806         case LINE_STAT_UNTRACKED:
4807                 text = "Press %s to stage %s for addition";
4808                 break;
4810         case LINE_STAT_HEAD:
4811         case LINE_STAT_NONE:
4812                 text = "Nothing to update";
4813                 break;
4815         default:
4816                 die("line type %d not handled in switch", line->type);
4817         }
4819         if (status && status->status == 'U') {
4820                 text = "Press %s to resolve conflict in %s";
4821                 key = get_key(REQ_STATUS_MERGE);
4823         } else {
4824                 key = get_key(REQ_STATUS_UPDATE);
4825         }
4827         string_format(view->ref, text, key, file);
4830 static bool
4831 status_grep(struct view *view, struct line *line)
4833         struct status *status = line->data;
4834         enum { S_STATUS, S_NAME, S_END } state;
4835         char buf[2] = "?";
4836         regmatch_t pmatch;
4838         if (!status)
4839                 return FALSE;
4841         for (state = S_STATUS; state < S_END; state++) {
4842                 const char *text;
4844                 switch (state) {
4845                 case S_NAME:    text = status->new.name;        break;
4846                 case S_STATUS:
4847                         buf[0] = status->status;
4848                         text = buf;
4849                         break;
4851                 default:
4852                         return FALSE;
4853                 }
4855                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4856                         return TRUE;
4857         }
4859         return FALSE;
4862 static struct view_ops status_ops = {
4863         "file",
4864         NULL,
4865         status_open,
4866         NULL,
4867         status_draw,
4868         status_request,
4869         status_grep,
4870         status_select,
4871 };
4874 static bool
4875 stage_diff_write(struct io *io, struct line *line, struct line *end)
4877         while (line < end) {
4878                 if (!io_write(io, line->data, strlen(line->data)) ||
4879                     !io_write(io, "\n", 1))
4880                         return FALSE;
4881                 line++;
4882                 if (line->type == LINE_DIFF_CHUNK ||
4883                     line->type == LINE_DIFF_HEADER)
4884                         break;
4885         }
4887         return TRUE;
4890 static struct line *
4891 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4893         for (; view->line < line; line--)
4894                 if (line->type == type)
4895                         return line;
4897         return NULL;
4900 static bool
4901 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4903         const char *apply_argv[SIZEOF_ARG] = {
4904                 "git", "apply", "--whitespace=nowarn", NULL
4905         };
4906         struct line *diff_hdr;
4907         struct io io = {};
4908         int argc = 3;
4910         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4911         if (!diff_hdr)
4912                 return FALSE;
4914         if (!revert)
4915                 apply_argv[argc++] = "--cached";
4916         if (revert || stage_line_type == LINE_STAT_STAGED)
4917                 apply_argv[argc++] = "-R";
4918         apply_argv[argc++] = "-";
4919         apply_argv[argc++] = NULL;
4920         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4921                 return FALSE;
4923         if (!stage_diff_write(&io, diff_hdr, chunk) ||
4924             !stage_diff_write(&io, chunk, view->line + view->lines))
4925                 chunk = NULL;
4927         done_io(&io);
4928         run_io_bg(update_index_argv);
4930         return chunk ? TRUE : FALSE;
4933 static bool
4934 stage_update(struct view *view, struct line *line)
4936         struct line *chunk = NULL;
4938         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4939                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4941         if (chunk) {
4942                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4943                         report("Failed to apply chunk");
4944                         return FALSE;
4945                 }
4947         } else if (!stage_status.status) {
4948                 view = VIEW(REQ_VIEW_STATUS);
4950                 for (line = view->line; line < view->line + view->lines; line++)
4951                         if (line->type == stage_line_type)
4952                                 break;
4954                 if (!status_update_files(view, line + 1)) {
4955                         report("Failed to update files");
4956                         return FALSE;
4957                 }
4959         } else if (!status_update_file(&stage_status, stage_line_type)) {
4960                 report("Failed to update file");
4961                 return FALSE;
4962         }
4964         return TRUE;
4967 static bool
4968 stage_revert(struct view *view, struct line *line)
4970         struct line *chunk = NULL;
4972         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4973                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4975         if (chunk) {
4976                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4977                         return FALSE;
4979                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4980                         report("Failed to revert chunk");
4981                         return FALSE;
4982                 }
4983                 return TRUE;
4985         } else {
4986                 return status_revert(stage_status.status ? &stage_status : NULL,
4987                                      stage_line_type, FALSE);
4988         }
4992 static void
4993 stage_next(struct view *view, struct line *line)
4995         int i;
4997         if (!stage_chunks) {
4998                 static size_t alloc = 0;
4999                 int *tmp;
5001                 for (line = view->line; line < view->line + view->lines; line++) {
5002                         if (line->type != LINE_DIFF_CHUNK)
5003                                 continue;
5005                         tmp = realloc_items(stage_chunk, &alloc,
5006                                             stage_chunks, sizeof(*tmp));
5007                         if (!tmp) {
5008                                 report("Allocation failure");
5009                                 return;
5010                         }
5012                         stage_chunk = tmp;
5013                         stage_chunk[stage_chunks++] = line - view->line;
5014                 }
5015         }
5017         for (i = 0; i < stage_chunks; i++) {
5018                 if (stage_chunk[i] > view->lineno) {
5019                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5020                         report("Chunk %d of %d", i + 1, stage_chunks);
5021                         return;
5022                 }
5023         }
5025         report("No next chunk found");
5028 static enum request
5029 stage_request(struct view *view, enum request request, struct line *line)
5031         switch (request) {
5032         case REQ_STATUS_UPDATE:
5033                 if (!stage_update(view, line))
5034                         return REQ_NONE;
5035                 break;
5037         case REQ_STATUS_REVERT:
5038                 if (!stage_revert(view, line))
5039                         return REQ_NONE;
5040                 break;
5042         case REQ_STAGE_NEXT:
5043                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5044                         report("File is untracked; press %s to add",
5045                                get_key(REQ_STATUS_UPDATE));
5046                         return REQ_NONE;
5047                 }
5048                 stage_next(view, line);
5049                 return REQ_NONE;
5051         case REQ_EDIT:
5052                 if (!stage_status.new.name[0])
5053                         return request;
5054                 if (stage_status.status == 'D') {
5055                         report("File has been deleted.");
5056                         return REQ_NONE;
5057                 }
5059                 open_editor(stage_status.status != '?', stage_status.new.name);
5060                 break;
5062         case REQ_REFRESH:
5063                 /* Reload everything ... */
5064                 break;
5066         case REQ_VIEW_BLAME:
5067                 if (stage_status.new.name[0]) {
5068                         string_copy(opt_file, stage_status.new.name);
5069                         opt_ref[0] = 0;
5070                 }
5071                 return request;
5073         case REQ_ENTER:
5074                 return pager_request(view, request, line);
5076         default:
5077                 return request;
5078         }
5080         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5082         /* Check whether the staged entry still exists, and close the
5083          * stage view if it doesn't. */
5084         if (!status_exists(&stage_status, stage_line_type))
5085                 return REQ_VIEW_CLOSE;
5087         if (stage_line_type == LINE_STAT_UNTRACKED) {
5088                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5089                         report("Cannot display a directory");
5090                         return REQ_NONE;
5091                 }
5093                 if (!prepare_update_file(view, stage_status.new.name)) {
5094                         report("Failed to open file: %s", strerror(errno));
5095                         return REQ_NONE;
5096                 }
5097         }
5098         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5100         return REQ_NONE;
5103 static struct view_ops stage_ops = {
5104         "line",
5105         NULL,
5106         NULL,
5107         pager_read,
5108         pager_draw,
5109         stage_request,
5110         pager_grep,
5111         pager_select,
5112 };
5115 /*
5116  * Revision graph
5117  */
5119 struct commit {
5120         char id[SIZEOF_REV];            /* SHA1 ID. */
5121         char title[128];                /* First line of the commit message. */
5122         char author[75];                /* Author of the commit. */
5123         struct tm time;                 /* Date from the author ident. */
5124         struct ref **refs;              /* Repository references. */
5125         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5126         size_t graph_size;              /* The width of the graph array. */
5127         bool has_parents;               /* Rewritten --parents seen. */
5128 };
5130 /* Size of rev graph with no  "padding" columns */
5131 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5133 struct rev_graph {
5134         struct rev_graph *prev, *next, *parents;
5135         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5136         size_t size;
5137         struct commit *commit;
5138         size_t pos;
5139         unsigned int boundary:1;
5140 };
5142 /* Parents of the commit being visualized. */
5143 static struct rev_graph graph_parents[4];
5145 /* The current stack of revisions on the graph. */
5146 static struct rev_graph graph_stacks[4] = {
5147         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5148         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5149         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5150         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5151 };
5153 static inline bool
5154 graph_parent_is_merge(struct rev_graph *graph)
5156         return graph->parents->size > 1;
5159 static inline void
5160 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5162         struct commit *commit = graph->commit;
5164         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5165                 commit->graph[commit->graph_size++] = symbol;
5168 static void
5169 clear_rev_graph(struct rev_graph *graph)
5171         graph->boundary = 0;
5172         graph->size = graph->pos = 0;
5173         graph->commit = NULL;
5174         memset(graph->parents, 0, sizeof(*graph->parents));
5177 static void
5178 done_rev_graph(struct rev_graph *graph)
5180         if (graph_parent_is_merge(graph) &&
5181             graph->pos < graph->size - 1 &&
5182             graph->next->size == graph->size + graph->parents->size - 1) {
5183                 size_t i = graph->pos + graph->parents->size - 1;
5185                 graph->commit->graph_size = i * 2;
5186                 while (i < graph->next->size - 1) {
5187                         append_to_rev_graph(graph, ' ');
5188                         append_to_rev_graph(graph, '\\');
5189                         i++;
5190                 }
5191         }
5193         clear_rev_graph(graph);
5196 static void
5197 push_rev_graph(struct rev_graph *graph, const char *parent)
5199         int i;
5201         /* "Collapse" duplicate parents lines.
5202          *
5203          * FIXME: This needs to also update update the drawn graph but
5204          * for now it just serves as a method for pruning graph lines. */
5205         for (i = 0; i < graph->size; i++)
5206                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5207                         return;
5209         if (graph->size < SIZEOF_REVITEMS) {
5210                 string_copy_rev(graph->rev[graph->size++], parent);
5211         }
5214 static chtype
5215 get_rev_graph_symbol(struct rev_graph *graph)
5217         chtype symbol;
5219         if (graph->boundary)
5220                 symbol = REVGRAPH_BOUND;
5221         else if (graph->parents->size == 0)
5222                 symbol = REVGRAPH_INIT;
5223         else if (graph_parent_is_merge(graph))
5224                 symbol = REVGRAPH_MERGE;
5225         else if (graph->pos >= graph->size)
5226                 symbol = REVGRAPH_BRANCH;
5227         else
5228                 symbol = REVGRAPH_COMMIT;
5230         return symbol;
5233 static void
5234 draw_rev_graph(struct rev_graph *graph)
5236         struct rev_filler {
5237                 chtype separator, line;
5238         };
5239         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5240         static struct rev_filler fillers[] = {
5241                 { ' ',  '|' },
5242                 { '`',  '.' },
5243                 { '\'', ' ' },
5244                 { '/',  ' ' },
5245         };
5246         chtype symbol = get_rev_graph_symbol(graph);
5247         struct rev_filler *filler;
5248         size_t i;
5250         if (opt_line_graphics)
5251                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5253         filler = &fillers[DEFAULT];
5255         for (i = 0; i < graph->pos; i++) {
5256                 append_to_rev_graph(graph, filler->line);
5257                 if (graph_parent_is_merge(graph->prev) &&
5258                     graph->prev->pos == i)
5259                         filler = &fillers[RSHARP];
5261                 append_to_rev_graph(graph, filler->separator);
5262         }
5264         /* Place the symbol for this revision. */
5265         append_to_rev_graph(graph, symbol);
5267         if (graph->prev->size > graph->size)
5268                 filler = &fillers[RDIAG];
5269         else
5270                 filler = &fillers[DEFAULT];
5272         i++;
5274         for (; i < graph->size; i++) {
5275                 append_to_rev_graph(graph, filler->separator);
5276                 append_to_rev_graph(graph, filler->line);
5277                 if (graph_parent_is_merge(graph->prev) &&
5278                     i < graph->prev->pos + graph->parents->size)
5279                         filler = &fillers[RSHARP];
5280                 if (graph->prev->size > graph->size)
5281                         filler = &fillers[LDIAG];
5282         }
5284         if (graph->prev->size > graph->size) {
5285                 append_to_rev_graph(graph, filler->separator);
5286                 if (filler->line != ' ')
5287                         append_to_rev_graph(graph, filler->line);
5288         }
5291 /* Prepare the next rev graph */
5292 static void
5293 prepare_rev_graph(struct rev_graph *graph)
5295         size_t i;
5297         /* First, traverse all lines of revisions up to the active one. */
5298         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5299                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5300                         break;
5302                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5303         }
5305         /* Interleave the new revision parent(s). */
5306         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5307                 push_rev_graph(graph->next, graph->parents->rev[i]);
5309         /* Lastly, put any remaining revisions. */
5310         for (i = graph->pos + 1; i < graph->size; i++)
5311                 push_rev_graph(graph->next, graph->rev[i]);
5314 static void
5315 update_rev_graph(struct rev_graph *graph)
5317         /* If this is the finalizing update ... */
5318         if (graph->commit)
5319                 prepare_rev_graph(graph);
5321         /* Graph visualization needs a one rev look-ahead,
5322          * so the first update doesn't visualize anything. */
5323         if (!graph->prev->commit)
5324                 return;
5326         draw_rev_graph(graph->prev);
5327         done_rev_graph(graph->prev->prev);
5331 /*
5332  * Main view backend
5333  */
5335 static const char *main_argv[SIZEOF_ARG] = {
5336         "git", "log", "--no-color", "--pretty=raw", "--parents",
5337                       "--topo-order", "%(head)", NULL
5338 };
5340 static bool
5341 main_draw(struct view *view, struct line *line, unsigned int lineno)
5343         struct commit *commit = line->data;
5345         if (!*commit->author)
5346                 return FALSE;
5348         if (opt_date && draw_date(view, &commit->time))
5349                 return TRUE;
5351         if (opt_author &&
5352             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5353                 return TRUE;
5355         if (opt_rev_graph && commit->graph_size &&
5356             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5357                 return TRUE;
5359         if (opt_show_refs && commit->refs) {
5360                 size_t i = 0;
5362                 do {
5363                         enum line_type type;
5365                         if (commit->refs[i]->head)
5366                                 type = LINE_MAIN_HEAD;
5367                         else if (commit->refs[i]->ltag)
5368                                 type = LINE_MAIN_LOCAL_TAG;
5369                         else if (commit->refs[i]->tag)
5370                                 type = LINE_MAIN_TAG;
5371                         else if (commit->refs[i]->tracked)
5372                                 type = LINE_MAIN_TRACKED;
5373                         else if (commit->refs[i]->remote)
5374                                 type = LINE_MAIN_REMOTE;
5375                         else
5376                                 type = LINE_MAIN_REF;
5378                         if (draw_text(view, type, "[", TRUE) ||
5379                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5380                             draw_text(view, type, "]", TRUE))
5381                                 return TRUE;
5383                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5384                                 return TRUE;
5385                 } while (commit->refs[i++]->next);
5386         }
5388         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5389         return TRUE;
5392 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5393 static bool
5394 main_read(struct view *view, char *line)
5396         static struct rev_graph *graph = graph_stacks;
5397         enum line_type type;
5398         struct commit *commit;
5400         if (!line) {
5401                 int i;
5403                 if (!view->lines && !view->parent)
5404                         die("No revisions match the given arguments.");
5405                 if (view->lines > 0) {
5406                         commit = view->line[view->lines - 1].data;
5407                         if (!*commit->author) {
5408                                 view->lines--;
5409                                 free(commit);
5410                                 graph->commit = NULL;
5411                         }
5412                 }
5413                 update_rev_graph(graph);
5415                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5416                         clear_rev_graph(&graph_stacks[i]);
5417                 return TRUE;
5418         }
5420         type = get_line_type(line);
5421         if (type == LINE_COMMIT) {
5422                 commit = calloc(1, sizeof(struct commit));
5423                 if (!commit)
5424                         return FALSE;
5426                 line += STRING_SIZE("commit ");
5427                 if (*line == '-') {
5428                         graph->boundary = 1;
5429                         line++;
5430                 }
5432                 string_copy_rev(commit->id, line);
5433                 commit->refs = get_refs(commit->id);
5434                 graph->commit = commit;
5435                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5437                 while ((line = strchr(line, ' '))) {
5438                         line++;
5439                         push_rev_graph(graph->parents, line);
5440                         commit->has_parents = TRUE;
5441                 }
5442                 return TRUE;
5443         }
5445         if (!view->lines)
5446                 return TRUE;
5447         commit = view->line[view->lines - 1].data;
5449         switch (type) {
5450         case LINE_PARENT:
5451                 if (commit->has_parents)
5452                         break;
5453                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5454                 break;
5456         case LINE_AUTHOR:
5457         {
5458                 /* Parse author lines where the name may be empty:
5459                  *      author  <email@address.tld> 1138474660 +0100
5460                  */
5461                 char *ident = line + STRING_SIZE("author ");
5462                 char *nameend = strchr(ident, '<');
5463                 char *emailend = strchr(ident, '>');
5465                 if (!nameend || !emailend)
5466                         break;
5468                 update_rev_graph(graph);
5469                 graph = graph->next;
5471                 *nameend = *emailend = 0;
5472                 ident = chomp_string(ident);
5473                 if (!*ident) {
5474                         ident = chomp_string(nameend + 1);
5475                         if (!*ident)
5476                                 ident = "Unknown";
5477                 }
5479                 string_ncopy(commit->author, ident, strlen(ident));
5481                 /* Parse epoch and timezone */
5482                 if (emailend[1] == ' ') {
5483                         char *secs = emailend + 2;
5484                         char *zone = strchr(secs, ' ');
5485                         time_t time = (time_t) atol(secs);
5487                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5488                                 long tz;
5490                                 zone++;
5491                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5492                                 tz += ('0' - zone[2]) * 60 * 60;
5493                                 tz += ('0' - zone[3]) * 60;
5494                                 tz += ('0' - zone[4]) * 60;
5496                                 if (zone[0] == '-')
5497                                         tz = -tz;
5499                                 time -= tz;
5500                         }
5502                         gmtime_r(&time, &commit->time);
5503                 }
5504                 break;
5505         }
5506         default:
5507                 /* Fill in the commit title if it has not already been set. */
5508                 if (commit->title[0])
5509                         break;
5511                 /* Require titles to start with a non-space character at the
5512                  * offset used by git log. */
5513                 if (strncmp(line, "    ", 4))
5514                         break;
5515                 line += 4;
5516                 /* Well, if the title starts with a whitespace character,
5517                  * try to be forgiving.  Otherwise we end up with no title. */
5518                 while (isspace(*line))
5519                         line++;
5520                 if (*line == '\0')
5521                         break;
5522                 /* FIXME: More graceful handling of titles; append "..." to
5523                  * shortened titles, etc. */
5525                 string_ncopy(commit->title, line, strlen(line));
5526         }
5528         return TRUE;
5531 static enum request
5532 main_request(struct view *view, enum request request, struct line *line)
5534         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5536         switch (request) {
5537         case REQ_ENTER:
5538                 open_view(view, REQ_VIEW_DIFF, flags);
5539                 break;
5540         case REQ_REFRESH:
5541                 load_refs();
5542                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5543                 break;
5544         default:
5545                 return request;
5546         }
5548         return REQ_NONE;
5551 static bool
5552 grep_refs(struct ref **refs, regex_t *regex)
5554         regmatch_t pmatch;
5555         size_t i = 0;
5557         if (!refs)
5558                 return FALSE;
5559         do {
5560                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5561                         return TRUE;
5562         } while (refs[i++]->next);
5564         return FALSE;
5567 static bool
5568 main_grep(struct view *view, struct line *line)
5570         struct commit *commit = line->data;
5571         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5572         char buf[DATE_COLS + 1];
5573         regmatch_t pmatch;
5575         for (state = S_TITLE; state < S_END; state++) {
5576                 char *text;
5578                 switch (state) {
5579                 case S_TITLE:   text = commit->title;   break;
5580                 case S_AUTHOR:
5581                         if (!opt_author)
5582                                 continue;
5583                         text = commit->author;
5584                         break;
5585                 case S_DATE:
5586                         if (!opt_date)
5587                                 continue;
5588                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5589                                 continue;
5590                         text = buf;
5591                         break;
5592                 case S_REFS:
5593                         if (!opt_show_refs)
5594                                 continue;
5595                         if (grep_refs(commit->refs, view->regex) == TRUE)
5596                                 return TRUE;
5597                         continue;
5598                 default:
5599                         return FALSE;
5600                 }
5602                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5603                         return TRUE;
5604         }
5606         return FALSE;
5609 static void
5610 main_select(struct view *view, struct line *line)
5612         struct commit *commit = line->data;
5614         string_copy_rev(view->ref, commit->id);
5615         string_copy_rev(ref_commit, view->ref);
5618 static struct view_ops main_ops = {
5619         "commit",
5620         main_argv,
5621         NULL,
5622         main_read,
5623         main_draw,
5624         main_request,
5625         main_grep,
5626         main_select,
5627 };
5630 /*
5631  * Unicode / UTF-8 handling
5632  *
5633  * NOTE: Much of the following code for dealing with unicode is derived from
5634  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5635  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5636  */
5638 /* I've (over)annotated a lot of code snippets because I am not entirely
5639  * confident that the approach taken by this small UTF-8 interface is correct.
5640  * --jonas */
5642 static inline int
5643 unicode_width(unsigned long c)
5645         if (c >= 0x1100 &&
5646            (c <= 0x115f                         /* Hangul Jamo */
5647             || c == 0x2329
5648             || c == 0x232a
5649             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5650                                                 /* CJK ... Yi */
5651             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5652             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5653             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5654             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5655             || (c >= 0xffe0  && c <= 0xffe6)
5656             || (c >= 0x20000 && c <= 0x2fffd)
5657             || (c >= 0x30000 && c <= 0x3fffd)))
5658                 return 2;
5660         if (c == '\t')
5661                 return opt_tab_size;
5663         return 1;
5666 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5667  * Illegal bytes are set one. */
5668 static const unsigned char utf8_bytes[256] = {
5669         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,
5670         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,
5671         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,
5672         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,
5673         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,
5674         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,
5675         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,
5676         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,
5677 };
5679 /* Decode UTF-8 multi-byte representation into a unicode character. */
5680 static inline unsigned long
5681 utf8_to_unicode(const char *string, size_t length)
5683         unsigned long unicode;
5685         switch (length) {
5686         case 1:
5687                 unicode  =   string[0];
5688                 break;
5689         case 2:
5690                 unicode  =  (string[0] & 0x1f) << 6;
5691                 unicode +=  (string[1] & 0x3f);
5692                 break;
5693         case 3:
5694                 unicode  =  (string[0] & 0x0f) << 12;
5695                 unicode += ((string[1] & 0x3f) << 6);
5696                 unicode +=  (string[2] & 0x3f);
5697                 break;
5698         case 4:
5699                 unicode  =  (string[0] & 0x0f) << 18;
5700                 unicode += ((string[1] & 0x3f) << 12);
5701                 unicode += ((string[2] & 0x3f) << 6);
5702                 unicode +=  (string[3] & 0x3f);
5703                 break;
5704         case 5:
5705                 unicode  =  (string[0] & 0x0f) << 24;
5706                 unicode += ((string[1] & 0x3f) << 18);
5707                 unicode += ((string[2] & 0x3f) << 12);
5708                 unicode += ((string[3] & 0x3f) << 6);
5709                 unicode +=  (string[4] & 0x3f);
5710                 break;
5711         case 6:
5712                 unicode  =  (string[0] & 0x01) << 30;
5713                 unicode += ((string[1] & 0x3f) << 24);
5714                 unicode += ((string[2] & 0x3f) << 18);
5715                 unicode += ((string[3] & 0x3f) << 12);
5716                 unicode += ((string[4] & 0x3f) << 6);
5717                 unicode +=  (string[5] & 0x3f);
5718                 break;
5719         default:
5720                 die("Invalid unicode length");
5721         }
5723         /* Invalid characters could return the special 0xfffd value but NUL
5724          * should be just as good. */
5725         return unicode > 0xffff ? 0 : unicode;
5728 /* Calculates how much of string can be shown within the given maximum width
5729  * and sets trimmed parameter to non-zero value if all of string could not be
5730  * shown. If the reserve flag is TRUE, it will reserve at least one
5731  * trailing character, which can be useful when drawing a delimiter.
5732  *
5733  * Returns the number of bytes to output from string to satisfy max_width. */
5734 static size_t
5735 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5737         const char *start = string;
5738         const char *end = strchr(string, '\0');
5739         unsigned char last_bytes = 0;
5740         size_t last_ucwidth = 0;
5742         *width = 0;
5743         *trimmed = 0;
5745         while (string < end) {
5746                 int c = *(unsigned char *) string;
5747                 unsigned char bytes = utf8_bytes[c];
5748                 size_t ucwidth;
5749                 unsigned long unicode;
5751                 if (string + bytes > end)
5752                         break;
5754                 /* Change representation to figure out whether
5755                  * it is a single- or double-width character. */
5757                 unicode = utf8_to_unicode(string, bytes);
5758                 /* FIXME: Graceful handling of invalid unicode character. */
5759                 if (!unicode)
5760                         break;
5762                 ucwidth = unicode_width(unicode);
5763                 *width  += ucwidth;
5764                 if (*width > max_width) {
5765                         *trimmed = 1;
5766                         *width -= ucwidth;
5767                         if (reserve && *width == max_width) {
5768                                 string -= last_bytes;
5769                                 *width -= last_ucwidth;
5770                         }
5771                         break;
5772                 }
5774                 string  += bytes;
5775                 last_bytes = bytes;
5776                 last_ucwidth = ucwidth;
5777         }
5779         return string - start;
5783 /*
5784  * Status management
5785  */
5787 /* Whether or not the curses interface has been initialized. */
5788 static bool cursed = FALSE;
5790 /* The status window is used for polling keystrokes. */
5791 static WINDOW *status_win;
5793 static bool status_empty = TRUE;
5795 /* Update status and title window. */
5796 static void
5797 report(const char *msg, ...)
5799         struct view *view = display[current_view];
5801         if (input_mode)
5802                 return;
5804         if (!view) {
5805                 char buf[SIZEOF_STR];
5806                 va_list args;
5808                 va_start(args, msg);
5809                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5810                         buf[sizeof(buf) - 1] = 0;
5811                         buf[sizeof(buf) - 2] = '.';
5812                         buf[sizeof(buf) - 3] = '.';
5813                         buf[sizeof(buf) - 4] = '.';
5814                 }
5815                 va_end(args);
5816                 die("%s", buf);
5817         }
5819         if (!status_empty || *msg) {
5820                 va_list args;
5822                 va_start(args, msg);
5824                 wmove(status_win, 0, 0);
5825                 if (*msg) {
5826                         vwprintw(status_win, msg, args);
5827                         status_empty = FALSE;
5828                 } else {
5829                         status_empty = TRUE;
5830                 }
5831                 wclrtoeol(status_win);
5832                 wrefresh(status_win);
5834                 va_end(args);
5835         }
5837         update_view_title(view);
5838         update_display_cursor(view);
5841 /* Controls when nodelay should be in effect when polling user input. */
5842 static void
5843 set_nonblocking_input(bool loading)
5845         static unsigned int loading_views;
5847         if ((loading == FALSE && loading_views-- == 1) ||
5848             (loading == TRUE  && loading_views++ == 0))
5849                 nodelay(status_win, loading);
5852 static void
5853 init_display(void)
5855         int x, y;
5857         /* Initialize the curses library */
5858         if (isatty(STDIN_FILENO)) {
5859                 cursed = !!initscr();
5860                 opt_tty = stdin;
5861         } else {
5862                 /* Leave stdin and stdout alone when acting as a pager. */
5863                 opt_tty = fopen("/dev/tty", "r+");
5864                 if (!opt_tty)
5865                         die("Failed to open /dev/tty");
5866                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5867         }
5869         if (!cursed)
5870                 die("Failed to initialize curses");
5872         nonl();         /* Tell curses not to do NL->CR/NL on output */
5873         cbreak();       /* Take input chars one at a time, no wait for \n */
5874         noecho();       /* Don't echo input */
5875         leaveok(stdscr, TRUE);
5877         if (has_colors())
5878                 init_colors();
5880         getmaxyx(stdscr, y, x);
5881         status_win = newwin(1, 0, y - 1, 0);
5882         if (!status_win)
5883                 die("Failed to create status window");
5885         /* Enable keyboard mapping */
5886         keypad(status_win, TRUE);
5887         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5889         TABSIZE = opt_tab_size;
5890         if (opt_line_graphics) {
5891                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5892         }
5895 static bool
5896 prompt_yesno(const char *prompt)
5898         enum { WAIT, STOP, CANCEL  } status = WAIT;
5899         bool answer = FALSE;
5901         while (status == WAIT) {
5902                 struct view *view;
5903                 int i, key;
5905                 input_mode = TRUE;
5907                 foreach_view (view, i)
5908                         update_view(view);
5910                 input_mode = FALSE;
5912                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5913                 wclrtoeol(status_win);
5915                 /* Refresh, accept single keystroke of input */
5916                 key = wgetch(status_win);
5917                 switch (key) {
5918                 case ERR:
5919                         break;
5921                 case 'y':
5922                 case 'Y':
5923                         answer = TRUE;
5924                         status = STOP;
5925                         break;
5927                 case KEY_ESC:
5928                 case KEY_RETURN:
5929                 case KEY_ENTER:
5930                 case KEY_BACKSPACE:
5931                 case 'n':
5932                 case 'N':
5933                 case '\n':
5934                 default:
5935                         answer = FALSE;
5936                         status = CANCEL;
5937                 }
5938         }
5940         /* Clear the status window */
5941         status_empty = FALSE;
5942         report("");
5944         return answer;
5947 static char *
5948 read_prompt(const char *prompt)
5950         enum { READING, STOP, CANCEL } status = READING;
5951         static char buf[SIZEOF_STR];
5952         int pos = 0;
5954         while (status == READING) {
5955                 struct view *view;
5956                 int i, key;
5958                 input_mode = TRUE;
5960                 foreach_view (view, i)
5961                         update_view(view);
5963                 input_mode = FALSE;
5965                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5966                 wclrtoeol(status_win);
5968                 /* Refresh, accept single keystroke of input */
5969                 key = wgetch(status_win);
5970                 switch (key) {
5971                 case KEY_RETURN:
5972                 case KEY_ENTER:
5973                 case '\n':
5974                         status = pos ? STOP : CANCEL;
5975                         break;
5977                 case KEY_BACKSPACE:
5978                         if (pos > 0)
5979                                 pos--;
5980                         else
5981                                 status = CANCEL;
5982                         break;
5984                 case KEY_ESC:
5985                         status = CANCEL;
5986                         break;
5988                 case ERR:
5989                         break;
5991                 default:
5992                         if (pos >= sizeof(buf)) {
5993                                 report("Input string too long");
5994                                 return NULL;
5995                         }
5997                         if (isprint(key))
5998                                 buf[pos++] = (char) key;
5999                 }
6000         }
6002         /* Clear the status window */
6003         status_empty = FALSE;
6004         report("");
6006         if (status == CANCEL)
6007                 return NULL;
6009         buf[pos++] = 0;
6011         return buf;
6014 /*
6015  * Repository properties
6016  */
6018 static int
6019 git_properties(const char **argv, const char *separators,
6020                int (*read_property)(char *, size_t, char *, size_t))
6022         struct io io = {};
6024         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6025                 return read_properties(&io, separators, read_property);
6026         return ERR;
6029 static struct ref *refs = NULL;
6030 static size_t refs_alloc = 0;
6031 static size_t refs_size = 0;
6033 /* Id <-> ref store */
6034 static struct ref ***id_refs = NULL;
6035 static size_t id_refs_alloc = 0;
6036 static size_t id_refs_size = 0;
6038 static int
6039 compare_refs(const void *ref1_, const void *ref2_)
6041         const struct ref *ref1 = *(const struct ref **)ref1_;
6042         const struct ref *ref2 = *(const struct ref **)ref2_;
6044         if (ref1->tag != ref2->tag)
6045                 return ref2->tag - ref1->tag;
6046         if (ref1->ltag != ref2->ltag)
6047                 return ref2->ltag - ref2->ltag;
6048         if (ref1->head != ref2->head)
6049                 return ref2->head - ref1->head;
6050         if (ref1->tracked != ref2->tracked)
6051                 return ref2->tracked - ref1->tracked;
6052         if (ref1->remote != ref2->remote)
6053                 return ref2->remote - ref1->remote;
6054         return strcmp(ref1->name, ref2->name);
6057 static struct ref **
6058 get_refs(const char *id)
6060         struct ref ***tmp_id_refs;
6061         struct ref **ref_list = NULL;
6062         size_t ref_list_alloc = 0;
6063         size_t ref_list_size = 0;
6064         size_t i;
6066         for (i = 0; i < id_refs_size; i++)
6067                 if (!strcmp(id, id_refs[i][0]->id))
6068                         return id_refs[i];
6070         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6071                                     sizeof(*id_refs));
6072         if (!tmp_id_refs)
6073                 return NULL;
6075         id_refs = tmp_id_refs;
6077         for (i = 0; i < refs_size; i++) {
6078                 struct ref **tmp;
6080                 if (strcmp(id, refs[i].id))
6081                         continue;
6083                 tmp = realloc_items(ref_list, &ref_list_alloc,
6084                                     ref_list_size + 1, sizeof(*ref_list));
6085                 if (!tmp) {
6086                         if (ref_list)
6087                                 free(ref_list);
6088                         return NULL;
6089                 }
6091                 ref_list = tmp;
6092                 ref_list[ref_list_size] = &refs[i];
6093                 /* XXX: The properties of the commit chains ensures that we can
6094                  * safely modify the shared ref. The repo references will
6095                  * always be similar for the same id. */
6096                 ref_list[ref_list_size]->next = 1;
6098                 ref_list_size++;
6099         }
6101         if (ref_list) {
6102                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6103                 ref_list[ref_list_size - 1]->next = 0;
6104                 id_refs[id_refs_size++] = ref_list;
6105         }
6107         return ref_list;
6110 static int
6111 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6113         struct ref *ref;
6114         bool tag = FALSE;
6115         bool ltag = FALSE;
6116         bool remote = FALSE;
6117         bool tracked = FALSE;
6118         bool check_replace = FALSE;
6119         bool head = FALSE;
6121         if (!prefixcmp(name, "refs/tags/")) {
6122                 if (!suffixcmp(name, namelen, "^{}")) {
6123                         namelen -= 3;
6124                         name[namelen] = 0;
6125                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6126                                 check_replace = TRUE;
6127                 } else {
6128                         ltag = TRUE;
6129                 }
6131                 tag = TRUE;
6132                 namelen -= STRING_SIZE("refs/tags/");
6133                 name    += STRING_SIZE("refs/tags/");
6135         } else if (!prefixcmp(name, "refs/remotes/")) {
6136                 remote = TRUE;
6137                 namelen -= STRING_SIZE("refs/remotes/");
6138                 name    += STRING_SIZE("refs/remotes/");
6139                 tracked  = !strcmp(opt_remote, name);
6141         } else if (!prefixcmp(name, "refs/heads/")) {
6142                 namelen -= STRING_SIZE("refs/heads/");
6143                 name    += STRING_SIZE("refs/heads/");
6144                 head     = !strncmp(opt_head, name, namelen);
6146         } else if (!strcmp(name, "HEAD")) {
6147                 string_ncopy(opt_head_rev, id, idlen);
6148                 return OK;
6149         }
6151         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6152                 /* it's an annotated tag, replace the previous sha1 with the
6153                  * resolved commit id; relies on the fact git-ls-remote lists
6154                  * the commit id of an annotated tag right before the commit id
6155                  * it points to. */
6156                 refs[refs_size - 1].ltag = ltag;
6157                 string_copy_rev(refs[refs_size - 1].id, id);
6159                 return OK;
6160         }
6161         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6162         if (!refs)
6163                 return ERR;
6165         ref = &refs[refs_size++];
6166         ref->name = malloc(namelen + 1);
6167         if (!ref->name)
6168                 return ERR;
6170         strncpy(ref->name, name, namelen);
6171         ref->name[namelen] = 0;
6172         ref->head = head;
6173         ref->tag = tag;
6174         ref->ltag = ltag;
6175         ref->remote = remote;
6176         ref->tracked = tracked;
6177         string_copy_rev(ref->id, id);
6179         return OK;
6182 static int
6183 load_refs(void)
6185         static const char *ls_remote_argv[SIZEOF_ARG] = {
6186                 "git", "ls-remote", ".", NULL
6187         };
6188         static bool init = FALSE;
6190         if (!init) {
6191                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6192                 init = TRUE;
6193         }
6195         if (!*opt_git_dir)
6196                 return OK;
6198         while (refs_size > 0)
6199                 free(refs[--refs_size].name);
6200         while (id_refs_size > 0)
6201                 free(id_refs[--id_refs_size]);
6203         return git_properties(ls_remote_argv, "\t", read_ref);
6206 static int
6207 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6209         if (!strcmp(name, "i18n.commitencoding"))
6210                 string_ncopy(opt_encoding, value, valuelen);
6212         if (!strcmp(name, "core.editor"))
6213                 string_ncopy(opt_editor, value, valuelen);
6215         /* branch.<head>.remote */
6216         if (*opt_head &&
6217             !strncmp(name, "branch.", 7) &&
6218             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6219             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6220                 string_ncopy(opt_remote, value, valuelen);
6222         if (*opt_head && *opt_remote &&
6223             !strncmp(name, "branch.", 7) &&
6224             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6225             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6226                 size_t from = strlen(opt_remote);
6228                 if (!prefixcmp(value, "refs/heads/")) {
6229                         value += STRING_SIZE("refs/heads/");
6230                         valuelen -= STRING_SIZE("refs/heads/");
6231                 }
6233                 if (!string_format_from(opt_remote, &from, "/%s", value))
6234                         opt_remote[0] = 0;
6235         }
6237         return OK;
6240 static int
6241 load_git_config(void)
6243         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6245         return git_properties(config_list_argv, "=", read_repo_config_option);
6248 static int
6249 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6251         if (!opt_git_dir[0]) {
6252                 string_ncopy(opt_git_dir, name, namelen);
6254         } else if (opt_is_inside_work_tree == -1) {
6255                 /* This can be 3 different values depending on the
6256                  * version of git being used. If git-rev-parse does not
6257                  * understand --is-inside-work-tree it will simply echo
6258                  * the option else either "true" or "false" is printed.
6259                  * Default to true for the unknown case. */
6260                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6261         } else {
6262                 string_ncopy(opt_cdup, name, namelen);
6263         }
6265         return OK;
6268 static int
6269 load_repo_info(void)
6271         const char *head_argv[] = {
6272                 "git", "symbolic-ref", "HEAD", NULL
6273         };
6274         const char *rev_parse_argv[] = {
6275                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6276                         "--show-cdup", NULL
6277         };
6279         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6280                 chomp_string(opt_head);
6281                 if (!prefixcmp(opt_head, "refs/heads/")) {
6282                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6284                         memmove(opt_head, offset, strlen(offset) + 1);
6285                 }
6286         }
6288         return git_properties(rev_parse_argv, "=", read_repo_info);
6291 static int
6292 read_properties(struct io *io, const char *separators,
6293                 int (*read_property)(char *, size_t, char *, size_t))
6295         char *name;
6296         int state = OK;
6298         if (!start_io(io))
6299                 return ERR;
6301         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6302                 char *value;
6303                 size_t namelen;
6304                 size_t valuelen;
6306                 name = chomp_string(name);
6307                 namelen = strcspn(name, separators);
6309                 if (name[namelen]) {
6310                         name[namelen] = 0;
6311                         value = chomp_string(name + namelen + 1);
6312                         valuelen = strlen(value);
6314                 } else {
6315                         value = "";
6316                         valuelen = 0;
6317                 }
6319                 state = read_property(name, namelen, value, valuelen);
6320         }
6322         if (state != ERR && io_error(io))
6323                 state = ERR;
6324         done_io(io);
6326         return state;
6330 /*
6331  * Main
6332  */
6334 static void __NORETURN
6335 quit(int sig)
6337         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6338         if (cursed)
6339                 endwin();
6340         exit(0);
6343 static void __NORETURN
6344 die(const char *err, ...)
6346         va_list args;
6348         endwin();
6350         va_start(args, err);
6351         fputs("tig: ", stderr);
6352         vfprintf(stderr, err, args);
6353         fputs("\n", stderr);
6354         va_end(args);
6356         exit(1);
6359 static void
6360 warn(const char *msg, ...)
6362         va_list args;
6364         va_start(args, msg);
6365         fputs("tig warning: ", stderr);
6366         vfprintf(stderr, msg, args);
6367         fputs("\n", stderr);
6368         va_end(args);
6371 int
6372 main(int argc, const char *argv[])
6374         const char **run_argv = NULL;
6375         struct view *view;
6376         enum request request;
6377         size_t i;
6379         signal(SIGINT, quit);
6381         if (setlocale(LC_ALL, "")) {
6382                 char *codeset = nl_langinfo(CODESET);
6384                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6385         }
6387         if (load_repo_info() == ERR)
6388                 die("Failed to load repo info.");
6390         if (load_options() == ERR)
6391                 die("Failed to load user config.");
6393         if (load_git_config() == ERR)
6394                 die("Failed to load repo config.");
6396         request = parse_options(argc, argv, &run_argv);
6397         if (request == REQ_NONE)
6398                 return 0;
6400         /* Require a git repository unless when running in pager mode. */
6401         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6402                 die("Not a git repository");
6404         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6405                 opt_utf8 = FALSE;
6407         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6408                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6409                 if (opt_iconv == ICONV_NONE)
6410                         die("Failed to initialize character set conversion");
6411         }
6413         if (load_refs() == ERR)
6414                 die("Failed to load refs.");
6416         foreach_view (view, i)
6417                 argv_from_env(view->ops->argv, view->cmd_env);
6419         init_display();
6421         if (request == REQ_VIEW_PAGER || run_argv) {
6422                 if (request == REQ_VIEW_PAGER)
6423                         io_open(&VIEW(request)->io, "");
6424                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6425                         die("Failed to format arguments");
6426                 open_view(NULL, request, OPEN_PREPARED);
6427                 request = REQ_NONE;
6428         }
6430         while (view_driver(display[current_view], request)) {
6431                 int key;
6432                 int i;
6434                 foreach_view (view, i)
6435                         update_view(view);
6436                 view = display[current_view];
6438                 /* Refresh, accept single keystroke of input */
6439                 key = wgetch(status_win);
6441                 /* wgetch() with nodelay() enabled returns ERR when there's no
6442                  * input. */
6443                 if (key == ERR) {
6444                         request = REQ_NONE;
6445                         continue;
6446                 }
6448                 request = get_keybinding(view->keymap, key);
6450                 /* Some low-level request handling. This keeps access to
6451                  * status_win restricted. */
6452                 switch (request) {
6453                 case REQ_PROMPT:
6454                 {
6455                         char *cmd = read_prompt(":");
6457                         if (cmd) {
6458                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6459                                 const char *argv[SIZEOF_ARG] = { "git" };
6460                                 int argc = 1;
6462                                 /* When running random commands, initially show the
6463                                  * command in the title. However, it maybe later be
6464                                  * overwritten if a commit line is selected. */
6465                                 string_ncopy(next->ref, cmd, strlen(cmd));
6467                                 if (!argv_from_string(argv, &argc, cmd)) {
6468                                         report("Too many arguments");
6469                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6470                                         report("Failed to format command");
6471                                 } else {
6472                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6473                                 }
6474                         }
6476                         request = REQ_NONE;
6477                         break;
6478                 }
6479                 case REQ_SEARCH:
6480                 case REQ_SEARCH_BACK:
6481                 {
6482                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6483                         char *search = read_prompt(prompt);
6485                         if (search)
6486                                 string_ncopy(opt_search, search, strlen(search));
6487                         else
6488                                 request = REQ_NONE;
6489                         break;
6490                 }
6491                 case REQ_SCREEN_RESIZE:
6492                 {
6493                         int height, width;
6495                         getmaxyx(stdscr, height, width);
6497                         /* Resize the status view and let the view driver take
6498                          * care of resizing the displayed views. */
6499                         wresize(status_win, 1, width);
6500                         mvwin(status_win, height - 1, 0);
6501                         wrefresh(status_win);
6502                         break;
6503                 }
6504                 default:
6505                         break;
6506                 }
6507         }
6509         quit(0);
6511         return 0;