Code

Cleanup and improve option toggling actions to include help message
[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         static 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;
1024         unsigned int cleareol:1;
1026         void *data;             /* User data */
1027 };
1030 /*
1031  * Keys
1032  */
1034 struct keybinding {
1035         int alias;
1036         enum request request;
1037 };
1039 static struct keybinding default_keybindings[] = {
1040         /* View switching */
1041         { 'm',          REQ_VIEW_MAIN },
1042         { 'd',          REQ_VIEW_DIFF },
1043         { 'l',          REQ_VIEW_LOG },
1044         { 't',          REQ_VIEW_TREE },
1045         { 'f',          REQ_VIEW_BLOB },
1046         { 'B',          REQ_VIEW_BLAME },
1047         { 'p',          REQ_VIEW_PAGER },
1048         { 'h',          REQ_VIEW_HELP },
1049         { 'S',          REQ_VIEW_STATUS },
1050         { 'c',          REQ_VIEW_STAGE },
1052         /* View manipulation */
1053         { 'q',          REQ_VIEW_CLOSE },
1054         { KEY_TAB,      REQ_VIEW_NEXT },
1055         { KEY_RETURN,   REQ_ENTER },
1056         { KEY_UP,       REQ_PREVIOUS },
1057         { KEY_DOWN,     REQ_NEXT },
1058         { 'R',          REQ_REFRESH },
1059         { KEY_F(5),     REQ_REFRESH },
1060         { 'O',          REQ_MAXIMIZE },
1062         /* Cursor navigation */
1063         { 'k',          REQ_MOVE_UP },
1064         { 'j',          REQ_MOVE_DOWN },
1065         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1066         { KEY_END,      REQ_MOVE_LAST_LINE },
1067         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1068         { ' ',          REQ_MOVE_PAGE_DOWN },
1069         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1070         { 'b',          REQ_MOVE_PAGE_UP },
1071         { '-',          REQ_MOVE_PAGE_UP },
1073         /* Scrolling */
1074         { KEY_IC,       REQ_SCROLL_LINE_UP },
1075         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1076         { 'w',          REQ_SCROLL_PAGE_UP },
1077         { 's',          REQ_SCROLL_PAGE_DOWN },
1079         /* Searching */
1080         { '/',          REQ_SEARCH },
1081         { '?',          REQ_SEARCH_BACK },
1082         { 'n',          REQ_FIND_NEXT },
1083         { 'N',          REQ_FIND_PREV },
1085         /* Misc */
1086         { 'Q',          REQ_QUIT },
1087         { 'z',          REQ_STOP_LOADING },
1088         { 'v',          REQ_SHOW_VERSION },
1089         { 'r',          REQ_SCREEN_REDRAW },
1090         { '.',          REQ_TOGGLE_LINENO },
1091         { 'D',          REQ_TOGGLE_DATE },
1092         { 'A',          REQ_TOGGLE_AUTHOR },
1093         { 'g',          REQ_TOGGLE_REV_GRAPH },
1094         { 'F',          REQ_TOGGLE_REFS },
1095         { ':',          REQ_PROMPT },
1096         { 'u',          REQ_STATUS_UPDATE },
1097         { '!',          REQ_STATUS_REVERT },
1098         { 'M',          REQ_STATUS_MERGE },
1099         { '@',          REQ_STAGE_NEXT },
1100         { ',',          REQ_TREE_PARENT },
1101         { 'e',          REQ_EDIT },
1103         /* Using the ncurses SIGWINCH handler. */
1104         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1105 };
1107 #define KEYMAP_INFO \
1108         KEYMAP_(GENERIC), \
1109         KEYMAP_(MAIN), \
1110         KEYMAP_(DIFF), \
1111         KEYMAP_(LOG), \
1112         KEYMAP_(TREE), \
1113         KEYMAP_(BLOB), \
1114         KEYMAP_(BLAME), \
1115         KEYMAP_(PAGER), \
1116         KEYMAP_(HELP), \
1117         KEYMAP_(STATUS), \
1118         KEYMAP_(STAGE)
1120 enum keymap {
1121 #define KEYMAP_(name) KEYMAP_##name
1122         KEYMAP_INFO
1123 #undef  KEYMAP_
1124 };
1126 static struct int_map keymap_table[] = {
1127 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1128         KEYMAP_INFO
1129 #undef  KEYMAP_
1130 };
1132 #define set_keymap(map, name) \
1133         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1135 struct keybinding_table {
1136         struct keybinding *data;
1137         size_t size;
1138 };
1140 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1142 static void
1143 add_keybinding(enum keymap keymap, enum request request, int key)
1145         struct keybinding_table *table = &keybindings[keymap];
1147         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1148         if (!table->data)
1149                 die("Failed to allocate keybinding");
1150         table->data[table->size].alias = key;
1151         table->data[table->size++].request = request;
1154 /* Looks for a key binding first in the given map, then in the generic map, and
1155  * lastly in the default keybindings. */
1156 static enum request
1157 get_keybinding(enum keymap keymap, int key)
1159         size_t i;
1161         for (i = 0; i < keybindings[keymap].size; i++)
1162                 if (keybindings[keymap].data[i].alias == key)
1163                         return keybindings[keymap].data[i].request;
1165         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1166                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1167                         return keybindings[KEYMAP_GENERIC].data[i].request;
1169         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1170                 if (default_keybindings[i].alias == key)
1171                         return default_keybindings[i].request;
1173         return (enum request) key;
1177 struct key {
1178         const char *name;
1179         int value;
1180 };
1182 static struct key key_table[] = {
1183         { "Enter",      KEY_RETURN },
1184         { "Space",      ' ' },
1185         { "Backspace",  KEY_BACKSPACE },
1186         { "Tab",        KEY_TAB },
1187         { "Escape",     KEY_ESC },
1188         { "Left",       KEY_LEFT },
1189         { "Right",      KEY_RIGHT },
1190         { "Up",         KEY_UP },
1191         { "Down",       KEY_DOWN },
1192         { "Insert",     KEY_IC },
1193         { "Delete",     KEY_DC },
1194         { "Hash",       '#' },
1195         { "Home",       KEY_HOME },
1196         { "End",        KEY_END },
1197         { "PageUp",     KEY_PPAGE },
1198         { "PageDown",   KEY_NPAGE },
1199         { "F1",         KEY_F(1) },
1200         { "F2",         KEY_F(2) },
1201         { "F3",         KEY_F(3) },
1202         { "F4",         KEY_F(4) },
1203         { "F5",         KEY_F(5) },
1204         { "F6",         KEY_F(6) },
1205         { "F7",         KEY_F(7) },
1206         { "F8",         KEY_F(8) },
1207         { "F9",         KEY_F(9) },
1208         { "F10",        KEY_F(10) },
1209         { "F11",        KEY_F(11) },
1210         { "F12",        KEY_F(12) },
1211 };
1213 static int
1214 get_key_value(const char *name)
1216         int i;
1218         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1219                 if (!strcasecmp(key_table[i].name, name))
1220                         return key_table[i].value;
1222         if (strlen(name) == 1 && isprint(*name))
1223                 return (int) *name;
1225         return ERR;
1228 static const char *
1229 get_key_name(int key_value)
1231         static char key_char[] = "'X'";
1232         const char *seq = NULL;
1233         int key;
1235         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1236                 if (key_table[key].value == key_value)
1237                         seq = key_table[key].name;
1239         if (seq == NULL &&
1240             key_value < 127 &&
1241             isprint(key_value)) {
1242                 key_char[1] = (char) key_value;
1243                 seq = key_char;
1244         }
1246         return seq ? seq : "(no key)";
1249 static const char *
1250 get_key(enum request request)
1252         static char buf[BUFSIZ];
1253         size_t pos = 0;
1254         char *sep = "";
1255         int i;
1257         buf[pos] = 0;
1259         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1260                 struct keybinding *keybinding = &default_keybindings[i];
1262                 if (keybinding->request != request)
1263                         continue;
1265                 if (!string_format_from(buf, &pos, "%s%s", sep,
1266                                         get_key_name(keybinding->alias)))
1267                         return "Too many keybindings!";
1268                 sep = ", ";
1269         }
1271         return buf;
1274 struct run_request {
1275         enum keymap keymap;
1276         int key;
1277         const char *argv[SIZEOF_ARG];
1278 };
1280 static struct run_request *run_request;
1281 static size_t run_requests;
1283 static enum request
1284 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1286         struct run_request *req;
1288         if (argc >= ARRAY_SIZE(req->argv) - 1)
1289                 return REQ_NONE;
1291         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1292         if (!req)
1293                 return REQ_NONE;
1295         run_request = req;
1296         req = &run_request[run_requests];
1297         req->keymap = keymap;
1298         req->key = key;
1299         req->argv[0] = NULL;
1301         if (!format_argv(req->argv, argv, FORMAT_NONE))
1302                 return REQ_NONE;
1304         return REQ_NONE + ++run_requests;
1307 static struct run_request *
1308 get_run_request(enum request request)
1310         if (request <= REQ_NONE)
1311                 return NULL;
1312         return &run_request[request - REQ_NONE - 1];
1315 static void
1316 add_builtin_run_requests(void)
1318         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1319         const char *gc[] = { "git", "gc", NULL };
1320         struct {
1321                 enum keymap keymap;
1322                 int key;
1323                 int argc;
1324                 const char **argv;
1325         } reqs[] = {
1326                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1327                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1328         };
1329         int i;
1331         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1332                 enum request req;
1334                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1335                 if (req != REQ_NONE)
1336                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1337         }
1340 /*
1341  * User config file handling.
1342  */
1344 static struct int_map color_map[] = {
1345 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1346         COLOR_MAP(DEFAULT),
1347         COLOR_MAP(BLACK),
1348         COLOR_MAP(BLUE),
1349         COLOR_MAP(CYAN),
1350         COLOR_MAP(GREEN),
1351         COLOR_MAP(MAGENTA),
1352         COLOR_MAP(RED),
1353         COLOR_MAP(WHITE),
1354         COLOR_MAP(YELLOW),
1355 };
1357 #define set_color(color, name) \
1358         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1360 static struct int_map attr_map[] = {
1361 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1362         ATTR_MAP(NORMAL),
1363         ATTR_MAP(BLINK),
1364         ATTR_MAP(BOLD),
1365         ATTR_MAP(DIM),
1366         ATTR_MAP(REVERSE),
1367         ATTR_MAP(STANDOUT),
1368         ATTR_MAP(UNDERLINE),
1369 };
1371 #define set_attribute(attr, name) \
1372         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1374 static int   config_lineno;
1375 static bool  config_errors;
1376 static const char *config_msg;
1378 /* Wants: object fgcolor bgcolor [attr] */
1379 static int
1380 option_color_command(int argc, const char *argv[])
1382         struct line_info *info;
1384         if (argc != 3 && argc != 4) {
1385                 config_msg = "Wrong number of arguments given to color command";
1386                 return ERR;
1387         }
1389         info = get_line_info(argv[0]);
1390         if (!info) {
1391                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1392                         info = get_line_info("delimiter");
1394                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1395                         info = get_line_info("date");
1397                 } else {
1398                         config_msg = "Unknown color name";
1399                         return ERR;
1400                 }
1401         }
1403         if (set_color(&info->fg, argv[1]) == ERR ||
1404             set_color(&info->bg, argv[2]) == ERR) {
1405                 config_msg = "Unknown color";
1406                 return ERR;
1407         }
1409         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1410                 config_msg = "Unknown attribute";
1411                 return ERR;
1412         }
1414         return OK;
1417 static bool parse_bool(const char *s)
1419         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1420                 !strcmp(s, "yes")) ? TRUE : FALSE;
1423 static int
1424 parse_int(const char *s, int default_value, int min, int max)
1426         int value = atoi(s);
1428         return (value < min || value > max) ? default_value : value;
1431 /* Wants: name = value */
1432 static int
1433 option_set_command(int argc, const char *argv[])
1435         if (argc != 3) {
1436                 config_msg = "Wrong number of arguments given to set command";
1437                 return ERR;
1438         }
1440         if (strcmp(argv[1], "=")) {
1441                 config_msg = "No value assigned";
1442                 return ERR;
1443         }
1445         if (!strcmp(argv[0], "show-author")) {
1446                 opt_author = parse_bool(argv[2]);
1447                 return OK;
1448         }
1450         if (!strcmp(argv[0], "show-date")) {
1451                 opt_date = parse_bool(argv[2]);
1452                 return OK;
1453         }
1455         if (!strcmp(argv[0], "show-rev-graph")) {
1456                 opt_rev_graph = parse_bool(argv[2]);
1457                 return OK;
1458         }
1460         if (!strcmp(argv[0], "show-refs")) {
1461                 opt_show_refs = parse_bool(argv[2]);
1462                 return OK;
1463         }
1465         if (!strcmp(argv[0], "show-line-numbers")) {
1466                 opt_line_number = parse_bool(argv[2]);
1467                 return OK;
1468         }
1470         if (!strcmp(argv[0], "line-graphics")) {
1471                 opt_line_graphics = parse_bool(argv[2]);
1472                 return OK;
1473         }
1475         if (!strcmp(argv[0], "line-number-interval")) {
1476                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1477                 return OK;
1478         }
1480         if (!strcmp(argv[0], "author-width")) {
1481                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1482                 return OK;
1483         }
1485         if (!strcmp(argv[0], "tab-size")) {
1486                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1487                 return OK;
1488         }
1490         if (!strcmp(argv[0], "commit-encoding")) {
1491                 const char *arg = argv[2];
1492                 int arglen = strlen(arg);
1494                 switch (arg[0]) {
1495                 case '"':
1496                 case '\'':
1497                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1498                                 config_msg = "Unmatched quotation";
1499                                 return ERR;
1500                         }
1501                         arg += 1; arglen -= 2;
1502                 default:
1503                         string_ncopy(opt_encoding, arg, strlen(arg));
1504                         return OK;
1505                 }
1506         }
1508         config_msg = "Unknown variable name";
1509         return ERR;
1512 /* Wants: mode request key */
1513 static int
1514 option_bind_command(int argc, const char *argv[])
1516         enum request request;
1517         int keymap;
1518         int key;
1520         if (argc < 3) {
1521                 config_msg = "Wrong number of arguments given to bind command";
1522                 return ERR;
1523         }
1525         if (set_keymap(&keymap, argv[0]) == ERR) {
1526                 config_msg = "Unknown key map";
1527                 return ERR;
1528         }
1530         key = get_key_value(argv[1]);
1531         if (key == ERR) {
1532                 config_msg = "Unknown key";
1533                 return ERR;
1534         }
1536         request = get_request(argv[2]);
1537         if (request == REQ_NONE) {
1538                 const char *obsolete[] = { "cherry-pick" };
1539                 size_t namelen = strlen(argv[2]);
1540                 int i;
1542                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1543                         if (namelen == strlen(obsolete[i]) &&
1544                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1545                                 config_msg = "Obsolete request name";
1546                                 return ERR;
1547                         }
1548                 }
1549         }
1550         if (request == REQ_NONE && *argv[2]++ == '!')
1551                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1552         if (request == REQ_NONE) {
1553                 config_msg = "Unknown request name";
1554                 return ERR;
1555         }
1557         add_keybinding(keymap, request, key);
1559         return OK;
1562 static int
1563 set_option(const char *opt, char *value)
1565         const char *argv[SIZEOF_ARG];
1566         int argc = 0;
1568         if (!argv_from_string(argv, &argc, value)) {
1569                 config_msg = "Too many option arguments";
1570                 return ERR;
1571         }
1573         if (!strcmp(opt, "color"))
1574                 return option_color_command(argc, argv);
1576         if (!strcmp(opt, "set"))
1577                 return option_set_command(argc, argv);
1579         if (!strcmp(opt, "bind"))
1580                 return option_bind_command(argc, argv);
1582         config_msg = "Unknown option command";
1583         return ERR;
1586 static int
1587 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1589         int status = OK;
1591         config_lineno++;
1592         config_msg = "Internal error";
1594         /* Check for comment markers, since read_properties() will
1595          * only ensure opt and value are split at first " \t". */
1596         optlen = strcspn(opt, "#");
1597         if (optlen == 0)
1598                 return OK;
1600         if (opt[optlen] != 0) {
1601                 config_msg = "No option value";
1602                 status = ERR;
1604         }  else {
1605                 /* Look for comment endings in the value. */
1606                 size_t len = strcspn(value, "#");
1608                 if (len < valuelen) {
1609                         valuelen = len;
1610                         value[valuelen] = 0;
1611                 }
1613                 status = set_option(opt, value);
1614         }
1616         if (status == ERR) {
1617                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1618                         config_lineno, (int) optlen, opt, config_msg);
1619                 config_errors = TRUE;
1620         }
1622         /* Always keep going if errors are encountered. */
1623         return OK;
1626 static void
1627 load_option_file(const char *path)
1629         struct io io = {};
1631         /* It's ok that the file doesn't exist. */
1632         if (!io_open(&io, path))
1633                 return;
1635         config_lineno = 0;
1636         config_errors = FALSE;
1638         if (read_properties(&io, " \t", read_option) == ERR ||
1639             config_errors == TRUE)
1640                 fprintf(stderr, "Errors while loading %s.\n", path);
1643 static int
1644 load_options(void)
1646         const char *home = getenv("HOME");
1647         const char *tigrc_user = getenv("TIGRC_USER");
1648         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1649         char buf[SIZEOF_STR];
1651         add_builtin_run_requests();
1653         if (!tigrc_system) {
1654                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1655                         return ERR;
1656                 tigrc_system = buf;
1657         }
1658         load_option_file(tigrc_system);
1660         if (!tigrc_user) {
1661                 if (!home || !string_format(buf, "%s/.tigrc", home))
1662                         return ERR;
1663                 tigrc_user = buf;
1664         }
1665         load_option_file(tigrc_user);
1667         return OK;
1671 /*
1672  * The viewer
1673  */
1675 struct view;
1676 struct view_ops;
1678 /* The display array of active views and the index of the current view. */
1679 static struct view *display[2];
1680 static unsigned int current_view;
1682 /* Reading from the prompt? */
1683 static bool input_mode = FALSE;
1685 #define foreach_displayed_view(view, i) \
1686         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1688 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1690 /* Current head and commit ID */
1691 static char ref_blob[SIZEOF_REF]        = "";
1692 static char ref_commit[SIZEOF_REF]      = "HEAD";
1693 static char ref_head[SIZEOF_REF]        = "HEAD";
1695 struct view {
1696         const char *name;       /* View name */
1697         const char *cmd_env;    /* Command line set via environment */
1698         const char *id;         /* Points to either of ref_{head,commit,blob} */
1700         struct view_ops *ops;   /* View operations */
1702         enum keymap keymap;     /* What keymap does this view have */
1703         bool git_dir;           /* Whether the view requires a git directory. */
1705         char ref[SIZEOF_REF];   /* Hovered commit reference */
1706         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1708         int height, width;      /* The width and height of the main window */
1709         WINDOW *win;            /* The main window */
1710         WINDOW *title;          /* The title window living below the main window */
1712         /* Navigation */
1713         unsigned long offset;   /* Offset of the window top */
1714         unsigned long lineno;   /* Current line number */
1716         /* Searching */
1717         char grep[SIZEOF_STR];  /* Search string */
1718         regex_t *regex;         /* Pre-compiled regex */
1720         /* If non-NULL, points to the view that opened this view. If this view
1721          * is closed tig will switch back to the parent view. */
1722         struct view *parent;
1724         /* Buffering */
1725         size_t lines;           /* Total number of lines */
1726         struct line *line;      /* Line index */
1727         size_t line_alloc;      /* Total number of allocated lines */
1728         unsigned int digits;    /* Number of digits in the lines member. */
1730         /* Drawing */
1731         struct line *curline;   /* Line currently being drawn. */
1732         enum line_type curtype; /* Attribute currently used for drawing. */
1733         unsigned long col;      /* Column when drawing. */
1735         /* Loading */
1736         struct io io;
1737         struct io *pipe;
1738         time_t start_time;
1739         time_t update_secs;
1740 };
1742 struct view_ops {
1743         /* What type of content being displayed. Used in the title bar. */
1744         const char *type;
1745         /* Default command arguments. */
1746         const char **argv;
1747         /* Open and reads in all view content. */
1748         bool (*open)(struct view *view);
1749         /* Read one line; updates view->line. */
1750         bool (*read)(struct view *view, char *data);
1751         /* Draw one line; @lineno must be < view->height. */
1752         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1753         /* Depending on view handle a special requests. */
1754         enum request (*request)(struct view *view, enum request request, struct line *line);
1755         /* Search for regex in a line. */
1756         bool (*grep)(struct view *view, struct line *line);
1757         /* Select line */
1758         void (*select)(struct view *view, struct line *line);
1759 };
1761 static struct view_ops blame_ops;
1762 static struct view_ops blob_ops;
1763 static struct view_ops diff_ops;
1764 static struct view_ops help_ops;
1765 static struct view_ops log_ops;
1766 static struct view_ops main_ops;
1767 static struct view_ops pager_ops;
1768 static struct view_ops stage_ops;
1769 static struct view_ops status_ops;
1770 static struct view_ops tree_ops;
1772 #define VIEW_STR(name, env, ref, ops, map, git) \
1773         { name, #env, ref, ops, map, git }
1775 #define VIEW_(id, name, ops, git, ref) \
1776         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1779 static struct view views[] = {
1780         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1781         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1782         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1783         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1784         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1785         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1786         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1787         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1788         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1789         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1790 };
1792 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1793 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1795 #define foreach_view(view, i) \
1796         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1798 #define view_is_displayed(view) \
1799         (view == display[0] || view == display[1])
1802 enum line_graphic {
1803         LINE_GRAPHIC_VLINE
1804 };
1806 static int line_graphics[] = {
1807         /* LINE_GRAPHIC_VLINE: */ '|'
1808 };
1810 static inline void
1811 set_view_attr(struct view *view, enum line_type type)
1813         if (!view->curline->selected && view->curtype != type) {
1814                 wattrset(view->win, get_line_attr(type));
1815                 wchgat(view->win, -1, 0, type, NULL);
1816                 view->curtype = type;
1817         }
1820 static int
1821 draw_chars(struct view *view, enum line_type type, const char *string,
1822            int max_len, bool use_tilde)
1824         int len = 0;
1825         int col = 0;
1826         int trimmed = FALSE;
1828         if (max_len <= 0)
1829                 return 0;
1831         if (opt_utf8) {
1832                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1833         } else {
1834                 col = len = strlen(string);
1835                 if (len > max_len) {
1836                         if (use_tilde) {
1837                                 max_len -= 1;
1838                         }
1839                         col = len = max_len;
1840                         trimmed = TRUE;
1841                 }
1842         }
1844         set_view_attr(view, type);
1845         waddnstr(view->win, string, len);
1846         if (trimmed && use_tilde) {
1847                 set_view_attr(view, LINE_DELIMITER);
1848                 waddch(view->win, '~');
1849                 col++;
1850         }
1852         return col;
1855 static int
1856 draw_space(struct view *view, enum line_type type, int max, int spaces)
1858         static char space[] = "                    ";
1859         int col = 0;
1861         spaces = MIN(max, spaces);
1863         while (spaces > 0) {
1864                 int len = MIN(spaces, sizeof(space) - 1);
1866                 col += draw_chars(view, type, space, spaces, FALSE);
1867                 spaces -= len;
1868         }
1870         return col;
1873 static bool
1874 draw_lineno(struct view *view, unsigned int lineno)
1876         char number[10];
1877         int digits3 = view->digits < 3 ? 3 : view->digits;
1878         int max_number = MIN(digits3, STRING_SIZE(number));
1879         int max = view->width - view->col;
1880         int col;
1882         if (max < max_number)
1883                 max_number = max;
1885         lineno += view->offset + 1;
1886         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1887                 static char fmt[] = "%1ld";
1889                 if (view->digits <= 9)
1890                         fmt[1] = '0' + digits3;
1892                 if (!string_format(number, fmt, lineno))
1893                         number[0] = 0;
1894                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1895         } else {
1896                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1897         }
1899         if (col < max) {
1900                 set_view_attr(view, LINE_DEFAULT);
1901                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1902                 col++;
1903         }
1905         if (col < max)
1906                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1907         view->col += col;
1909         return view->width - view->col <= 0;
1912 static bool
1913 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1915         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1916         return view->width - view->col <= 0;
1919 static bool
1920 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1922         int max = view->width - view->col;
1923         int i;
1925         if (max < size)
1926                 size = max;
1928         set_view_attr(view, type);
1929         /* Using waddch() instead of waddnstr() ensures that
1930          * they'll be rendered correctly for the cursor line. */
1931         for (i = 0; i < size; i++)
1932                 waddch(view->win, graphic[i]);
1934         view->col += size;
1935         if (size < max) {
1936                 waddch(view->win, ' ');
1937                 view->col++;
1938         }
1940         return view->width - view->col <= 0;
1943 static bool
1944 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1946         int max = MIN(view->width - view->col, len);
1947         int col;
1949         if (text)
1950                 col = draw_chars(view, type, text, max - 1, trim);
1951         else
1952                 col = draw_space(view, type, max - 1, max - 1);
1954         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1955         return view->width - view->col <= 0;
1958 static bool
1959 draw_date(struct view *view, struct tm *time)
1961         char buf[DATE_COLS];
1962         char *date;
1963         int timelen = 0;
1965         if (time)
1966                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1967         date = timelen ? buf : NULL;
1969         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1972 static bool
1973 draw_view_line(struct view *view, unsigned int lineno)
1975         struct line *line;
1976         bool selected = (view->offset + lineno == view->lineno);
1977         bool draw_ok;
1979         assert(view_is_displayed(view));
1981         if (view->offset + lineno >= view->lines)
1982                 return FALSE;
1984         line = &view->line[view->offset + lineno];
1986         wmove(view->win, lineno, 0);
1987         if (line->cleareol)
1988                 wclrtoeol(view->win);
1989         view->col = 0;
1990         view->curline = line;
1991         view->curtype = LINE_NONE;
1992         line->selected = FALSE;
1993         line->dirty = line->cleareol = 0;
1995         if (selected) {
1996                 set_view_attr(view, LINE_CURSOR);
1997                 line->selected = TRUE;
1998                 view->ops->select(view, line);
1999         }
2001         scrollok(view->win, FALSE);
2002         draw_ok = view->ops->draw(view, line, lineno);
2003         scrollok(view->win, TRUE);
2005         return draw_ok;
2008 static void
2009 redraw_view_dirty(struct view *view)
2011         bool dirty = FALSE;
2012         int lineno;
2014         for (lineno = 0; lineno < view->height; lineno++) {
2015                 if (view->offset + lineno >= view->lines)
2016                         break;
2017                 if (!view->line[view->offset + lineno].dirty)
2018                         continue;
2019                 dirty = TRUE;
2020                 if (!draw_view_line(view, lineno))
2021                         break;
2022         }
2024         if (!dirty)
2025                 return;
2026         redrawwin(view->win);
2027         if (input_mode)
2028                 wnoutrefresh(view->win);
2029         else
2030                 wrefresh(view->win);
2033 static void
2034 redraw_view_from(struct view *view, int lineno)
2036         assert(0 <= lineno && lineno < view->height);
2038         for (; lineno < view->height; lineno++) {
2039                 if (!draw_view_line(view, lineno))
2040                         break;
2041         }
2043         redrawwin(view->win);
2044         if (input_mode)
2045                 wnoutrefresh(view->win);
2046         else
2047                 wrefresh(view->win);
2050 static void
2051 redraw_view(struct view *view)
2053         wclear(view->win);
2054         redraw_view_from(view, 0);
2058 static void
2059 update_view_title(struct view *view)
2061         char buf[SIZEOF_STR];
2062         char state[SIZEOF_STR];
2063         size_t bufpos = 0, statelen = 0;
2065         assert(view_is_displayed(view));
2067         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2068                 unsigned int view_lines = view->offset + view->height;
2069                 unsigned int lines = view->lines
2070                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2071                                    : 0;
2073                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2074                                    view->ops->type,
2075                                    view->lineno + 1,
2076                                    view->lines,
2077                                    lines);
2079         }
2081         if (view->pipe) {
2082                 time_t secs = time(NULL) - view->start_time;
2084                 /* Three git seconds are a long time ... */
2085                 if (secs > 2)
2086                         string_format_from(state, &statelen, " loading %lds", secs);
2087         }
2089         string_format_from(buf, &bufpos, "[%s]", view->name);
2090         if (*view->ref && bufpos < view->width) {
2091                 size_t refsize = strlen(view->ref);
2092                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2094                 if (minsize < view->width)
2095                         refsize = view->width - minsize + 7;
2096                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2097         }
2099         if (statelen && bufpos < view->width) {
2100                 string_format_from(buf, &bufpos, "%s", state);
2101         }
2103         if (view == display[current_view])
2104                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2105         else
2106                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2108         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2109         wclrtoeol(view->title);
2110         wmove(view->title, 0, view->width - 1);
2112         if (input_mode)
2113                 wnoutrefresh(view->title);
2114         else
2115                 wrefresh(view->title);
2118 static void
2119 resize_display(void)
2121         int offset, i;
2122         struct view *base = display[0];
2123         struct view *view = display[1] ? display[1] : display[0];
2125         /* Setup window dimensions */
2127         getmaxyx(stdscr, base->height, base->width);
2129         /* Make room for the status window. */
2130         base->height -= 1;
2132         if (view != base) {
2133                 /* Horizontal split. */
2134                 view->width   = base->width;
2135                 view->height  = SCALE_SPLIT_VIEW(base->height);
2136                 base->height -= view->height;
2138                 /* Make room for the title bar. */
2139                 view->height -= 1;
2140         }
2142         /* Make room for the title bar. */
2143         base->height -= 1;
2145         offset = 0;
2147         foreach_displayed_view (view, i) {
2148                 if (!view->win) {
2149                         view->win = newwin(view->height, 0, offset, 0);
2150                         if (!view->win)
2151                                 die("Failed to create %s view", view->name);
2153                         scrollok(view->win, TRUE);
2155                         view->title = newwin(1, 0, offset + view->height, 0);
2156                         if (!view->title)
2157                                 die("Failed to create title window");
2159                 } else {
2160                         wresize(view->win, view->height, view->width);
2161                         mvwin(view->win,   offset, 0);
2162                         mvwin(view->title, offset + view->height, 0);
2163                 }
2165                 offset += view->height + 1;
2166         }
2169 static void
2170 redraw_display(void)
2172         struct view *view;
2173         int i;
2175         foreach_displayed_view (view, i) {
2176                 redraw_view(view);
2177                 update_view_title(view);
2178         }
2181 static void
2182 update_display_cursor(struct view *view)
2184         /* Move the cursor to the right-most column of the cursor line.
2185          *
2186          * XXX: This could turn out to be a bit expensive, but it ensures that
2187          * the cursor does not jump around. */
2188         if (view->lines) {
2189                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2190                 wrefresh(view->win);
2191         }
2194 static void
2195 toggle_view_option(bool *option, const char *help)
2197         *option = !*option;
2198         redraw_display();
2199         report("%sabling %s", *option ? "En" : "Dis", help);
2202 /*
2203  * Navigation
2204  */
2206 /* Scrolling backend */
2207 static void
2208 do_scroll_view(struct view *view, int lines)
2210         bool redraw_current_line = FALSE;
2212         /* The rendering expects the new offset. */
2213         view->offset += lines;
2215         assert(0 <= view->offset && view->offset < view->lines);
2216         assert(lines);
2218         /* Move current line into the view. */
2219         if (view->lineno < view->offset) {
2220                 view->lineno = view->offset;
2221                 redraw_current_line = TRUE;
2222         } else if (view->lineno >= view->offset + view->height) {
2223                 view->lineno = view->offset + view->height - 1;
2224                 redraw_current_line = TRUE;
2225         }
2227         assert(view->offset <= view->lineno && view->lineno < view->lines);
2229         /* Redraw the whole screen if scrolling is pointless. */
2230         if (view->height < ABS(lines)) {
2231                 redraw_view(view);
2233         } else {
2234                 int line = lines > 0 ? view->height - lines : 0;
2235                 int end = line + ABS(lines);
2237                 wscrl(view->win, lines);
2239                 for (; line < end; line++) {
2240                         if (!draw_view_line(view, line))
2241                                 break;
2242                 }
2244                 if (redraw_current_line)
2245                         draw_view_line(view, view->lineno - view->offset);
2246         }
2248         redrawwin(view->win);
2249         wrefresh(view->win);
2250         report("");
2253 /* Scroll frontend */
2254 static void
2255 scroll_view(struct view *view, enum request request)
2257         int lines = 1;
2259         assert(view_is_displayed(view));
2261         switch (request) {
2262         case REQ_SCROLL_PAGE_DOWN:
2263                 lines = view->height;
2264         case REQ_SCROLL_LINE_DOWN:
2265                 if (view->offset + lines > view->lines)
2266                         lines = view->lines - view->offset;
2268                 if (lines == 0 || view->offset + view->height >= view->lines) {
2269                         report("Cannot scroll beyond the last line");
2270                         return;
2271                 }
2272                 break;
2274         case REQ_SCROLL_PAGE_UP:
2275                 lines = view->height;
2276         case REQ_SCROLL_LINE_UP:
2277                 if (lines > view->offset)
2278                         lines = view->offset;
2280                 if (lines == 0) {
2281                         report("Cannot scroll beyond the first line");
2282                         return;
2283                 }
2285                 lines = -lines;
2286                 break;
2288         default:
2289                 die("request %d not handled in switch", request);
2290         }
2292         do_scroll_view(view, lines);
2295 /* Cursor moving */
2296 static void
2297 move_view(struct view *view, enum request request)
2299         int scroll_steps = 0;
2300         int steps;
2302         switch (request) {
2303         case REQ_MOVE_FIRST_LINE:
2304                 steps = -view->lineno;
2305                 break;
2307         case REQ_MOVE_LAST_LINE:
2308                 steps = view->lines - view->lineno - 1;
2309                 break;
2311         case REQ_MOVE_PAGE_UP:
2312                 steps = view->height > view->lineno
2313                       ? -view->lineno : -view->height;
2314                 break;
2316         case REQ_MOVE_PAGE_DOWN:
2317                 steps = view->lineno + view->height >= view->lines
2318                       ? view->lines - view->lineno - 1 : view->height;
2319                 break;
2321         case REQ_MOVE_UP:
2322                 steps = -1;
2323                 break;
2325         case REQ_MOVE_DOWN:
2326                 steps = 1;
2327                 break;
2329         default:
2330                 die("request %d not handled in switch", request);
2331         }
2333         if (steps <= 0 && view->lineno == 0) {
2334                 report("Cannot move beyond the first line");
2335                 return;
2337         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2338                 report("Cannot move beyond the last line");
2339                 return;
2340         }
2342         /* Move the current line */
2343         view->lineno += steps;
2344         assert(0 <= view->lineno && view->lineno < view->lines);
2346         /* Check whether the view needs to be scrolled */
2347         if (view->lineno < view->offset ||
2348             view->lineno >= view->offset + view->height) {
2349                 scroll_steps = steps;
2350                 if (steps < 0 && -steps > view->offset) {
2351                         scroll_steps = -view->offset;
2353                 } else if (steps > 0) {
2354                         if (view->lineno == view->lines - 1 &&
2355                             view->lines > view->height) {
2356                                 scroll_steps = view->lines - view->offset - 1;
2357                                 if (scroll_steps >= view->height)
2358                                         scroll_steps -= view->height - 1;
2359                         }
2360                 }
2361         }
2363         if (!view_is_displayed(view)) {
2364                 view->offset += scroll_steps;
2365                 assert(0 <= view->offset && view->offset < view->lines);
2366                 view->ops->select(view, &view->line[view->lineno]);
2367                 return;
2368         }
2370         /* Repaint the old "current" line if we be scrolling */
2371         if (ABS(steps) < view->height)
2372                 draw_view_line(view, view->lineno - steps - view->offset);
2374         if (scroll_steps) {
2375                 do_scroll_view(view, scroll_steps);
2376                 return;
2377         }
2379         /* Draw the current line */
2380         draw_view_line(view, view->lineno - view->offset);
2382         redrawwin(view->win);
2383         wrefresh(view->win);
2384         report("");
2388 /*
2389  * Searching
2390  */
2392 static void search_view(struct view *view, enum request request);
2394 static bool
2395 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2397         assert(view_is_displayed(view));
2399         if (!view->ops->grep(view, line))
2400                 return FALSE;
2402         if (lineno - view->offset >= view->height) {
2403                 view->offset = lineno;
2404                 view->lineno = lineno;
2405                 redraw_view(view);
2407         } else {
2408                 unsigned long old_lineno = view->lineno - view->offset;
2410                 view->lineno = lineno;
2411                 draw_view_line(view, old_lineno);
2413                 draw_view_line(view, view->lineno - view->offset);
2414                 redrawwin(view->win);
2415                 wrefresh(view->win);
2416         }
2418         report("Line %ld matches '%s'", lineno + 1, view->grep);
2419         return TRUE;
2422 static void
2423 find_next(struct view *view, enum request request)
2425         unsigned long lineno = view->lineno;
2426         int direction;
2428         if (!*view->grep) {
2429                 if (!*opt_search)
2430                         report("No previous search");
2431                 else
2432                         search_view(view, request);
2433                 return;
2434         }
2436         switch (request) {
2437         case REQ_SEARCH:
2438         case REQ_FIND_NEXT:
2439                 direction = 1;
2440                 break;
2442         case REQ_SEARCH_BACK:
2443         case REQ_FIND_PREV:
2444                 direction = -1;
2445                 break;
2447         default:
2448                 return;
2449         }
2451         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2452                 lineno += direction;
2454         /* Note, lineno is unsigned long so will wrap around in which case it
2455          * will become bigger than view->lines. */
2456         for (; lineno < view->lines; lineno += direction) {
2457                 struct line *line = &view->line[lineno];
2459                 if (find_next_line(view, lineno, line))
2460                         return;
2461         }
2463         report("No match found for '%s'", view->grep);
2466 static void
2467 search_view(struct view *view, enum request request)
2469         int regex_err;
2471         if (view->regex) {
2472                 regfree(view->regex);
2473                 *view->grep = 0;
2474         } else {
2475                 view->regex = calloc(1, sizeof(*view->regex));
2476                 if (!view->regex)
2477                         return;
2478         }
2480         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2481         if (regex_err != 0) {
2482                 char buf[SIZEOF_STR] = "unknown error";
2484                 regerror(regex_err, view->regex, buf, sizeof(buf));
2485                 report("Search failed: %s", buf);
2486                 return;
2487         }
2489         string_copy(view->grep, opt_search);
2491         find_next(view, request);
2494 /*
2495  * Incremental updating
2496  */
2498 static void
2499 reset_view(struct view *view)
2501         int i;
2503         for (i = 0; i < view->lines; i++)
2504                 free(view->line[i].data);
2505         free(view->line);
2507         view->line = NULL;
2508         view->offset = 0;
2509         view->lines  = 0;
2510         view->lineno = 0;
2511         view->line_alloc = 0;
2512         view->vid[0] = 0;
2513         view->update_secs = 0;
2516 static void
2517 free_argv(const char *argv[])
2519         int argc;
2521         for (argc = 0; argv[argc]; argc++)
2522                 free((void *) argv[argc]);
2525 static bool
2526 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2528         char buf[SIZEOF_STR];
2529         int argc;
2530         bool noreplace = flags == FORMAT_NONE;
2532         free_argv(dst_argv);
2534         for (argc = 0; src_argv[argc]; argc++) {
2535                 const char *arg = src_argv[argc];
2536                 size_t bufpos = 0;
2538                 while (arg) {
2539                         char *next = strstr(arg, "%(");
2540                         int len = next - arg;
2541                         const char *value;
2543                         if (!next || noreplace) {
2544                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2545                                         noreplace = TRUE;
2546                                 len = strlen(arg);
2547                                 value = "";
2549                         } else if (!prefixcmp(next, "%(directory)")) {
2550                                 value = opt_path;
2552                         } else if (!prefixcmp(next, "%(file)")) {
2553                                 value = opt_file;
2555                         } else if (!prefixcmp(next, "%(ref)")) {
2556                                 value = *opt_ref ? opt_ref : "HEAD";
2558                         } else if (!prefixcmp(next, "%(head)")) {
2559                                 value = ref_head;
2561                         } else if (!prefixcmp(next, "%(commit)")) {
2562                                 value = ref_commit;
2564                         } else if (!prefixcmp(next, "%(blob)")) {
2565                                 value = ref_blob;
2567                         } else {
2568                                 report("Unknown replacement: `%s`", next);
2569                                 return FALSE;
2570                         }
2572                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2573                                 return FALSE;
2575                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2576                 }
2578                 dst_argv[argc] = strdup(buf);
2579                 if (!dst_argv[argc])
2580                         break;
2581         }
2583         dst_argv[argc] = NULL;
2585         return src_argv[argc] == NULL;
2588 static void
2589 end_update(struct view *view, bool force)
2591         if (!view->pipe)
2592                 return;
2593         while (!view->ops->read(view, NULL))
2594                 if (!force)
2595                         return;
2596         set_nonblocking_input(FALSE);
2597         if (force)
2598                 kill_io(view->pipe);
2599         done_io(view->pipe);
2600         view->pipe = NULL;
2603 static void
2604 setup_update(struct view *view, const char *vid)
2606         set_nonblocking_input(TRUE);
2607         reset_view(view);
2608         string_copy_rev(view->vid, vid);
2609         view->pipe = &view->io;
2610         view->start_time = time(NULL);
2613 static bool
2614 prepare_update(struct view *view, const char *argv[], const char *dir,
2615                enum format_flags flags)
2617         if (view->pipe)
2618                 end_update(view, TRUE);
2619         return init_io_rd(&view->io, argv, dir, flags);
2622 static bool
2623 prepare_update_file(struct view *view, const char *name)
2625         if (view->pipe)
2626                 end_update(view, TRUE);
2627         return io_open(&view->io, name);
2630 static bool
2631 begin_update(struct view *view, bool refresh)
2633         if (view->pipe)
2634                 end_update(view, TRUE);
2636         if (refresh) {
2637                 if (!start_io(&view->io))
2638                         return FALSE;
2640         } else {
2641                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2642                         opt_path[0] = 0;
2644                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2645                         return FALSE;
2647                 /* Put the current ref_* value to the view title ref
2648                  * member. This is needed by the blob view. Most other
2649                  * views sets it automatically after loading because the
2650                  * first line is a commit line. */
2651                 string_copy_rev(view->ref, view->id);
2652         }
2654         setup_update(view, view->id);
2656         return TRUE;
2659 #define ITEM_CHUNK_SIZE 256
2660 static void *
2661 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2663         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2664         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2666         if (mem == NULL || num_chunks != num_chunks_new) {
2667                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2668                 mem = realloc(mem, *size * item_size);
2669         }
2671         return mem;
2674 static struct line *
2675 realloc_lines(struct view *view, size_t line_size)
2677         size_t alloc = view->line_alloc;
2678         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2679                                          sizeof(*view->line));
2681         if (!tmp)
2682                 return NULL;
2684         view->line = tmp;
2685         view->line_alloc = alloc;
2686         return view->line;
2689 static bool
2690 update_view(struct view *view)
2692         char out_buffer[BUFSIZ * 2];
2693         char *line;
2694         /* Clear the view and redraw everything since the tree sorting
2695          * might have rearranged things. */
2696         bool redraw = view->lines == 0;
2697         bool can_read = TRUE;
2699         if (!view->pipe)
2700                 return TRUE;
2702         if (!io_can_read(view->pipe)) {
2703                 if (view->lines == 0) {
2704                         time_t secs = time(NULL) - view->start_time;
2706                         if (secs > view->update_secs) {
2707                                 if (view->update_secs == 0)
2708                                         redraw_view(view);
2709                                 update_view_title(view);
2710                                 view->update_secs = secs;
2711                         }
2712                 }
2713                 return TRUE;
2714         }
2716         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2717                 if (opt_iconv != ICONV_NONE) {
2718                         ICONV_CONST char *inbuf = line;
2719                         size_t inlen = strlen(line) + 1;
2721                         char *outbuf = out_buffer;
2722                         size_t outlen = sizeof(out_buffer);
2724                         size_t ret;
2726                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2727                         if (ret != (size_t) -1)
2728                                 line = out_buffer;
2729                 }
2731                 if (!view->ops->read(view, line))
2732                         goto alloc_error;
2733         }
2735         {
2736                 unsigned long lines = view->lines;
2737                 int digits;
2739                 for (digits = 0; lines; digits++)
2740                         lines /= 10;
2742                 /* Keep the displayed view in sync with line number scaling. */
2743                 if (digits != view->digits) {
2744                         view->digits = digits;
2745                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2746                                 redraw = TRUE;
2747                 }
2748         }
2750         if (io_error(view->pipe)) {
2751                 report("Failed to read: %s", io_strerror(view->pipe));
2752                 end_update(view, TRUE);
2754         } else if (io_eof(view->pipe)) {
2755                 report("");
2756                 end_update(view, FALSE);
2757         }
2759         if (!view_is_displayed(view))
2760                 return TRUE;
2762         if (redraw)
2763                 redraw_view_from(view, 0);
2764         else
2765                 redraw_view_dirty(view);
2767         /* Update the title _after_ the redraw so that if the redraw picks up a
2768          * commit reference in view->ref it'll be available here. */
2769         update_view_title(view);
2770         return TRUE;
2772 alloc_error:
2773         report("Allocation failure");
2774         end_update(view, TRUE);
2775         return FALSE;
2778 static struct line *
2779 add_line_data(struct view *view, void *data, enum line_type type)
2781         struct line *line;
2783         if (!realloc_lines(view, view->lines + 1))
2784                 return NULL;
2786         line = &view->line[view->lines++];
2787         memset(line, 0, sizeof(*line));
2788         line->type = type;
2789         line->data = data;
2790         line->dirty = 1;
2792         return line;
2795 static struct line *
2796 add_line_text(struct view *view, const char *text, enum line_type type)
2798         char *data = text ? strdup(text) : NULL;
2800         return data ? add_line_data(view, data, type) : NULL;
2803 static struct line *
2804 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2806         char buf[SIZEOF_STR];
2807         va_list args;
2809         va_start(args, fmt);
2810         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2811                 buf[0] = 0;
2812         va_end(args);
2814         return buf[0] ? add_line_text(view, buf, type) : NULL;
2817 /*
2818  * View opening
2819  */
2821 enum open_flags {
2822         OPEN_DEFAULT = 0,       /* Use default view switching. */
2823         OPEN_SPLIT = 1,         /* Split current view. */
2824         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2825         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2826         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2827         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2828         OPEN_PREPARED = 32,     /* Open already prepared command. */
2829 };
2831 static void
2832 open_view(struct view *prev, enum request request, enum open_flags flags)
2834         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2835         bool split = !!(flags & OPEN_SPLIT);
2836         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2837         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2838         struct view *view = VIEW(request);
2839         int nviews = displayed_views();
2840         struct view *base_view = display[0];
2842         if (view == prev && nviews == 1 && !reload) {
2843                 report("Already in %s view", view->name);
2844                 return;
2845         }
2847         if (view->git_dir && !opt_git_dir[0]) {
2848                 report("The %s view is disabled in pager view", view->name);
2849                 return;
2850         }
2852         if (split) {
2853                 display[1] = view;
2854                 if (!backgrounded)
2855                         current_view = 1;
2856         } else if (!nomaximize) {
2857                 /* Maximize the current view. */
2858                 memset(display, 0, sizeof(display));
2859                 current_view = 0;
2860                 display[current_view] = view;
2861         }
2863         /* Resize the view when switching between split- and full-screen,
2864          * or when switching between two different full-screen views. */
2865         if (nviews != displayed_views() ||
2866             (nviews == 1 && base_view != display[0]))
2867                 resize_display();
2869         if (view->ops->open) {
2870                 if (!view->ops->open(view)) {
2871                         report("Failed to load %s view", view->name);
2872                         return;
2873                 }
2875         } else if ((reload || strcmp(view->vid, view->id)) &&
2876                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2877                 report("Failed to load %s view", view->name);
2878                 return;
2879         }
2881         if (split && prev->lineno - prev->offset >= prev->height) {
2882                 /* Take the title line into account. */
2883                 int lines = prev->lineno - prev->offset - prev->height + 1;
2885                 /* Scroll the view that was split if the current line is
2886                  * outside the new limited view. */
2887                 do_scroll_view(prev, lines);
2888         }
2890         if (prev && view != prev) {
2891                 if (split && !backgrounded) {
2892                         /* "Blur" the previous view. */
2893                         update_view_title(prev);
2894                 }
2896                 view->parent = prev;
2897         }
2899         if (view->pipe && view->lines == 0) {
2900                 /* Clear the old view and let the incremental updating refill
2901                  * the screen. */
2902                 werase(view->win);
2903                 report("");
2904         } else if (view_is_displayed(view)) {
2905                 redraw_view(view);
2906                 report("");
2907         }
2909         /* If the view is backgrounded the above calls to report()
2910          * won't redraw the view title. */
2911         if (backgrounded)
2912                 update_view_title(view);
2915 static void
2916 open_external_viewer(const char *argv[], const char *dir)
2918         def_prog_mode();           /* save current tty modes */
2919         endwin();                  /* restore original tty modes */
2920         run_io_fg(argv, dir);
2921         fprintf(stderr, "Press Enter to continue");
2922         getc(opt_tty);
2923         reset_prog_mode();
2924         redraw_display();
2927 static void
2928 open_mergetool(const char *file)
2930         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2932         open_external_viewer(mergetool_argv, opt_cdup);
2935 static void
2936 open_editor(bool from_root, const char *file)
2938         const char *editor_argv[] = { "vi", file, NULL };
2939         const char *editor;
2941         editor = getenv("GIT_EDITOR");
2942         if (!editor && *opt_editor)
2943                 editor = opt_editor;
2944         if (!editor)
2945                 editor = getenv("VISUAL");
2946         if (!editor)
2947                 editor = getenv("EDITOR");
2948         if (!editor)
2949                 editor = "vi";
2951         editor_argv[0] = editor;
2952         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2955 static void
2956 open_run_request(enum request request)
2958         struct run_request *req = get_run_request(request);
2959         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2961         if (!req) {
2962                 report("Unknown run request");
2963                 return;
2964         }
2966         if (format_argv(argv, req->argv, FORMAT_ALL))
2967                 open_external_viewer(argv, NULL);
2968         free_argv(argv);
2971 /*
2972  * User request switch noodle
2973  */
2975 static int
2976 view_driver(struct view *view, enum request request)
2978         int i;
2980         if (request == REQ_NONE) {
2981                 doupdate();
2982                 return TRUE;
2983         }
2985         if (request > REQ_NONE) {
2986                 open_run_request(request);
2987                 /* FIXME: When all views can refresh always do this. */
2988                 if (view == VIEW(REQ_VIEW_STATUS) ||
2989                     view == VIEW(REQ_VIEW_MAIN) ||
2990                     view == VIEW(REQ_VIEW_LOG) ||
2991                     view == VIEW(REQ_VIEW_STAGE))
2992                         request = REQ_REFRESH;
2993                 else
2994                         return TRUE;
2995         }
2997         if (view && view->lines) {
2998                 request = view->ops->request(view, request, &view->line[view->lineno]);
2999                 if (request == REQ_NONE)
3000                         return TRUE;
3001         }
3003         switch (request) {
3004         case REQ_MOVE_UP:
3005         case REQ_MOVE_DOWN:
3006         case REQ_MOVE_PAGE_UP:
3007         case REQ_MOVE_PAGE_DOWN:
3008         case REQ_MOVE_FIRST_LINE:
3009         case REQ_MOVE_LAST_LINE:
3010                 move_view(view, request);
3011                 break;
3013         case REQ_SCROLL_LINE_DOWN:
3014         case REQ_SCROLL_LINE_UP:
3015         case REQ_SCROLL_PAGE_DOWN:
3016         case REQ_SCROLL_PAGE_UP:
3017                 scroll_view(view, request);
3018                 break;
3020         case REQ_VIEW_BLAME:
3021                 if (!opt_file[0]) {
3022                         report("No file chosen, press %s to open tree view",
3023                                get_key(REQ_VIEW_TREE));
3024                         break;
3025                 }
3026                 open_view(view, request, OPEN_DEFAULT);
3027                 break;
3029         case REQ_VIEW_BLOB:
3030                 if (!ref_blob[0]) {
3031                         report("No file chosen, press %s to open tree view",
3032                                get_key(REQ_VIEW_TREE));
3033                         break;
3034                 }
3035                 open_view(view, request, OPEN_DEFAULT);
3036                 break;
3038         case REQ_VIEW_PAGER:
3039                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3040                         report("No pager content, press %s to run command from prompt",
3041                                get_key(REQ_PROMPT));
3042                         break;
3043                 }
3044                 open_view(view, request, OPEN_DEFAULT);
3045                 break;
3047         case REQ_VIEW_STAGE:
3048                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3049                         report("No stage content, press %s to open the status view and choose file",
3050                                get_key(REQ_VIEW_STATUS));
3051                         break;
3052                 }
3053                 open_view(view, request, OPEN_DEFAULT);
3054                 break;
3056         case REQ_VIEW_STATUS:
3057                 if (opt_is_inside_work_tree == FALSE) {
3058                         report("The status view requires a working tree");
3059                         break;
3060                 }
3061                 open_view(view, request, OPEN_DEFAULT);
3062                 break;
3064         case REQ_VIEW_MAIN:
3065         case REQ_VIEW_DIFF:
3066         case REQ_VIEW_LOG:
3067         case REQ_VIEW_TREE:
3068         case REQ_VIEW_HELP:
3069                 open_view(view, request, OPEN_DEFAULT);
3070                 break;
3072         case REQ_NEXT:
3073         case REQ_PREVIOUS:
3074                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3076                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3077                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3078                    (view == VIEW(REQ_VIEW_DIFF) &&
3079                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3080                    (view == VIEW(REQ_VIEW_STAGE) &&
3081                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3082                    (view == VIEW(REQ_VIEW_BLOB) &&
3083                      view->parent == VIEW(REQ_VIEW_TREE))) {
3084                         int line;
3086                         view = view->parent;
3087                         line = view->lineno;
3088                         move_view(view, request);
3089                         if (view_is_displayed(view))
3090                                 update_view_title(view);
3091                         if (line != view->lineno)
3092                                 view->ops->request(view, REQ_ENTER,
3093                                                    &view->line[view->lineno]);
3095                 } else {
3096                         move_view(view, request);
3097                 }
3098                 break;
3100         case REQ_VIEW_NEXT:
3101         {
3102                 int nviews = displayed_views();
3103                 int next_view = (current_view + 1) % nviews;
3105                 if (next_view == current_view) {
3106                         report("Only one view is displayed");
3107                         break;
3108                 }
3110                 current_view = next_view;
3111                 /* Blur out the title of the previous view. */
3112                 update_view_title(view);
3113                 report("");
3114                 break;
3115         }
3116         case REQ_REFRESH:
3117                 report("Refreshing is not yet supported for the %s view", view->name);
3118                 break;
3120         case REQ_MAXIMIZE:
3121                 if (displayed_views() == 2)
3122                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3123                 break;
3125         case REQ_TOGGLE_LINENO:
3126                 toggle_view_option(&opt_line_number, "line numbers");
3127                 break;
3129         case REQ_TOGGLE_DATE:
3130                 toggle_view_option(&opt_date, "date display");
3131                 break;
3133         case REQ_TOGGLE_AUTHOR:
3134                 toggle_view_option(&opt_author, "author display");
3135                 break;
3137         case REQ_TOGGLE_REV_GRAPH:
3138                 toggle_view_option(&opt_rev_graph, "revision graph display");
3139                 break;
3141         case REQ_TOGGLE_REFS:
3142                 toggle_view_option(&opt_show_refs, "reference display");
3143                 break;
3145         case REQ_SEARCH:
3146         case REQ_SEARCH_BACK:
3147                 search_view(view, request);
3148                 break;
3150         case REQ_FIND_NEXT:
3151         case REQ_FIND_PREV:
3152                 find_next(view, request);
3153                 break;
3155         case REQ_STOP_LOADING:
3156                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3157                         view = &views[i];
3158                         if (view->pipe)
3159                                 report("Stopped loading the %s view", view->name),
3160                         end_update(view, TRUE);
3161                 }
3162                 break;
3164         case REQ_SHOW_VERSION:
3165                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3166                 return TRUE;
3168         case REQ_SCREEN_RESIZE:
3169                 resize_display();
3170                 /* Fall-through */
3171         case REQ_SCREEN_REDRAW:
3172                 redraw_display();
3173                 break;
3175         case REQ_EDIT:
3176                 report("Nothing to edit");
3177                 break;
3179         case REQ_ENTER:
3180                 report("Nothing to enter");
3181                 break;
3183         case REQ_VIEW_CLOSE:
3184                 /* XXX: Mark closed views by letting view->parent point to the
3185                  * view itself. Parents to closed view should never be
3186                  * followed. */
3187                 if (view->parent &&
3188                     view->parent->parent != view->parent) {
3189                         memset(display, 0, sizeof(display));
3190                         current_view = 0;
3191                         display[current_view] = view->parent;
3192                         view->parent = view;
3193                         resize_display();
3194                         redraw_display();
3195                         report("");
3196                         break;
3197                 }
3198                 /* Fall-through */
3199         case REQ_QUIT:
3200                 return FALSE;
3202         default:
3203                 report("Unknown key, press 'h' for help");
3204                 return TRUE;
3205         }
3207         return TRUE;
3211 /*
3212  * Pager backend
3213  */
3215 static bool
3216 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3218         char *text = line->data;
3220         if (opt_line_number && draw_lineno(view, lineno))
3221                 return TRUE;
3223         draw_text(view, line->type, text, TRUE);
3224         return TRUE;
3227 static bool
3228 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3230         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3231         char refbuf[SIZEOF_STR];
3232         char *ref = NULL;
3234         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3235                 ref = chomp_string(refbuf);
3237         if (!ref || !*ref)
3238                 return TRUE;
3240         /* This is the only fatal call, since it can "corrupt" the buffer. */
3241         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3242                 return FALSE;
3244         return TRUE;
3247 static void
3248 add_pager_refs(struct view *view, struct line *line)
3250         char buf[SIZEOF_STR];
3251         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3252         struct ref **refs;
3253         size_t bufpos = 0, refpos = 0;
3254         const char *sep = "Refs: ";
3255         bool is_tag = FALSE;
3257         assert(line->type == LINE_COMMIT);
3259         refs = get_refs(commit_id);
3260         if (!refs) {
3261                 if (view == VIEW(REQ_VIEW_DIFF))
3262                         goto try_add_describe_ref;
3263                 return;
3264         }
3266         do {
3267                 struct ref *ref = refs[refpos];
3268                 const char *fmt = ref->tag    ? "%s[%s]" :
3269                                   ref->remote ? "%s<%s>" : "%s%s";
3271                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3272                         return;
3273                 sep = ", ";
3274                 if (ref->tag)
3275                         is_tag = TRUE;
3276         } while (refs[refpos++]->next);
3278         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3279 try_add_describe_ref:
3280                 /* Add <tag>-g<commit_id> "fake" reference. */
3281                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3282                         return;
3283         }
3285         if (bufpos == 0)
3286                 return;
3288         add_line_text(view, buf, LINE_PP_REFS);
3291 static bool
3292 pager_read(struct view *view, char *data)
3294         struct line *line;
3296         if (!data)
3297                 return TRUE;
3299         line = add_line_text(view, data, get_line_type(data));
3300         if (!line)
3301                 return FALSE;
3303         if (line->type == LINE_COMMIT &&
3304             (view == VIEW(REQ_VIEW_DIFF) ||
3305              view == VIEW(REQ_VIEW_LOG)))
3306                 add_pager_refs(view, line);
3308         return TRUE;
3311 static enum request
3312 pager_request(struct view *view, enum request request, struct line *line)
3314         int split = 0;
3316         if (request != REQ_ENTER)
3317                 return request;
3319         if (line->type == LINE_COMMIT &&
3320            (view == VIEW(REQ_VIEW_LOG) ||
3321             view == VIEW(REQ_VIEW_PAGER))) {
3322                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3323                 split = 1;
3324         }
3326         /* Always scroll the view even if it was split. That way
3327          * you can use Enter to scroll through the log view and
3328          * split open each commit diff. */
3329         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3331         /* FIXME: A minor workaround. Scrolling the view will call report("")
3332          * but if we are scrolling a non-current view this won't properly
3333          * update the view title. */
3334         if (split)
3335                 update_view_title(view);
3337         return REQ_NONE;
3340 static bool
3341 pager_grep(struct view *view, struct line *line)
3343         regmatch_t pmatch;
3344         char *text = line->data;
3346         if (!*text)
3347                 return FALSE;
3349         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3350                 return FALSE;
3352         return TRUE;
3355 static void
3356 pager_select(struct view *view, struct line *line)
3358         if (line->type == LINE_COMMIT) {
3359                 char *text = (char *)line->data + STRING_SIZE("commit ");
3361                 if (view != VIEW(REQ_VIEW_PAGER))
3362                         string_copy_rev(view->ref, text);
3363                 string_copy_rev(ref_commit, text);
3364         }
3367 static struct view_ops pager_ops = {
3368         "line",
3369         NULL,
3370         NULL,
3371         pager_read,
3372         pager_draw,
3373         pager_request,
3374         pager_grep,
3375         pager_select,
3376 };
3378 static const char *log_argv[SIZEOF_ARG] = {
3379         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3380 };
3382 static enum request
3383 log_request(struct view *view, enum request request, struct line *line)
3385         switch (request) {
3386         case REQ_REFRESH:
3387                 load_refs();
3388                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3389                 return REQ_NONE;
3390         default:
3391                 return pager_request(view, request, line);
3392         }
3395 static struct view_ops log_ops = {
3396         "line",
3397         log_argv,
3398         NULL,
3399         pager_read,
3400         pager_draw,
3401         log_request,
3402         pager_grep,
3403         pager_select,
3404 };
3406 static const char *diff_argv[SIZEOF_ARG] = {
3407         "git", "show", "--pretty=fuller", "--no-color", "--root",
3408                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3409 };
3411 static struct view_ops diff_ops = {
3412         "line",
3413         diff_argv,
3414         NULL,
3415         pager_read,
3416         pager_draw,
3417         pager_request,
3418         pager_grep,
3419         pager_select,
3420 };
3422 /*
3423  * Help backend
3424  */
3426 static bool
3427 help_open(struct view *view)
3429         int lines = ARRAY_SIZE(req_info) + 2;
3430         int i;
3432         if (view->lines > 0)
3433                 return TRUE;
3435         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3436                 if (!req_info[i].request)
3437                         lines++;
3439         lines += run_requests + 1;
3441         view->line = calloc(lines, sizeof(*view->line));
3442         if (!view->line)
3443                 return FALSE;
3445         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3447         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3448                 const char *key;
3450                 if (req_info[i].request == REQ_NONE)
3451                         continue;
3453                 if (!req_info[i].request) {
3454                         add_line_text(view, "", LINE_DEFAULT);
3455                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3456                         continue;
3457                 }
3459                 key = get_key(req_info[i].request);
3460                 if (!*key)
3461                         key = "(no key defined)";
3463                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3464                                 key, req_info[i].help);
3465         }
3467         if (run_requests) {
3468                 add_line_text(view, "", LINE_DEFAULT);
3469                 add_line_text(view, "External commands:", LINE_DEFAULT);
3470         }
3472         for (i = 0; i < run_requests; i++) {
3473                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3474                 const char *key;
3475                 char cmd[SIZEOF_STR];
3476                 size_t bufpos;
3477                 int argc;
3479                 if (!req)
3480                         continue;
3482                 key = get_key_name(req->key);
3483                 if (!*key)
3484                         key = "(no key defined)";
3486                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3487                         if (!string_format_from(cmd, &bufpos, "%s%s",
3488                                                 argc ? " " : "", req->argv[argc]))
3489                                 return REQ_NONE;
3491                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3492                                 keymap_table[req->keymap].name, key, cmd);
3493         }
3495         return TRUE;
3498 static struct view_ops help_ops = {
3499         "line",
3500         NULL,
3501         help_open,
3502         NULL,
3503         pager_draw,
3504         pager_request,
3505         pager_grep,
3506         pager_select,
3507 };
3510 /*
3511  * Tree backend
3512  */
3514 struct tree_stack_entry {
3515         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3516         unsigned long lineno;           /* Line number to restore */
3517         char *name;                     /* Position of name in opt_path */
3518 };
3520 /* The top of the path stack. */
3521 static struct tree_stack_entry *tree_stack = NULL;
3522 unsigned long tree_lineno = 0;
3524 static void
3525 pop_tree_stack_entry(void)
3527         struct tree_stack_entry *entry = tree_stack;
3529         tree_lineno = entry->lineno;
3530         entry->name[0] = 0;
3531         tree_stack = entry->prev;
3532         free(entry);
3535 static void
3536 push_tree_stack_entry(const char *name, unsigned long lineno)
3538         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3539         size_t pathlen = strlen(opt_path);
3541         if (!entry)
3542                 return;
3544         entry->prev = tree_stack;
3545         entry->name = opt_path + pathlen;
3546         tree_stack = entry;
3548         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3549                 pop_tree_stack_entry();
3550                 return;
3551         }
3553         /* Move the current line to the first tree entry. */
3554         tree_lineno = 1;
3555         entry->lineno = lineno;
3558 /* Parse output from git-ls-tree(1):
3559  *
3560  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3561  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3562  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3563  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3564  */
3566 #define SIZEOF_TREE_ATTR \
3567         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3569 #define TREE_UP_FORMAT "040000 tree %s\t.."
3571 static const char *
3572 tree_path(struct line *line)
3574         const char *path = line->data;
3576         return path + SIZEOF_TREE_ATTR;
3579 static int
3580 tree_compare_entry(struct line *line1, struct line *line2)
3582         if (line1->type != line2->type)
3583                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3584         return strcmp(tree_path(line1), tree_path(line2));
3587 static bool
3588 tree_read(struct view *view, char *text)
3590         size_t textlen = text ? strlen(text) : 0;
3591         struct line *entry, *line;
3592         enum line_type type;
3594         if (!text)
3595                 return TRUE;
3596         if (textlen <= SIZEOF_TREE_ATTR)
3597                 return FALSE;
3599         type = text[STRING_SIZE("100644 ")] == 't'
3600              ? LINE_TREE_DIR : LINE_TREE_FILE;
3602         if (view->lines == 0 &&
3603             !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
3604                 return FALSE;
3606         /* Strip the path part ... */
3607         if (*opt_path) {
3608                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3609                 size_t striplen = strlen(opt_path);
3610                 char *path = text + SIZEOF_TREE_ATTR;
3612                 if (pathlen > striplen)
3613                         memmove(path, path + striplen,
3614                                 pathlen - striplen + 1);
3616                 /* Insert "link" to parent directory. */
3617                 if (view->lines == 1 &&
3618                     !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
3619                         return FALSE;
3620         }
3622         entry = add_line_text(view, text, type);
3623         if (!entry)
3624                 return FALSE;
3625         text = entry->data;
3627         /* Skip "Directory ..." and ".." line. */
3628         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3629                 if (tree_compare_entry(line, entry) <= 0)
3630                         continue;
3632                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3634                 line->data = text;
3635                 line->type = type;
3636                 for (; line <= entry; line++)
3637                         line->dirty = line->cleareol = 1;
3638                 return TRUE;
3639         }
3641         if (tree_lineno > view->lineno) {
3642                 view->lineno = tree_lineno;
3643                 tree_lineno = 0;
3644         }
3646         return TRUE;
3649 static enum request
3650 tree_request(struct view *view, enum request request, struct line *line)
3652         enum open_flags flags;
3654         switch (request) {
3655         case REQ_VIEW_BLAME:
3656                 if (line->type != LINE_TREE_FILE) {
3657                         report("Blame only supported for files");
3658                         return REQ_NONE;
3659                 }
3661                 string_copy(opt_ref, view->vid);
3662                 return request;
3664         case REQ_EDIT:
3665                 if (line->type != LINE_TREE_FILE) {
3666                         report("Edit only supported for files");
3667                 } else if (!is_head_commit(view->vid)) {
3668                         report("Edit only supported for files in the current work tree");
3669                 } else {
3670                         open_editor(TRUE, opt_file);
3671                 }
3672                 return REQ_NONE;
3674         case REQ_TREE_PARENT:
3675                 if (!*opt_path) {
3676                         /* quit view if at top of tree */
3677                         return REQ_VIEW_CLOSE;
3678                 }
3679                 /* fake 'cd  ..' */
3680                 line = &view->line[1];
3681                 break;
3683         case REQ_ENTER:
3684                 break;
3686         default:
3687                 return request;
3688         }
3690         /* Cleanup the stack if the tree view is at a different tree. */
3691         while (!*opt_path && tree_stack)
3692                 pop_tree_stack_entry();
3694         switch (line->type) {
3695         case LINE_TREE_DIR:
3696                 /* Depending on whether it is a subdir or parent (updir?) link
3697                  * mangle the path buffer. */
3698                 if (line == &view->line[1] && *opt_path) {
3699                         pop_tree_stack_entry();
3701                 } else {
3702                         const char *basename = tree_path(line);
3704                         push_tree_stack_entry(basename, view->lineno);
3705                 }
3707                 /* Trees and subtrees share the same ID, so they are not not
3708                  * unique like blobs. */
3709                 flags = OPEN_RELOAD;
3710                 request = REQ_VIEW_TREE;
3711                 break;
3713         case LINE_TREE_FILE:
3714                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3715                 request = REQ_VIEW_BLOB;
3716                 break;
3718         default:
3719                 return TRUE;
3720         }
3722         open_view(view, request, flags);
3723         if (request == REQ_VIEW_TREE) {
3724                 view->lineno = tree_lineno;
3725         }
3727         return REQ_NONE;
3730 static void
3731 tree_select(struct view *view, struct line *line)
3733         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3735         if (line->type == LINE_TREE_FILE) {
3736                 string_copy_rev(ref_blob, text);
3737                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3739         } else if (line->type != LINE_TREE_DIR) {
3740                 return;
3741         }
3743         string_copy_rev(view->ref, text);
3746 static const char *tree_argv[SIZEOF_ARG] = {
3747         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3748 };
3750 static struct view_ops tree_ops = {
3751         "file",
3752         tree_argv,
3753         NULL,
3754         tree_read,
3755         pager_draw,
3756         tree_request,
3757         pager_grep,
3758         tree_select,
3759 };
3761 static bool
3762 blob_read(struct view *view, char *line)
3764         if (!line)
3765                 return TRUE;
3766         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3769 static const char *blob_argv[SIZEOF_ARG] = {
3770         "git", "cat-file", "blob", "%(blob)", NULL
3771 };
3773 static struct view_ops blob_ops = {
3774         "line",
3775         blob_argv,
3776         NULL,
3777         blob_read,
3778         pager_draw,
3779         pager_request,
3780         pager_grep,
3781         pager_select,
3782 };
3784 /*
3785  * Blame backend
3786  *
3787  * Loading the blame view is a two phase job:
3788  *
3789  *  1. File content is read either using opt_file from the
3790  *     filesystem or using git-cat-file.
3791  *  2. Then blame information is incrementally added by
3792  *     reading output from git-blame.
3793  */
3795 static const char *blame_head_argv[] = {
3796         "git", "blame", "--incremental", "--", "%(file)", NULL
3797 };
3799 static const char *blame_ref_argv[] = {
3800         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3801 };
3803 static const char *blame_cat_file_argv[] = {
3804         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3805 };
3807 struct blame_commit {
3808         char id[SIZEOF_REV];            /* SHA1 ID. */
3809         char title[128];                /* First line of the commit message. */
3810         char author[75];                /* Author of the commit. */
3811         struct tm time;                 /* Date from the author ident. */
3812         char filename[128];             /* Name of file. */
3813 };
3815 struct blame {
3816         struct blame_commit *commit;
3817         char text[1];
3818 };
3820 static bool
3821 blame_open(struct view *view)
3823         if (*opt_ref || !io_open(&view->io, opt_file)) {
3824                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3825                         return FALSE;
3826         }
3828         setup_update(view, opt_file);
3829         string_format(view->ref, "%s ...", opt_file);
3831         return TRUE;
3834 static struct blame_commit *
3835 get_blame_commit(struct view *view, const char *id)
3837         size_t i;
3839         for (i = 0; i < view->lines; i++) {
3840                 struct blame *blame = view->line[i].data;
3842                 if (!blame->commit)
3843                         continue;
3845                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3846                         return blame->commit;
3847         }
3849         {
3850                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3852                 if (commit)
3853                         string_ncopy(commit->id, id, SIZEOF_REV);
3854                 return commit;
3855         }
3858 static bool
3859 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3861         const char *pos = *posref;
3863         *posref = NULL;
3864         pos = strchr(pos + 1, ' ');
3865         if (!pos || !isdigit(pos[1]))
3866                 return FALSE;
3867         *number = atoi(pos + 1);
3868         if (*number < min || *number > max)
3869                 return FALSE;
3871         *posref = pos;
3872         return TRUE;
3875 static struct blame_commit *
3876 parse_blame_commit(struct view *view, const char *text, int *blamed)
3878         struct blame_commit *commit;
3879         struct blame *blame;
3880         const char *pos = text + SIZEOF_REV - 1;
3881         size_t lineno;
3882         size_t group;
3884         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3885                 return NULL;
3887         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3888             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3889                 return NULL;
3891         commit = get_blame_commit(view, text);
3892         if (!commit)
3893                 return NULL;
3895         *blamed += group;
3896         while (group--) {
3897                 struct line *line = &view->line[lineno + group - 1];
3899                 blame = line->data;
3900                 blame->commit = commit;
3901                 line->dirty = 1;
3902         }
3904         return commit;
3907 static bool
3908 blame_read_file(struct view *view, const char *line, bool *read_file)
3910         if (!line) {
3911                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3912                 struct io io = {};
3914                 if (view->lines == 0 && !view->parent)
3915                         die("No blame exist for %s", view->vid);
3917                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3918                         report("Failed to load blame data");
3919                         return TRUE;
3920                 }
3922                 done_io(view->pipe);
3923                 view->io = io;
3924                 *read_file = FALSE;
3925                 return FALSE;
3927         } else {
3928                 size_t linelen = strlen(line);
3929                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3931                 blame->commit = NULL;
3932                 strncpy(blame->text, line, linelen);
3933                 blame->text[linelen] = 0;
3934                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3935         }
3938 static bool
3939 match_blame_header(const char *name, char **line)
3941         size_t namelen = strlen(name);
3942         bool matched = !strncmp(name, *line, namelen);
3944         if (matched)
3945                 *line += namelen;
3947         return matched;
3950 static bool
3951 blame_read(struct view *view, char *line)
3953         static struct blame_commit *commit = NULL;
3954         static int blamed = 0;
3955         static time_t author_time;
3956         static bool read_file = TRUE;
3958         if (read_file)
3959                 return blame_read_file(view, line, &read_file);
3961         if (!line) {
3962                 /* Reset all! */
3963                 commit = NULL;
3964                 blamed = 0;
3965                 read_file = TRUE;
3966                 string_format(view->ref, "%s", view->vid);
3967                 if (view_is_displayed(view)) {
3968                         update_view_title(view);
3969                         redraw_view_from(view, 0);
3970                 }
3971                 return TRUE;
3972         }
3974         if (!commit) {
3975                 commit = parse_blame_commit(view, line, &blamed);
3976                 string_format(view->ref, "%s %2d%%", view->vid,
3977                               blamed * 100 / view->lines);
3979         } else if (match_blame_header("author ", &line)) {
3980                 string_ncopy(commit->author, line, strlen(line));
3982         } else if (match_blame_header("author-time ", &line)) {
3983                 author_time = (time_t) atol(line);
3985         } else if (match_blame_header("author-tz ", &line)) {
3986                 long tz;
3988                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3989                 tz += ('0' - line[2]) * 60 * 60;
3990                 tz += ('0' - line[3]) * 60;
3991                 tz += ('0' - line[4]) * 60;
3993                 if (line[0] == '-')
3994                         tz = -tz;
3996                 author_time -= tz;
3997                 gmtime_r(&author_time, &commit->time);
3999         } else if (match_blame_header("summary ", &line)) {
4000                 string_ncopy(commit->title, line, strlen(line));
4002         } else if (match_blame_header("filename ", &line)) {
4003                 string_ncopy(commit->filename, line, strlen(line));
4004                 commit = NULL;
4005         }
4007         return TRUE;
4010 static bool
4011 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4013         struct blame *blame = line->data;
4014         struct tm *time = NULL;
4015         const char *id = NULL, *author = NULL;
4017         if (blame->commit && *blame->commit->filename) {
4018                 id = blame->commit->id;
4019                 author = blame->commit->author;
4020                 time = &blame->commit->time;
4021         }
4023         if (opt_date && draw_date(view, time))
4024                 return TRUE;
4026         if (opt_author &&
4027             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4028                 return TRUE;
4030         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4031                 return TRUE;
4033         if (draw_lineno(view, lineno))
4034                 return TRUE;
4036         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4037         return TRUE;
4040 static bool
4041 check_blame_commit(struct blame *blame)
4043         if (!blame->commit)
4044                 report("Commit data not loaded yet");
4045         else if (!strcmp(blame->commit->id, NULL_ID))
4046                 report("No commit exist for the selected line");
4047         else
4048                 return TRUE;
4049         return FALSE;
4052 static enum request
4053 blame_request(struct view *view, enum request request, struct line *line)
4055         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4056         struct blame *blame = line->data;
4058         switch (request) {
4059         case REQ_VIEW_BLAME:
4060                 if (check_blame_commit(blame)) {
4061                         string_copy(opt_ref, blame->commit->id);
4062                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4063                 }
4064                 break;
4066         case REQ_ENTER:
4067                 if (!blame->commit) {
4068                         report("No commit loaded yet");
4069                         break;
4070                 }
4072                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4073                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4074                         break;
4076                 if (!strcmp(blame->commit->id, NULL_ID)) {
4077                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4078                         const char *diff_index_argv[] = {
4079                                 "git", "diff-index", "--root", "--cached",
4080                                         "--patch-with-stat", "-C", "-M",
4081                                         "HEAD", "--", view->vid, NULL
4082                         };
4084                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4085                                 report("Failed to allocate diff command");
4086                                 break;
4087                         }
4088                         flags |= OPEN_PREPARED;
4089                 }
4091                 open_view(view, REQ_VIEW_DIFF, flags);
4092                 break;
4094         default:
4095                 return request;
4096         }
4098         return REQ_NONE;
4101 static bool
4102 blame_grep(struct view *view, struct line *line)
4104         struct blame *blame = line->data;
4105         struct blame_commit *commit = blame->commit;
4106         regmatch_t pmatch;
4108 #define MATCH(text, on)                                                 \
4109         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4111         if (commit) {
4112                 char buf[DATE_COLS + 1];
4114                 if (MATCH(commit->title, 1) ||
4115                     MATCH(commit->author, opt_author) ||
4116                     MATCH(commit->id, opt_date))
4117                         return TRUE;
4119                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4120                     MATCH(buf, 1))
4121                         return TRUE;
4122         }
4124         return MATCH(blame->text, 1);
4126 #undef MATCH
4129 static void
4130 blame_select(struct view *view, struct line *line)
4132         struct blame *blame = line->data;
4133         struct blame_commit *commit = blame->commit;
4135         if (!commit)
4136                 return;
4138         if (!strcmp(commit->id, NULL_ID))
4139                 string_ncopy(ref_commit, "HEAD", 4);
4140         else
4141                 string_copy_rev(ref_commit, commit->id);
4144 static struct view_ops blame_ops = {
4145         "line",
4146         NULL,
4147         blame_open,
4148         blame_read,
4149         blame_draw,
4150         blame_request,
4151         blame_grep,
4152         blame_select,
4153 };
4155 /*
4156  * Status backend
4157  */
4159 struct status {
4160         char status;
4161         struct {
4162                 mode_t mode;
4163                 char rev[SIZEOF_REV];
4164                 char name[SIZEOF_STR];
4165         } old;
4166         struct {
4167                 mode_t mode;
4168                 char rev[SIZEOF_REV];
4169                 char name[SIZEOF_STR];
4170         } new;
4171 };
4173 static char status_onbranch[SIZEOF_STR];
4174 static struct status stage_status;
4175 static enum line_type stage_line_type;
4176 static size_t stage_chunks;
4177 static int *stage_chunk;
4179 /* This should work even for the "On branch" line. */
4180 static inline bool
4181 status_has_none(struct view *view, struct line *line)
4183         return line < view->line + view->lines && !line[1].data;
4186 /* Get fields from the diff line:
4187  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4188  */
4189 static inline bool
4190 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4192         const char *old_mode = buf +  1;
4193         const char *new_mode = buf +  8;
4194         const char *old_rev  = buf + 15;
4195         const char *new_rev  = buf + 56;
4196         const char *status   = buf + 97;
4198         if (bufsize < 98 ||
4199             old_mode[-1] != ':' ||
4200             new_mode[-1] != ' ' ||
4201             old_rev[-1]  != ' ' ||
4202             new_rev[-1]  != ' ' ||
4203             status[-1]   != ' ')
4204                 return FALSE;
4206         file->status = *status;
4208         string_copy_rev(file->old.rev, old_rev);
4209         string_copy_rev(file->new.rev, new_rev);
4211         file->old.mode = strtoul(old_mode, NULL, 8);
4212         file->new.mode = strtoul(new_mode, NULL, 8);
4214         file->old.name[0] = file->new.name[0] = 0;
4216         return TRUE;
4219 static bool
4220 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4222         struct status *file = NULL;
4223         struct status *unmerged = NULL;
4224         char *buf;
4225         struct io io = {};
4227         if (!run_io(&io, argv, NULL, IO_RD))
4228                 return FALSE;
4230         add_line_data(view, NULL, type);
4232         while ((buf = io_get(&io, 0, TRUE))) {
4233                 if (!file) {
4234                         file = calloc(1, sizeof(*file));
4235                         if (!file || !add_line_data(view, file, type))
4236                                 goto error_out;
4237                 }
4239                 /* Parse diff info part. */
4240                 if (status) {
4241                         file->status = status;
4242                         if (status == 'A')
4243                                 string_copy(file->old.rev, NULL_ID);
4245                 } else if (!file->status) {
4246                         if (!status_get_diff(file, buf, strlen(buf)))
4247                                 goto error_out;
4249                         buf = io_get(&io, 0, TRUE);
4250                         if (!buf)
4251                                 break;
4253                         /* Collapse all 'M'odified entries that follow a
4254                          * associated 'U'nmerged entry. */
4255                         if (file->status == 'U') {
4256                                 unmerged = file;
4258                         } else if (unmerged) {
4259                                 int collapse = !strcmp(buf, unmerged->new.name);
4261                                 unmerged = NULL;
4262                                 if (collapse) {
4263                                         free(file);
4264                                         view->lines--;
4265                                         continue;
4266                                 }
4267                         }
4268                 }
4270                 /* Grab the old name for rename/copy. */
4271                 if (!*file->old.name &&
4272                     (file->status == 'R' || file->status == 'C')) {
4273                         string_ncopy(file->old.name, buf, strlen(buf));
4275                         buf = io_get(&io, 0, TRUE);
4276                         if (!buf)
4277                                 break;
4278                 }
4280                 /* git-ls-files just delivers a NUL separated list of
4281                  * file names similar to the second half of the
4282                  * git-diff-* output. */
4283                 string_ncopy(file->new.name, buf, strlen(buf));
4284                 if (!*file->old.name)
4285                         string_copy(file->old.name, file->new.name);
4286                 file = NULL;
4287         }
4289         if (io_error(&io)) {
4290 error_out:
4291                 done_io(&io);
4292                 return FALSE;
4293         }
4295         if (!view->line[view->lines - 1].data)
4296                 add_line_data(view, NULL, LINE_STAT_NONE);
4298         done_io(&io);
4299         return TRUE;
4302 /* Don't show unmerged entries in the staged section. */
4303 static const char *status_diff_index_argv[] = {
4304         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4305                              "--cached", "-M", "HEAD", NULL
4306 };
4308 static const char *status_diff_files_argv[] = {
4309         "git", "diff-files", "-z", NULL
4310 };
4312 static const char *status_list_other_argv[] = {
4313         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4314 };
4316 static const char *status_list_no_head_argv[] = {
4317         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4318 };
4320 static const char *update_index_argv[] = {
4321         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4322 };
4324 /* First parse staged info using git-diff-index(1), then parse unstaged
4325  * info using git-diff-files(1), and finally untracked files using
4326  * git-ls-files(1). */
4327 static bool
4328 status_open(struct view *view)
4330         unsigned long prev_lineno = view->lineno;
4332         reset_view(view);
4334         add_line_data(view, NULL, LINE_STAT_HEAD);
4335         if (is_initial_commit())
4336                 string_copy(status_onbranch, "Initial commit");
4337         else if (!*opt_head)
4338                 string_copy(status_onbranch, "Not currently on any branch");
4339         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4340                 return FALSE;
4342         run_io_bg(update_index_argv);
4344         if (is_initial_commit()) {
4345                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4346                         return FALSE;
4347         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4348                 return FALSE;
4349         }
4351         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4352             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4353                 return FALSE;
4355         /* If all went well restore the previous line number to stay in
4356          * the context or select a line with something that can be
4357          * updated. */
4358         if (prev_lineno >= view->lines)
4359                 prev_lineno = view->lines - 1;
4360         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4361                 prev_lineno++;
4362         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4363                 prev_lineno--;
4365         /* If the above fails, always skip the "On branch" line. */
4366         if (prev_lineno < view->lines)
4367                 view->lineno = prev_lineno;
4368         else
4369                 view->lineno = 1;
4371         if (view->lineno < view->offset)
4372                 view->offset = view->lineno;
4373         else if (view->offset + view->height <= view->lineno)
4374                 view->offset = view->lineno - view->height + 1;
4376         return TRUE;
4379 static bool
4380 status_draw(struct view *view, struct line *line, unsigned int lineno)
4382         struct status *status = line->data;
4383         enum line_type type;
4384         const char *text;
4386         if (!status) {
4387                 switch (line->type) {
4388                 case LINE_STAT_STAGED:
4389                         type = LINE_STAT_SECTION;
4390                         text = "Changes to be committed:";
4391                         break;
4393                 case LINE_STAT_UNSTAGED:
4394                         type = LINE_STAT_SECTION;
4395                         text = "Changed but not updated:";
4396                         break;
4398                 case LINE_STAT_UNTRACKED:
4399                         type = LINE_STAT_SECTION;
4400                         text = "Untracked files:";
4401                         break;
4403                 case LINE_STAT_NONE:
4404                         type = LINE_DEFAULT;
4405                         text = "    (no files)";
4406                         break;
4408                 case LINE_STAT_HEAD:
4409                         type = LINE_STAT_HEAD;
4410                         text = status_onbranch;
4411                         break;
4413                 default:
4414                         return FALSE;
4415                 }
4416         } else {
4417                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4419                 buf[0] = status->status;
4420                 if (draw_text(view, line->type, buf, TRUE))
4421                         return TRUE;
4422                 type = LINE_DEFAULT;
4423                 text = status->new.name;
4424         }
4426         draw_text(view, type, text, TRUE);
4427         return TRUE;
4430 static enum request
4431 status_enter(struct view *view, struct line *line)
4433         struct status *status = line->data;
4434         const char *oldpath = status ? status->old.name : NULL;
4435         /* Diffs for unmerged entries are empty when passing the new
4436          * path, so leave it empty. */
4437         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4438         const char *info;
4439         enum open_flags split;
4440         struct view *stage = VIEW(REQ_VIEW_STAGE);
4442         if (line->type == LINE_STAT_NONE ||
4443             (!status && line[1].type == LINE_STAT_NONE)) {
4444                 report("No file to diff");
4445                 return REQ_NONE;
4446         }
4448         switch (line->type) {
4449         case LINE_STAT_STAGED:
4450                 if (is_initial_commit()) {
4451                         const char *no_head_diff_argv[] = {
4452                                 "git", "diff", "--no-color", "--patch-with-stat",
4453                                         "--", "/dev/null", newpath, NULL
4454                         };
4456                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4457                                 return REQ_QUIT;
4458                 } else {
4459                         const char *index_show_argv[] = {
4460                                 "git", "diff-index", "--root", "--patch-with-stat",
4461                                         "-C", "-M", "--cached", "HEAD", "--",
4462                                         oldpath, newpath, NULL
4463                         };
4465                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4466                                 return REQ_QUIT;
4467                 }
4469                 if (status)
4470                         info = "Staged changes to %s";
4471                 else
4472                         info = "Staged changes";
4473                 break;
4475         case LINE_STAT_UNSTAGED:
4476         {
4477                 const char *files_show_argv[] = {
4478                         "git", "diff-files", "--root", "--patch-with-stat",
4479                                 "-C", "-M", "--", oldpath, newpath, NULL
4480                 };
4482                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4483                         return REQ_QUIT;
4484                 if (status)
4485                         info = "Unstaged changes to %s";
4486                 else
4487                         info = "Unstaged changes";
4488                 break;
4489         }
4490         case LINE_STAT_UNTRACKED:
4491                 if (!newpath) {
4492                         report("No file to show");
4493                         return REQ_NONE;
4494                 }
4496                 if (!suffixcmp(status->new.name, -1, "/")) {
4497                         report("Cannot display a directory");
4498                         return REQ_NONE;
4499                 }
4501                 if (!prepare_update_file(stage, newpath))
4502                         return REQ_QUIT;
4503                 info = "Untracked file %s";
4504                 break;
4506         case LINE_STAT_HEAD:
4507                 return REQ_NONE;
4509         default:
4510                 die("line type %d not handled in switch", line->type);
4511         }
4513         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4514         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4515         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4516                 if (status) {
4517                         stage_status = *status;
4518                 } else {
4519                         memset(&stage_status, 0, sizeof(stage_status));
4520                 }
4522                 stage_line_type = line->type;
4523                 stage_chunks = 0;
4524                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4525         }
4527         return REQ_NONE;
4530 static bool
4531 status_exists(struct status *status, enum line_type type)
4533         struct view *view = VIEW(REQ_VIEW_STATUS);
4534         struct line *line;
4536         for (line = view->line; line < view->line + view->lines; line++) {
4537                 struct status *pos = line->data;
4539                 if (line->type != type)
4540                         continue;
4541                 if (!pos && (!status || !status->status))
4542                         return TRUE;
4543                 if (pos && !strcmp(status->new.name, pos->new.name))
4544                         return TRUE;
4545         }
4547         return FALSE;
4551 static bool
4552 status_update_prepare(struct io *io, enum line_type type)
4554         const char *staged_argv[] = {
4555                 "git", "update-index", "-z", "--index-info", NULL
4556         };
4557         const char *others_argv[] = {
4558                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4559         };
4561         switch (type) {
4562         case LINE_STAT_STAGED:
4563                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4565         case LINE_STAT_UNSTAGED:
4566                 return run_io(io, others_argv, opt_cdup, IO_WR);
4568         case LINE_STAT_UNTRACKED:
4569                 return run_io(io, others_argv, NULL, IO_WR);
4571         default:
4572                 die("line type %d not handled in switch", type);
4573                 return FALSE;
4574         }
4577 static bool
4578 status_update_write(struct io *io, struct status *status, enum line_type type)
4580         char buf[SIZEOF_STR];
4581         size_t bufsize = 0;
4583         switch (type) {
4584         case LINE_STAT_STAGED:
4585                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4586                                         status->old.mode,
4587                                         status->old.rev,
4588                                         status->old.name, 0))
4589                         return FALSE;
4590                 break;
4592         case LINE_STAT_UNSTAGED:
4593         case LINE_STAT_UNTRACKED:
4594                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4595                         return FALSE;
4596                 break;
4598         default:
4599                 die("line type %d not handled in switch", type);
4600         }
4602         return io_write(io, buf, bufsize);
4605 static bool
4606 status_update_file(struct status *status, enum line_type type)
4608         struct io io = {};
4609         bool result;
4611         if (!status_update_prepare(&io, type))
4612                 return FALSE;
4614         result = status_update_write(&io, status, type);
4615         done_io(&io);
4616         return result;
4619 static bool
4620 status_update_files(struct view *view, struct line *line)
4622         struct io io = {};
4623         bool result = TRUE;
4624         struct line *pos = view->line + view->lines;
4625         int files = 0;
4626         int file, done;
4628         if (!status_update_prepare(&io, line->type))
4629                 return FALSE;
4631         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4632                 files++;
4634         for (file = 0, done = 0; result && file < files; line++, file++) {
4635                 int almost_done = file * 100 / files;
4637                 if (almost_done > done) {
4638                         done = almost_done;
4639                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4640                                       file, files, done);
4641                         update_view_title(view);
4642                 }
4643                 result = status_update_write(&io, line->data, line->type);
4644         }
4646         done_io(&io);
4647         return result;
4650 static bool
4651 status_update(struct view *view)
4653         struct line *line = &view->line[view->lineno];
4655         assert(view->lines);
4657         if (!line->data) {
4658                 /* This should work even for the "On branch" line. */
4659                 if (line < view->line + view->lines && !line[1].data) {
4660                         report("Nothing to update");
4661                         return FALSE;
4662                 }
4664                 if (!status_update_files(view, line + 1)) {
4665                         report("Failed to update file status");
4666                         return FALSE;
4667                 }
4669         } else if (!status_update_file(line->data, line->type)) {
4670                 report("Failed to update file status");
4671                 return FALSE;
4672         }
4674         return TRUE;
4677 static bool
4678 status_revert(struct status *status, enum line_type type, bool has_none)
4680         if (!status || type != LINE_STAT_UNSTAGED) {
4681                 if (type == LINE_STAT_STAGED) {
4682                         report("Cannot revert changes to staged files");
4683                 } else if (type == LINE_STAT_UNTRACKED) {
4684                         report("Cannot revert changes to untracked files");
4685                 } else if (has_none) {
4686                         report("Nothing to revert");
4687                 } else {
4688                         report("Cannot revert changes to multiple files");
4689                 }
4690                 return FALSE;
4692         } else {
4693                 const char *checkout_argv[] = {
4694                         "git", "checkout", "--", status->old.name, NULL
4695                 };
4697                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4698                         return FALSE;
4699                 return run_io_fg(checkout_argv, opt_cdup);
4700         }
4703 static enum request
4704 status_request(struct view *view, enum request request, struct line *line)
4706         struct status *status = line->data;
4708         switch (request) {
4709         case REQ_STATUS_UPDATE:
4710                 if (!status_update(view))
4711                         return REQ_NONE;
4712                 break;
4714         case REQ_STATUS_REVERT:
4715                 if (!status_revert(status, line->type, status_has_none(view, line)))
4716                         return REQ_NONE;
4717                 break;
4719         case REQ_STATUS_MERGE:
4720                 if (!status || status->status != 'U') {
4721                         report("Merging only possible for files with unmerged status ('U').");
4722                         return REQ_NONE;
4723                 }
4724                 open_mergetool(status->new.name);
4725                 break;
4727         case REQ_EDIT:
4728                 if (!status)
4729                         return request;
4730                 if (status->status == 'D') {
4731                         report("File has been deleted.");
4732                         return REQ_NONE;
4733                 }
4735                 open_editor(status->status != '?', status->new.name);
4736                 break;
4738         case REQ_VIEW_BLAME:
4739                 if (status) {
4740                         string_copy(opt_file, status->new.name);
4741                         opt_ref[0] = 0;
4742                 }
4743                 return request;
4745         case REQ_ENTER:
4746                 /* After returning the status view has been split to
4747                  * show the stage view. No further reloading is
4748                  * necessary. */
4749                 status_enter(view, line);
4750                 return REQ_NONE;
4752         case REQ_REFRESH:
4753                 /* Simply reload the view. */
4754                 break;
4756         default:
4757                 return request;
4758         }
4760         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4762         return REQ_NONE;
4765 static void
4766 status_select(struct view *view, struct line *line)
4768         struct status *status = line->data;
4769         char file[SIZEOF_STR] = "all files";
4770         const char *text;
4771         const char *key;
4773         if (status && !string_format(file, "'%s'", status->new.name))
4774                 return;
4776         if (!status && line[1].type == LINE_STAT_NONE)
4777                 line++;
4779         switch (line->type) {
4780         case LINE_STAT_STAGED:
4781                 text = "Press %s to unstage %s for commit";
4782                 break;
4784         case LINE_STAT_UNSTAGED:
4785                 text = "Press %s to stage %s for commit";
4786                 break;
4788         case LINE_STAT_UNTRACKED:
4789                 text = "Press %s to stage %s for addition";
4790                 break;
4792         case LINE_STAT_HEAD:
4793         case LINE_STAT_NONE:
4794                 text = "Nothing to update";
4795                 break;
4797         default:
4798                 die("line type %d not handled in switch", line->type);
4799         }
4801         if (status && status->status == 'U') {
4802                 text = "Press %s to resolve conflict in %s";
4803                 key = get_key(REQ_STATUS_MERGE);
4805         } else {
4806                 key = get_key(REQ_STATUS_UPDATE);
4807         }
4809         string_format(view->ref, text, key, file);
4812 static bool
4813 status_grep(struct view *view, struct line *line)
4815         struct status *status = line->data;
4816         enum { S_STATUS, S_NAME, S_END } state;
4817         char buf[2] = "?";
4818         regmatch_t pmatch;
4820         if (!status)
4821                 return FALSE;
4823         for (state = S_STATUS; state < S_END; state++) {
4824                 const char *text;
4826                 switch (state) {
4827                 case S_NAME:    text = status->new.name;        break;
4828                 case S_STATUS:
4829                         buf[0] = status->status;
4830                         text = buf;
4831                         break;
4833                 default:
4834                         return FALSE;
4835                 }
4837                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4838                         return TRUE;
4839         }
4841         return FALSE;
4844 static struct view_ops status_ops = {
4845         "file",
4846         NULL,
4847         status_open,
4848         NULL,
4849         status_draw,
4850         status_request,
4851         status_grep,
4852         status_select,
4853 };
4856 static bool
4857 stage_diff_write(struct io *io, struct line *line, struct line *end)
4859         while (line < end) {
4860                 if (!io_write(io, line->data, strlen(line->data)) ||
4861                     !io_write(io, "\n", 1))
4862                         return FALSE;
4863                 line++;
4864                 if (line->type == LINE_DIFF_CHUNK ||
4865                     line->type == LINE_DIFF_HEADER)
4866                         break;
4867         }
4869         return TRUE;
4872 static struct line *
4873 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4875         for (; view->line < line; line--)
4876                 if (line->type == type)
4877                         return line;
4879         return NULL;
4882 static bool
4883 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4885         const char *apply_argv[SIZEOF_ARG] = {
4886                 "git", "apply", "--whitespace=nowarn", NULL
4887         };
4888         struct line *diff_hdr;
4889         struct io io = {};
4890         int argc = 3;
4892         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4893         if (!diff_hdr)
4894                 return FALSE;
4896         if (!revert)
4897                 apply_argv[argc++] = "--cached";
4898         if (revert || stage_line_type == LINE_STAT_STAGED)
4899                 apply_argv[argc++] = "-R";
4900         apply_argv[argc++] = "-";
4901         apply_argv[argc++] = NULL;
4902         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4903                 return FALSE;
4905         if (!stage_diff_write(&io, diff_hdr, chunk) ||
4906             !stage_diff_write(&io, chunk, view->line + view->lines))
4907                 chunk = NULL;
4909         done_io(&io);
4910         run_io_bg(update_index_argv);
4912         return chunk ? TRUE : FALSE;
4915 static bool
4916 stage_update(struct view *view, struct line *line)
4918         struct line *chunk = NULL;
4920         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4921                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4923         if (chunk) {
4924                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4925                         report("Failed to apply chunk");
4926                         return FALSE;
4927                 }
4929         } else if (!stage_status.status) {
4930                 view = VIEW(REQ_VIEW_STATUS);
4932                 for (line = view->line; line < view->line + view->lines; line++)
4933                         if (line->type == stage_line_type)
4934                                 break;
4936                 if (!status_update_files(view, line + 1)) {
4937                         report("Failed to update files");
4938                         return FALSE;
4939                 }
4941         } else if (!status_update_file(&stage_status, stage_line_type)) {
4942                 report("Failed to update file");
4943                 return FALSE;
4944         }
4946         return TRUE;
4949 static bool
4950 stage_revert(struct view *view, struct line *line)
4952         struct line *chunk = NULL;
4954         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4955                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4957         if (chunk) {
4958                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4959                         return FALSE;
4961                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4962                         report("Failed to revert chunk");
4963                         return FALSE;
4964                 }
4965                 return TRUE;
4967         } else {
4968                 return status_revert(stage_status.status ? &stage_status : NULL,
4969                                      stage_line_type, FALSE);
4970         }
4974 static void
4975 stage_next(struct view *view, struct line *line)
4977         int i;
4979         if (!stage_chunks) {
4980                 static size_t alloc = 0;
4981                 int *tmp;
4983                 for (line = view->line; line < view->line + view->lines; line++) {
4984                         if (line->type != LINE_DIFF_CHUNK)
4985                                 continue;
4987                         tmp = realloc_items(stage_chunk, &alloc,
4988                                             stage_chunks, sizeof(*tmp));
4989                         if (!tmp) {
4990                                 report("Allocation failure");
4991                                 return;
4992                         }
4994                         stage_chunk = tmp;
4995                         stage_chunk[stage_chunks++] = line - view->line;
4996                 }
4997         }
4999         for (i = 0; i < stage_chunks; i++) {
5000                 if (stage_chunk[i] > view->lineno) {
5001                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5002                         report("Chunk %d of %d", i + 1, stage_chunks);
5003                         return;
5004                 }
5005         }
5007         report("No next chunk found");
5010 static enum request
5011 stage_request(struct view *view, enum request request, struct line *line)
5013         switch (request) {
5014         case REQ_STATUS_UPDATE:
5015                 if (!stage_update(view, line))
5016                         return REQ_NONE;
5017                 break;
5019         case REQ_STATUS_REVERT:
5020                 if (!stage_revert(view, line))
5021                         return REQ_NONE;
5022                 break;
5024         case REQ_STAGE_NEXT:
5025                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5026                         report("File is untracked; press %s to add",
5027                                get_key(REQ_STATUS_UPDATE));
5028                         return REQ_NONE;
5029                 }
5030                 stage_next(view, line);
5031                 return REQ_NONE;
5033         case REQ_EDIT:
5034                 if (!stage_status.new.name[0])
5035                         return request;
5036                 if (stage_status.status == 'D') {
5037                         report("File has been deleted.");
5038                         return REQ_NONE;
5039                 }
5041                 open_editor(stage_status.status != '?', stage_status.new.name);
5042                 break;
5044         case REQ_REFRESH:
5045                 /* Reload everything ... */
5046                 break;
5048         case REQ_VIEW_BLAME:
5049                 if (stage_status.new.name[0]) {
5050                         string_copy(opt_file, stage_status.new.name);
5051                         opt_ref[0] = 0;
5052                 }
5053                 return request;
5055         case REQ_ENTER:
5056                 return pager_request(view, request, line);
5058         default:
5059                 return request;
5060         }
5062         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5064         /* Check whether the staged entry still exists, and close the
5065          * stage view if it doesn't. */
5066         if (!status_exists(&stage_status, stage_line_type))
5067                 return REQ_VIEW_CLOSE;
5069         if (stage_line_type == LINE_STAT_UNTRACKED) {
5070                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5071                         report("Cannot display a directory");
5072                         return REQ_NONE;
5073                 }
5075                 if (!prepare_update_file(view, stage_status.new.name)) {
5076                         report("Failed to open file: %s", strerror(errno));
5077                         return REQ_NONE;
5078                 }
5079         }
5080         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5082         return REQ_NONE;
5085 static struct view_ops stage_ops = {
5086         "line",
5087         NULL,
5088         NULL,
5089         pager_read,
5090         pager_draw,
5091         stage_request,
5092         pager_grep,
5093         pager_select,
5094 };
5097 /*
5098  * Revision graph
5099  */
5101 struct commit {
5102         char id[SIZEOF_REV];            /* SHA1 ID. */
5103         char title[128];                /* First line of the commit message. */
5104         char author[75];                /* Author of the commit. */
5105         struct tm time;                 /* Date from the author ident. */
5106         struct ref **refs;              /* Repository references. */
5107         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5108         size_t graph_size;              /* The width of the graph array. */
5109         bool has_parents;               /* Rewritten --parents seen. */
5110 };
5112 /* Size of rev graph with no  "padding" columns */
5113 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5115 struct rev_graph {
5116         struct rev_graph *prev, *next, *parents;
5117         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5118         size_t size;
5119         struct commit *commit;
5120         size_t pos;
5121         unsigned int boundary:1;
5122 };
5124 /* Parents of the commit being visualized. */
5125 static struct rev_graph graph_parents[4];
5127 /* The current stack of revisions on the graph. */
5128 static struct rev_graph graph_stacks[4] = {
5129         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5130         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5131         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5132         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5133 };
5135 static inline bool
5136 graph_parent_is_merge(struct rev_graph *graph)
5138         return graph->parents->size > 1;
5141 static inline void
5142 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5144         struct commit *commit = graph->commit;
5146         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5147                 commit->graph[commit->graph_size++] = symbol;
5150 static void
5151 clear_rev_graph(struct rev_graph *graph)
5153         graph->boundary = 0;
5154         graph->size = graph->pos = 0;
5155         graph->commit = NULL;
5156         memset(graph->parents, 0, sizeof(*graph->parents));
5159 static void
5160 done_rev_graph(struct rev_graph *graph)
5162         if (graph_parent_is_merge(graph) &&
5163             graph->pos < graph->size - 1 &&
5164             graph->next->size == graph->size + graph->parents->size - 1) {
5165                 size_t i = graph->pos + graph->parents->size - 1;
5167                 graph->commit->graph_size = i * 2;
5168                 while (i < graph->next->size - 1) {
5169                         append_to_rev_graph(graph, ' ');
5170                         append_to_rev_graph(graph, '\\');
5171                         i++;
5172                 }
5173         }
5175         clear_rev_graph(graph);
5178 static void
5179 push_rev_graph(struct rev_graph *graph, const char *parent)
5181         int i;
5183         /* "Collapse" duplicate parents lines.
5184          *
5185          * FIXME: This needs to also update update the drawn graph but
5186          * for now it just serves as a method for pruning graph lines. */
5187         for (i = 0; i < graph->size; i++)
5188                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5189                         return;
5191         if (graph->size < SIZEOF_REVITEMS) {
5192                 string_copy_rev(graph->rev[graph->size++], parent);
5193         }
5196 static chtype
5197 get_rev_graph_symbol(struct rev_graph *graph)
5199         chtype symbol;
5201         if (graph->boundary)
5202                 symbol = REVGRAPH_BOUND;
5203         else if (graph->parents->size == 0)
5204                 symbol = REVGRAPH_INIT;
5205         else if (graph_parent_is_merge(graph))
5206                 symbol = REVGRAPH_MERGE;
5207         else if (graph->pos >= graph->size)
5208                 symbol = REVGRAPH_BRANCH;
5209         else
5210                 symbol = REVGRAPH_COMMIT;
5212         return symbol;
5215 static void
5216 draw_rev_graph(struct rev_graph *graph)
5218         struct rev_filler {
5219                 chtype separator, line;
5220         };
5221         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5222         static struct rev_filler fillers[] = {
5223                 { ' ',  '|' },
5224                 { '`',  '.' },
5225                 { '\'', ' ' },
5226                 { '/',  ' ' },
5227         };
5228         chtype symbol = get_rev_graph_symbol(graph);
5229         struct rev_filler *filler;
5230         size_t i;
5232         if (opt_line_graphics)
5233                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5235         filler = &fillers[DEFAULT];
5237         for (i = 0; i < graph->pos; i++) {
5238                 append_to_rev_graph(graph, filler->line);
5239                 if (graph_parent_is_merge(graph->prev) &&
5240                     graph->prev->pos == i)
5241                         filler = &fillers[RSHARP];
5243                 append_to_rev_graph(graph, filler->separator);
5244         }
5246         /* Place the symbol for this revision. */
5247         append_to_rev_graph(graph, symbol);
5249         if (graph->prev->size > graph->size)
5250                 filler = &fillers[RDIAG];
5251         else
5252                 filler = &fillers[DEFAULT];
5254         i++;
5256         for (; i < graph->size; i++) {
5257                 append_to_rev_graph(graph, filler->separator);
5258                 append_to_rev_graph(graph, filler->line);
5259                 if (graph_parent_is_merge(graph->prev) &&
5260                     i < graph->prev->pos + graph->parents->size)
5261                         filler = &fillers[RSHARP];
5262                 if (graph->prev->size > graph->size)
5263                         filler = &fillers[LDIAG];
5264         }
5266         if (graph->prev->size > graph->size) {
5267                 append_to_rev_graph(graph, filler->separator);
5268                 if (filler->line != ' ')
5269                         append_to_rev_graph(graph, filler->line);
5270         }
5273 /* Prepare the next rev graph */
5274 static void
5275 prepare_rev_graph(struct rev_graph *graph)
5277         size_t i;
5279         /* First, traverse all lines of revisions up to the active one. */
5280         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5281                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5282                         break;
5284                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5285         }
5287         /* Interleave the new revision parent(s). */
5288         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5289                 push_rev_graph(graph->next, graph->parents->rev[i]);
5291         /* Lastly, put any remaining revisions. */
5292         for (i = graph->pos + 1; i < graph->size; i++)
5293                 push_rev_graph(graph->next, graph->rev[i]);
5296 static void
5297 update_rev_graph(struct view *view, struct rev_graph *graph)
5299         /* If this is the finalizing update ... */
5300         if (graph->commit)
5301                 prepare_rev_graph(graph);
5303         /* Graph visualization needs a one rev look-ahead,
5304          * so the first update doesn't visualize anything. */
5305         if (!graph->prev->commit)
5306                 return;
5308         if (view->lines > 2)
5309                 view->line[view->lines - 3].dirty = 1;
5310         if (view->lines > 1)
5311                 view->line[view->lines - 2].dirty = 1;
5312         draw_rev_graph(graph->prev);
5313         done_rev_graph(graph->prev->prev);
5317 /*
5318  * Main view backend
5319  */
5321 static const char *main_argv[SIZEOF_ARG] = {
5322         "git", "log", "--no-color", "--pretty=raw", "--parents",
5323                       "--topo-order", "%(head)", NULL
5324 };
5326 static bool
5327 main_draw(struct view *view, struct line *line, unsigned int lineno)
5329         struct commit *commit = line->data;
5331         if (!*commit->author)
5332                 return FALSE;
5334         if (opt_date && draw_date(view, &commit->time))
5335                 return TRUE;
5337         if (opt_author &&
5338             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5339                 return TRUE;
5341         if (opt_rev_graph && commit->graph_size &&
5342             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5343                 return TRUE;
5345         if (opt_show_refs && commit->refs) {
5346                 size_t i = 0;
5348                 do {
5349                         enum line_type type;
5351                         if (commit->refs[i]->head)
5352                                 type = LINE_MAIN_HEAD;
5353                         else if (commit->refs[i]->ltag)
5354                                 type = LINE_MAIN_LOCAL_TAG;
5355                         else if (commit->refs[i]->tag)
5356                                 type = LINE_MAIN_TAG;
5357                         else if (commit->refs[i]->tracked)
5358                                 type = LINE_MAIN_TRACKED;
5359                         else if (commit->refs[i]->remote)
5360                                 type = LINE_MAIN_REMOTE;
5361                         else
5362                                 type = LINE_MAIN_REF;
5364                         if (draw_text(view, type, "[", TRUE) ||
5365                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5366                             draw_text(view, type, "]", TRUE))
5367                                 return TRUE;
5369                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5370                                 return TRUE;
5371                 } while (commit->refs[i++]->next);
5372         }
5374         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5375         return TRUE;
5378 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5379 static bool
5380 main_read(struct view *view, char *line)
5382         static struct rev_graph *graph = graph_stacks;
5383         enum line_type type;
5384         struct commit *commit;
5386         if (!line) {
5387                 int i;
5389                 if (!view->lines && !view->parent)
5390                         die("No revisions match the given arguments.");
5391                 if (view->lines > 0) {
5392                         commit = view->line[view->lines - 1].data;
5393                         view->line[view->lines - 1].dirty = 1;
5394                         if (!*commit->author) {
5395                                 view->lines--;
5396                                 free(commit);
5397                                 graph->commit = NULL;
5398                         }
5399                 }
5400                 update_rev_graph(view, graph);
5402                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5403                         clear_rev_graph(&graph_stacks[i]);
5404                 return TRUE;
5405         }
5407         type = get_line_type(line);
5408         if (type == LINE_COMMIT) {
5409                 commit = calloc(1, sizeof(struct commit));
5410                 if (!commit)
5411                         return FALSE;
5413                 line += STRING_SIZE("commit ");
5414                 if (*line == '-') {
5415                         graph->boundary = 1;
5416                         line++;
5417                 }
5419                 string_copy_rev(commit->id, line);
5420                 commit->refs = get_refs(commit->id);
5421                 graph->commit = commit;
5422                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5424                 while ((line = strchr(line, ' '))) {
5425                         line++;
5426                         push_rev_graph(graph->parents, line);
5427                         commit->has_parents = TRUE;
5428                 }
5429                 return TRUE;
5430         }
5432         if (!view->lines)
5433                 return TRUE;
5434         commit = view->line[view->lines - 1].data;
5436         switch (type) {
5437         case LINE_PARENT:
5438                 if (commit->has_parents)
5439                         break;
5440                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5441                 break;
5443         case LINE_AUTHOR:
5444         {
5445                 /* Parse author lines where the name may be empty:
5446                  *      author  <email@address.tld> 1138474660 +0100
5447                  */
5448                 char *ident = line + STRING_SIZE("author ");
5449                 char *nameend = strchr(ident, '<');
5450                 char *emailend = strchr(ident, '>');
5452                 if (!nameend || !emailend)
5453                         break;
5455                 update_rev_graph(view, graph);
5456                 graph = graph->next;
5458                 *nameend = *emailend = 0;
5459                 ident = chomp_string(ident);
5460                 if (!*ident) {
5461                         ident = chomp_string(nameend + 1);
5462                         if (!*ident)
5463                                 ident = "Unknown";
5464                 }
5466                 string_ncopy(commit->author, ident, strlen(ident));
5467                 view->line[view->lines - 1].dirty = 1;
5469                 /* Parse epoch and timezone */
5470                 if (emailend[1] == ' ') {
5471                         char *secs = emailend + 2;
5472                         char *zone = strchr(secs, ' ');
5473                         time_t time = (time_t) atol(secs);
5475                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5476                                 long tz;
5478                                 zone++;
5479                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5480                                 tz += ('0' - zone[2]) * 60 * 60;
5481                                 tz += ('0' - zone[3]) * 60;
5482                                 tz += ('0' - zone[4]) * 60;
5484                                 if (zone[0] == '-')
5485                                         tz = -tz;
5487                                 time -= tz;
5488                         }
5490                         gmtime_r(&time, &commit->time);
5491                 }
5492                 break;
5493         }
5494         default:
5495                 /* Fill in the commit title if it has not already been set. */
5496                 if (commit->title[0])
5497                         break;
5499                 /* Require titles to start with a non-space character at the
5500                  * offset used by git log. */
5501                 if (strncmp(line, "    ", 4))
5502                         break;
5503                 line += 4;
5504                 /* Well, if the title starts with a whitespace character,
5505                  * try to be forgiving.  Otherwise we end up with no title. */
5506                 while (isspace(*line))
5507                         line++;
5508                 if (*line == '\0')
5509                         break;
5510                 /* FIXME: More graceful handling of titles; append "..." to
5511                  * shortened titles, etc. */
5513                 string_ncopy(commit->title, line, strlen(line));
5514                 view->line[view->lines - 1].dirty = 1;
5515         }
5517         return TRUE;
5520 static enum request
5521 main_request(struct view *view, enum request request, struct line *line)
5523         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5525         switch (request) {
5526         case REQ_ENTER:
5527                 open_view(view, REQ_VIEW_DIFF, flags);
5528                 break;
5529         case REQ_REFRESH:
5530                 load_refs();
5531                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5532                 break;
5533         default:
5534                 return request;
5535         }
5537         return REQ_NONE;
5540 static bool
5541 grep_refs(struct ref **refs, regex_t *regex)
5543         regmatch_t pmatch;
5544         size_t i = 0;
5546         if (!refs)
5547                 return FALSE;
5548         do {
5549                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5550                         return TRUE;
5551         } while (refs[i++]->next);
5553         return FALSE;
5556 static bool
5557 main_grep(struct view *view, struct line *line)
5559         struct commit *commit = line->data;
5560         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5561         char buf[DATE_COLS + 1];
5562         regmatch_t pmatch;
5564         for (state = S_TITLE; state < S_END; state++) {
5565                 char *text;
5567                 switch (state) {
5568                 case S_TITLE:   text = commit->title;   break;
5569                 case S_AUTHOR:
5570                         if (!opt_author)
5571                                 continue;
5572                         text = commit->author;
5573                         break;
5574                 case S_DATE:
5575                         if (!opt_date)
5576                                 continue;
5577                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5578                                 continue;
5579                         text = buf;
5580                         break;
5581                 case S_REFS:
5582                         if (!opt_show_refs)
5583                                 continue;
5584                         if (grep_refs(commit->refs, view->regex) == TRUE)
5585                                 return TRUE;
5586                         continue;
5587                 default:
5588                         return FALSE;
5589                 }
5591                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5592                         return TRUE;
5593         }
5595         return FALSE;
5598 static void
5599 main_select(struct view *view, struct line *line)
5601         struct commit *commit = line->data;
5603         string_copy_rev(view->ref, commit->id);
5604         string_copy_rev(ref_commit, view->ref);
5607 static struct view_ops main_ops = {
5608         "commit",
5609         main_argv,
5610         NULL,
5611         main_read,
5612         main_draw,
5613         main_request,
5614         main_grep,
5615         main_select,
5616 };
5619 /*
5620  * Unicode / UTF-8 handling
5621  *
5622  * NOTE: Much of the following code for dealing with unicode is derived from
5623  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5624  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5625  */
5627 /* I've (over)annotated a lot of code snippets because I am not entirely
5628  * confident that the approach taken by this small UTF-8 interface is correct.
5629  * --jonas */
5631 static inline int
5632 unicode_width(unsigned long c)
5634         if (c >= 0x1100 &&
5635            (c <= 0x115f                         /* Hangul Jamo */
5636             || c == 0x2329
5637             || c == 0x232a
5638             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5639                                                 /* CJK ... Yi */
5640             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5641             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5642             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5643             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5644             || (c >= 0xffe0  && c <= 0xffe6)
5645             || (c >= 0x20000 && c <= 0x2fffd)
5646             || (c >= 0x30000 && c <= 0x3fffd)))
5647                 return 2;
5649         if (c == '\t')
5650                 return opt_tab_size;
5652         return 1;
5655 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5656  * Illegal bytes are set one. */
5657 static const unsigned char utf8_bytes[256] = {
5658         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,
5659         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,
5660         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,
5661         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,
5662         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,
5663         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,
5664         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,
5665         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,
5666 };
5668 /* Decode UTF-8 multi-byte representation into a unicode character. */
5669 static inline unsigned long
5670 utf8_to_unicode(const char *string, size_t length)
5672         unsigned long unicode;
5674         switch (length) {
5675         case 1:
5676                 unicode  =   string[0];
5677                 break;
5678         case 2:
5679                 unicode  =  (string[0] & 0x1f) << 6;
5680                 unicode +=  (string[1] & 0x3f);
5681                 break;
5682         case 3:
5683                 unicode  =  (string[0] & 0x0f) << 12;
5684                 unicode += ((string[1] & 0x3f) << 6);
5685                 unicode +=  (string[2] & 0x3f);
5686                 break;
5687         case 4:
5688                 unicode  =  (string[0] & 0x0f) << 18;
5689                 unicode += ((string[1] & 0x3f) << 12);
5690                 unicode += ((string[2] & 0x3f) << 6);
5691                 unicode +=  (string[3] & 0x3f);
5692                 break;
5693         case 5:
5694                 unicode  =  (string[0] & 0x0f) << 24;
5695                 unicode += ((string[1] & 0x3f) << 18);
5696                 unicode += ((string[2] & 0x3f) << 12);
5697                 unicode += ((string[3] & 0x3f) << 6);
5698                 unicode +=  (string[4] & 0x3f);
5699                 break;
5700         case 6:
5701                 unicode  =  (string[0] & 0x01) << 30;
5702                 unicode += ((string[1] & 0x3f) << 24);
5703                 unicode += ((string[2] & 0x3f) << 18);
5704                 unicode += ((string[3] & 0x3f) << 12);
5705                 unicode += ((string[4] & 0x3f) << 6);
5706                 unicode +=  (string[5] & 0x3f);
5707                 break;
5708         default:
5709                 die("Invalid unicode length");
5710         }
5712         /* Invalid characters could return the special 0xfffd value but NUL
5713          * should be just as good. */
5714         return unicode > 0xffff ? 0 : unicode;
5717 /* Calculates how much of string can be shown within the given maximum width
5718  * and sets trimmed parameter to non-zero value if all of string could not be
5719  * shown. If the reserve flag is TRUE, it will reserve at least one
5720  * trailing character, which can be useful when drawing a delimiter.
5721  *
5722  * Returns the number of bytes to output from string to satisfy max_width. */
5723 static size_t
5724 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5726         const char *start = string;
5727         const char *end = strchr(string, '\0');
5728         unsigned char last_bytes = 0;
5729         size_t last_ucwidth = 0;
5731         *width = 0;
5732         *trimmed = 0;
5734         while (string < end) {
5735                 int c = *(unsigned char *) string;
5736                 unsigned char bytes = utf8_bytes[c];
5737                 size_t ucwidth;
5738                 unsigned long unicode;
5740                 if (string + bytes > end)
5741                         break;
5743                 /* Change representation to figure out whether
5744                  * it is a single- or double-width character. */
5746                 unicode = utf8_to_unicode(string, bytes);
5747                 /* FIXME: Graceful handling of invalid unicode character. */
5748                 if (!unicode)
5749                         break;
5751                 ucwidth = unicode_width(unicode);
5752                 *width  += ucwidth;
5753                 if (*width > max_width) {
5754                         *trimmed = 1;
5755                         *width -= ucwidth;
5756                         if (reserve && *width == max_width) {
5757                                 string -= last_bytes;
5758                                 *width -= last_ucwidth;
5759                         }
5760                         break;
5761                 }
5763                 string  += bytes;
5764                 last_bytes = bytes;
5765                 last_ucwidth = ucwidth;
5766         }
5768         return string - start;
5772 /*
5773  * Status management
5774  */
5776 /* Whether or not the curses interface has been initialized. */
5777 static bool cursed = FALSE;
5779 /* The status window is used for polling keystrokes. */
5780 static WINDOW *status_win;
5782 static bool status_empty = TRUE;
5784 /* Update status and title window. */
5785 static void
5786 report(const char *msg, ...)
5788         struct view *view = display[current_view];
5790         if (input_mode)
5791                 return;
5793         if (!view) {
5794                 char buf[SIZEOF_STR];
5795                 va_list args;
5797                 va_start(args, msg);
5798                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5799                         buf[sizeof(buf) - 1] = 0;
5800                         buf[sizeof(buf) - 2] = '.';
5801                         buf[sizeof(buf) - 3] = '.';
5802                         buf[sizeof(buf) - 4] = '.';
5803                 }
5804                 va_end(args);
5805                 die("%s", buf);
5806         }
5808         if (!status_empty || *msg) {
5809                 va_list args;
5811                 va_start(args, msg);
5813                 wmove(status_win, 0, 0);
5814                 if (*msg) {
5815                         vwprintw(status_win, msg, args);
5816                         status_empty = FALSE;
5817                 } else {
5818                         status_empty = TRUE;
5819                 }
5820                 wclrtoeol(status_win);
5821                 wrefresh(status_win);
5823                 va_end(args);
5824         }
5826         update_view_title(view);
5827         update_display_cursor(view);
5830 /* Controls when nodelay should be in effect when polling user input. */
5831 static void
5832 set_nonblocking_input(bool loading)
5834         static unsigned int loading_views;
5836         if ((loading == FALSE && loading_views-- == 1) ||
5837             (loading == TRUE  && loading_views++ == 0))
5838                 nodelay(status_win, loading);
5841 static void
5842 init_display(void)
5844         int x, y;
5846         /* Initialize the curses library */
5847         if (isatty(STDIN_FILENO)) {
5848                 cursed = !!initscr();
5849                 opt_tty = stdin;
5850         } else {
5851                 /* Leave stdin and stdout alone when acting as a pager. */
5852                 opt_tty = fopen("/dev/tty", "r+");
5853                 if (!opt_tty)
5854                         die("Failed to open /dev/tty");
5855                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5856         }
5858         if (!cursed)
5859                 die("Failed to initialize curses");
5861         nonl();         /* Tell curses not to do NL->CR/NL on output */
5862         cbreak();       /* Take input chars one at a time, no wait for \n */
5863         noecho();       /* Don't echo input */
5864         leaveok(stdscr, TRUE);
5866         if (has_colors())
5867                 init_colors();
5869         getmaxyx(stdscr, y, x);
5870         status_win = newwin(1, 0, y - 1, 0);
5871         if (!status_win)
5872                 die("Failed to create status window");
5874         /* Enable keyboard mapping */
5875         keypad(status_win, TRUE);
5876         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5878         TABSIZE = opt_tab_size;
5879         if (opt_line_graphics) {
5880                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5881         }
5884 static bool
5885 prompt_yesno(const char *prompt)
5887         enum { WAIT, STOP, CANCEL  } status = WAIT;
5888         bool answer = FALSE;
5890         while (status == WAIT) {
5891                 struct view *view;
5892                 int i, key;
5894                 input_mode = TRUE;
5896                 foreach_view (view, i)
5897                         update_view(view);
5899                 input_mode = FALSE;
5901                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5902                 wclrtoeol(status_win);
5904                 /* Refresh, accept single keystroke of input */
5905                 key = wgetch(status_win);
5906                 switch (key) {
5907                 case ERR:
5908                         break;
5910                 case 'y':
5911                 case 'Y':
5912                         answer = TRUE;
5913                         status = STOP;
5914                         break;
5916                 case KEY_ESC:
5917                 case KEY_RETURN:
5918                 case KEY_ENTER:
5919                 case KEY_BACKSPACE:
5920                 case 'n':
5921                 case 'N':
5922                 case '\n':
5923                 default:
5924                         answer = FALSE;
5925                         status = CANCEL;
5926                 }
5927         }
5929         /* Clear the status window */
5930         status_empty = FALSE;
5931         report("");
5933         return answer;
5936 static char *
5937 read_prompt(const char *prompt)
5939         enum { READING, STOP, CANCEL } status = READING;
5940         static char buf[SIZEOF_STR];
5941         int pos = 0;
5943         while (status == READING) {
5944                 struct view *view;
5945                 int i, key;
5947                 input_mode = TRUE;
5949                 foreach_view (view, i)
5950                         update_view(view);
5952                 input_mode = FALSE;
5954                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5955                 wclrtoeol(status_win);
5957                 /* Refresh, accept single keystroke of input */
5958                 key = wgetch(status_win);
5959                 switch (key) {
5960                 case KEY_RETURN:
5961                 case KEY_ENTER:
5962                 case '\n':
5963                         status = pos ? STOP : CANCEL;
5964                         break;
5966                 case KEY_BACKSPACE:
5967                         if (pos > 0)
5968                                 pos--;
5969                         else
5970                                 status = CANCEL;
5971                         break;
5973                 case KEY_ESC:
5974                         status = CANCEL;
5975                         break;
5977                 case ERR:
5978                         break;
5980                 default:
5981                         if (pos >= sizeof(buf)) {
5982                                 report("Input string too long");
5983                                 return NULL;
5984                         }
5986                         if (isprint(key))
5987                                 buf[pos++] = (char) key;
5988                 }
5989         }
5991         /* Clear the status window */
5992         status_empty = FALSE;
5993         report("");
5995         if (status == CANCEL)
5996                 return NULL;
5998         buf[pos++] = 0;
6000         return buf;
6003 /*
6004  * Repository properties
6005  */
6007 static int
6008 git_properties(const char **argv, const char *separators,
6009                int (*read_property)(char *, size_t, char *, size_t))
6011         struct io io = {};
6013         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6014                 return read_properties(&io, separators, read_property);
6015         return ERR;
6018 static struct ref *refs = NULL;
6019 static size_t refs_alloc = 0;
6020 static size_t refs_size = 0;
6022 /* Id <-> ref store */
6023 static struct ref ***id_refs = NULL;
6024 static size_t id_refs_alloc = 0;
6025 static size_t id_refs_size = 0;
6027 static int
6028 compare_refs(const void *ref1_, const void *ref2_)
6030         const struct ref *ref1 = *(const struct ref **)ref1_;
6031         const struct ref *ref2 = *(const struct ref **)ref2_;
6033         if (ref1->tag != ref2->tag)
6034                 return ref2->tag - ref1->tag;
6035         if (ref1->ltag != ref2->ltag)
6036                 return ref2->ltag - ref2->ltag;
6037         if (ref1->head != ref2->head)
6038                 return ref2->head - ref1->head;
6039         if (ref1->tracked != ref2->tracked)
6040                 return ref2->tracked - ref1->tracked;
6041         if (ref1->remote != ref2->remote)
6042                 return ref2->remote - ref1->remote;
6043         return strcmp(ref1->name, ref2->name);
6046 static struct ref **
6047 get_refs(const char *id)
6049         struct ref ***tmp_id_refs;
6050         struct ref **ref_list = NULL;
6051         size_t ref_list_alloc = 0;
6052         size_t ref_list_size = 0;
6053         size_t i;
6055         for (i = 0; i < id_refs_size; i++)
6056                 if (!strcmp(id, id_refs[i][0]->id))
6057                         return id_refs[i];
6059         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6060                                     sizeof(*id_refs));
6061         if (!tmp_id_refs)
6062                 return NULL;
6064         id_refs = tmp_id_refs;
6066         for (i = 0; i < refs_size; i++) {
6067                 struct ref **tmp;
6069                 if (strcmp(id, refs[i].id))
6070                         continue;
6072                 tmp = realloc_items(ref_list, &ref_list_alloc,
6073                                     ref_list_size + 1, sizeof(*ref_list));
6074                 if (!tmp) {
6075                         if (ref_list)
6076                                 free(ref_list);
6077                         return NULL;
6078                 }
6080                 ref_list = tmp;
6081                 ref_list[ref_list_size] = &refs[i];
6082                 /* XXX: The properties of the commit chains ensures that we can
6083                  * safely modify the shared ref. The repo references will
6084                  * always be similar for the same id. */
6085                 ref_list[ref_list_size]->next = 1;
6087                 ref_list_size++;
6088         }
6090         if (ref_list) {
6091                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6092                 ref_list[ref_list_size - 1]->next = 0;
6093                 id_refs[id_refs_size++] = ref_list;
6094         }
6096         return ref_list;
6099 static int
6100 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6102         struct ref *ref;
6103         bool tag = FALSE;
6104         bool ltag = FALSE;
6105         bool remote = FALSE;
6106         bool tracked = FALSE;
6107         bool check_replace = FALSE;
6108         bool head = FALSE;
6110         if (!prefixcmp(name, "refs/tags/")) {
6111                 if (!suffixcmp(name, namelen, "^{}")) {
6112                         namelen -= 3;
6113                         name[namelen] = 0;
6114                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6115                                 check_replace = TRUE;
6116                 } else {
6117                         ltag = TRUE;
6118                 }
6120                 tag = TRUE;
6121                 namelen -= STRING_SIZE("refs/tags/");
6122                 name    += STRING_SIZE("refs/tags/");
6124         } else if (!prefixcmp(name, "refs/remotes/")) {
6125                 remote = TRUE;
6126                 namelen -= STRING_SIZE("refs/remotes/");
6127                 name    += STRING_SIZE("refs/remotes/");
6128                 tracked  = !strcmp(opt_remote, name);
6130         } else if (!prefixcmp(name, "refs/heads/")) {
6131                 namelen -= STRING_SIZE("refs/heads/");
6132                 name    += STRING_SIZE("refs/heads/");
6133                 head     = !strncmp(opt_head, name, namelen);
6135         } else if (!strcmp(name, "HEAD")) {
6136                 string_ncopy(opt_head_rev, id, idlen);
6137                 return OK;
6138         }
6140         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6141                 /* it's an annotated tag, replace the previous sha1 with the
6142                  * resolved commit id; relies on the fact git-ls-remote lists
6143                  * the commit id of an annotated tag right before the commit id
6144                  * it points to. */
6145                 refs[refs_size - 1].ltag = ltag;
6146                 string_copy_rev(refs[refs_size - 1].id, id);
6148                 return OK;
6149         }
6150         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6151         if (!refs)
6152                 return ERR;
6154         ref = &refs[refs_size++];
6155         ref->name = malloc(namelen + 1);
6156         if (!ref->name)
6157                 return ERR;
6159         strncpy(ref->name, name, namelen);
6160         ref->name[namelen] = 0;
6161         ref->head = head;
6162         ref->tag = tag;
6163         ref->ltag = ltag;
6164         ref->remote = remote;
6165         ref->tracked = tracked;
6166         string_copy_rev(ref->id, id);
6168         return OK;
6171 static int
6172 load_refs(void)
6174         static const char *ls_remote_argv[SIZEOF_ARG] = {
6175                 "git", "ls-remote", ".", NULL
6176         };
6177         static bool init = FALSE;
6179         if (!init) {
6180                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6181                 init = TRUE;
6182         }
6184         if (!*opt_git_dir)
6185                 return OK;
6187         while (refs_size > 0)
6188                 free(refs[--refs_size].name);
6189         while (id_refs_size > 0)
6190                 free(id_refs[--id_refs_size]);
6192         return git_properties(ls_remote_argv, "\t", read_ref);
6195 static int
6196 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6198         if (!strcmp(name, "i18n.commitencoding"))
6199                 string_ncopy(opt_encoding, value, valuelen);
6201         if (!strcmp(name, "core.editor"))
6202                 string_ncopy(opt_editor, value, valuelen);
6204         /* branch.<head>.remote */
6205         if (*opt_head &&
6206             !strncmp(name, "branch.", 7) &&
6207             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6208             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6209                 string_ncopy(opt_remote, value, valuelen);
6211         if (*opt_head && *opt_remote &&
6212             !strncmp(name, "branch.", 7) &&
6213             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6214             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6215                 size_t from = strlen(opt_remote);
6217                 if (!prefixcmp(value, "refs/heads/")) {
6218                         value += STRING_SIZE("refs/heads/");
6219                         valuelen -= STRING_SIZE("refs/heads/");
6220                 }
6222                 if (!string_format_from(opt_remote, &from, "/%s", value))
6223                         opt_remote[0] = 0;
6224         }
6226         return OK;
6229 static int
6230 load_git_config(void)
6232         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6234         return git_properties(config_list_argv, "=", read_repo_config_option);
6237 static int
6238 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6240         if (!opt_git_dir[0]) {
6241                 string_ncopy(opt_git_dir, name, namelen);
6243         } else if (opt_is_inside_work_tree == -1) {
6244                 /* This can be 3 different values depending on the
6245                  * version of git being used. If git-rev-parse does not
6246                  * understand --is-inside-work-tree it will simply echo
6247                  * the option else either "true" or "false" is printed.
6248                  * Default to true for the unknown case. */
6249                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6250         } else {
6251                 string_ncopy(opt_cdup, name, namelen);
6252         }
6254         return OK;
6257 static int
6258 load_repo_info(void)
6260         const char *head_argv[] = {
6261                 "git", "symbolic-ref", "HEAD", NULL
6262         };
6263         const char *rev_parse_argv[] = {
6264                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6265                         "--show-cdup", NULL
6266         };
6268         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6269                 chomp_string(opt_head);
6270                 if (!prefixcmp(opt_head, "refs/heads/")) {
6271                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6273                         memmove(opt_head, offset, strlen(offset) + 1);
6274                 }
6275         }
6277         return git_properties(rev_parse_argv, "=", read_repo_info);
6280 static int
6281 read_properties(struct io *io, const char *separators,
6282                 int (*read_property)(char *, size_t, char *, size_t))
6284         char *name;
6285         int state = OK;
6287         if (!start_io(io))
6288                 return ERR;
6290         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6291                 char *value;
6292                 size_t namelen;
6293                 size_t valuelen;
6295                 name = chomp_string(name);
6296                 namelen = strcspn(name, separators);
6298                 if (name[namelen]) {
6299                         name[namelen] = 0;
6300                         value = chomp_string(name + namelen + 1);
6301                         valuelen = strlen(value);
6303                 } else {
6304                         value = "";
6305                         valuelen = 0;
6306                 }
6308                 state = read_property(name, namelen, value, valuelen);
6309         }
6311         if (state != ERR && io_error(io))
6312                 state = ERR;
6313         done_io(io);
6315         return state;
6319 /*
6320  * Main
6321  */
6323 static void __NORETURN
6324 quit(int sig)
6326         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6327         if (cursed)
6328                 endwin();
6329         exit(0);
6332 static void __NORETURN
6333 die(const char *err, ...)
6335         va_list args;
6337         endwin();
6339         va_start(args, err);
6340         fputs("tig: ", stderr);
6341         vfprintf(stderr, err, args);
6342         fputs("\n", stderr);
6343         va_end(args);
6345         exit(1);
6348 static void
6349 warn(const char *msg, ...)
6351         va_list args;
6353         va_start(args, msg);
6354         fputs("tig warning: ", stderr);
6355         vfprintf(stderr, msg, args);
6356         fputs("\n", stderr);
6357         va_end(args);
6360 int
6361 main(int argc, const char *argv[])
6363         const char **run_argv = NULL;
6364         struct view *view;
6365         enum request request;
6366         size_t i;
6368         signal(SIGINT, quit);
6370         if (setlocale(LC_ALL, "")) {
6371                 char *codeset = nl_langinfo(CODESET);
6373                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6374         }
6376         if (load_repo_info() == ERR)
6377                 die("Failed to load repo info.");
6379         if (load_options() == ERR)
6380                 die("Failed to load user config.");
6382         if (load_git_config() == ERR)
6383                 die("Failed to load repo config.");
6385         request = parse_options(argc, argv, &run_argv);
6386         if (request == REQ_NONE)
6387                 return 0;
6389         /* Require a git repository unless when running in pager mode. */
6390         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6391                 die("Not a git repository");
6393         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6394                 opt_utf8 = FALSE;
6396         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6397                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6398                 if (opt_iconv == ICONV_NONE)
6399                         die("Failed to initialize character set conversion");
6400         }
6402         if (load_refs() == ERR)
6403                 die("Failed to load refs.");
6405         foreach_view (view, i)
6406                 argv_from_env(view->ops->argv, view->cmd_env);
6408         init_display();
6410         if (request == REQ_VIEW_PAGER || run_argv) {
6411                 if (request == REQ_VIEW_PAGER)
6412                         io_open(&VIEW(request)->io, "");
6413                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6414                         die("Failed to format arguments");
6415                 open_view(NULL, request, OPEN_PREPARED);
6416                 request = REQ_NONE;
6417         }
6419         while (view_driver(display[current_view], request)) {
6420                 int key;
6421                 int i;
6423                 foreach_view (view, i)
6424                         update_view(view);
6425                 view = display[current_view];
6427                 /* Refresh, accept single keystroke of input */
6428                 key = wgetch(status_win);
6430                 /* wgetch() with nodelay() enabled returns ERR when there's no
6431                  * input. */
6432                 if (key == ERR) {
6433                         request = REQ_NONE;
6434                         continue;
6435                 }
6437                 request = get_keybinding(view->keymap, key);
6439                 /* Some low-level request handling. This keeps access to
6440                  * status_win restricted. */
6441                 switch (request) {
6442                 case REQ_PROMPT:
6443                 {
6444                         char *cmd = read_prompt(":");
6446                         if (cmd) {
6447                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6448                                 const char *argv[SIZEOF_ARG] = { "git" };
6449                                 int argc = 1;
6451                                 /* When running random commands, initially show the
6452                                  * command in the title. However, it maybe later be
6453                                  * overwritten if a commit line is selected. */
6454                                 string_ncopy(next->ref, cmd, strlen(cmd));
6456                                 if (!argv_from_string(argv, &argc, cmd)) {
6457                                         report("Too many arguments");
6458                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6459                                         report("Failed to format command");
6460                                 } else {
6461                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6462                                 }
6463                         }
6465                         request = REQ_NONE;
6466                         break;
6467                 }
6468                 case REQ_SEARCH:
6469                 case REQ_SEARCH_BACK:
6470                 {
6471                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6472                         char *search = read_prompt(prompt);
6474                         if (search)
6475                                 string_ncopy(opt_search, search, strlen(search));
6476                         else
6477                                 request = REQ_NONE;
6478                         break;
6479                 }
6480                 case REQ_SCREEN_RESIZE:
6481                 {
6482                         int height, width;
6484                         getmaxyx(stdscr, height, width);
6486                         /* Resize the status view and let the view driver take
6487                          * care of resizing the displayed views. */
6488                         wresize(status_win, 1, width);
6489                         mvwin(status_win, height - 1, 0);
6490                         wrefresh(status_win);
6491                         break;
6492                 }
6493                 default:
6494                         break;
6495                 }
6496         }
6498         quit(0);
6500         return 0;