Code

IO API: use select(2) to check if pipe is readable when updating a view
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static bool prompt_yesno(const char *prompt);
73 static int load_refs(void);
75 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
76 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
108 #define AUTHOR_COLS     20
109 #define ID_COLS         8
111 /* The default interval between line numbers. */
112 #define NUMBER_INTERVAL 5
114 #define TAB_SIZE        8
116 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char *name;             /* Ref name; tag or head names are shortened. */
132         char id[SIZEOF_REV];    /* Commit SHA1 ID */
133         unsigned int head:1;    /* Is it the current HEAD? */
134         unsigned int tag:1;     /* Is it a tag? */
135         unsigned int ltag:1;    /* If so, is the tag local? */
136         unsigned int remote:1;  /* Is it a remote ref? */
137         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138         unsigned int next:1;    /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152         const char *name;
153         int namelen;
154         int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159                  int *value, const char *name, int namelen)
162         int i;
164         for (i = 0; i < map_size; i++)
165                 if (namelen == map[i].namelen &&
166                     !strncasecmp(name, map[i].name, namelen)) {
167                         *value = map[i].value;
168                         return OK;
169                 }
171         return ERR;
175 /*
176  * String helpers
177  */
179 static inline void
180 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
182         if (srclen > dstlen - 1)
183                 srclen = dstlen - 1;
185         strncpy(dst, src, srclen);
186         dst[srclen] = 0;
189 /* Shorthands for safely copying into a fixed buffer. */
191 #define string_copy(dst, src) \
192         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
194 #define string_ncopy(dst, src, srclen) \
195         string_ncopy_do(dst, sizeof(dst), src, srclen)
197 #define string_copy_rev(dst, src) \
198         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
200 #define string_add(dst, from, src) \
201         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 static char *
204 chomp_string(char *name)
206         int namelen;
208         while (isspace(*name))
209                 name++;
211         namelen = strlen(name) - 1;
212         while (namelen > 0 && isspace(name[namelen]))
213                 name[namelen--] = 0;
215         return name;
218 static bool
219 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
221         va_list args;
222         size_t pos = bufpos ? *bufpos : 0;
224         va_start(args, fmt);
225         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
226         va_end(args);
228         if (bufpos)
229                 *bufpos = pos;
231         return pos >= bufsize ? FALSE : TRUE;
234 #define string_format(buf, fmt, args...) \
235         string_nformat(buf, sizeof(buf), NULL, fmt, args)
237 #define string_format_from(buf, from, fmt, args...) \
238         string_nformat(buf, sizeof(buf), from, fmt, args)
240 static int
241 string_enum_compare(const char *str1, const char *str2, int len)
243         size_t i;
245 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
247         /* Diff-Header == DIFF_HEADER */
248         for (i = 0; i < len; i++) {
249                 if (toupper(str1[i]) == toupper(str2[i]))
250                         continue;
252                 if (string_enum_sep(str1[i]) &&
253                     string_enum_sep(str2[i]))
254                         continue;
256                 return str1[i] - str2[i];
257         }
259         return 0;
262 #define prefixcmp(str1, str2) \
263         strncmp(str1, str2, STRING_SIZE(str2))
265 static inline int
266 suffixcmp(const char *str, int slen, const char *suffix)
268         size_t len = slen >= 0 ? slen : strlen(str);
269         size_t suffixlen = strlen(suffix);
271         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
275 static bool
276 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
278         int valuelen;
280         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
281                 bool advance = cmd[valuelen] != 0;
283                 cmd[valuelen] = 0;
284                 argv[(*argc)++] = chomp_string(cmd);
285                 cmd += valuelen + advance;
286         }
288         if (*argc < SIZEOF_ARG)
289                 argv[*argc] = NULL;
290         return *argc < SIZEOF_ARG;
293 static void
294 argv_from_env(const char **argv, const char *name)
296         char *env = argv ? getenv(name) : NULL;
297         int argc = 0;
299         if (env && *env)
300                 env = strdup(env);
301         if (env && !argv_from_string(argv, &argc, env))
302                 die("Too many arguments in the `%s` environment variable", name);
306 /*
307  * Executing external commands.
308  */
310 enum io_type {
311         IO_FD,                  /* File descriptor based IO. */
312         IO_BG,                  /* Execute command in the background. */
313         IO_FG,                  /* Execute command with same std{in,out,err}. */
314         IO_RD,                  /* Read only fork+exec IO. */
315         IO_WR,                  /* Write only fork+exec IO. */
316 };
318 struct io {
319         enum io_type type;      /* The requested type of pipe. */
320         const char *dir;        /* Directory from which to execute. */
321         pid_t pid;              /* Pipe for reading or writing. */
322         int pipe;               /* Pipe end for reading or writing. */
323         int error;              /* Error status. */
324         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
325         char *buf;              /* Read buffer. */
326         size_t bufalloc;        /* Allocated buffer size. */
327         size_t bufsize;         /* Buffer content size. */
328         char *bufpos;           /* Current buffer position. */
329         unsigned int eof:1;     /* Has end of file been reached. */
330 };
332 static void
333 reset_io(struct io *io)
335         io->pipe = -1;
336         io->pid = 0;
337         io->buf = io->bufpos = NULL;
338         io->bufalloc = io->bufsize = 0;
339         io->error = 0;
340         io->eof = 0;
343 static void
344 init_io(struct io *io, const char *dir, enum io_type type)
346         reset_io(io);
347         io->type = type;
348         io->dir = dir;
351 static bool
352 init_io_rd(struct io *io, const char *argv[], const char *dir,
353                 enum format_flags flags)
355         init_io(io, dir, IO_RD);
356         return format_argv(io->argv, argv, flags);
359 static bool
360 io_open(struct io *io, const char *name)
362         init_io(io, NULL, IO_FD);
363         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
364         return io->pipe != -1;
367 static bool
368 kill_io(struct io *io)
370         return kill(io->pid, SIGKILL) != -1;
373 static bool
374 done_io(struct io *io)
376         pid_t pid = io->pid;
378         if (io->pipe != -1)
379                 close(io->pipe);
380         free(io->buf);
381         reset_io(io);
383         while (pid > 0) {
384                 int status;
385                 pid_t waiting = waitpid(pid, &status, 0);
387                 if (waiting < 0) {
388                         if (errno == EINTR)
389                                 continue;
390                         report("waitpid failed (%s)", strerror(errno));
391                         return FALSE;
392                 }
394                 return waiting == pid &&
395                        !WIFSIGNALED(status) &&
396                        WIFEXITED(status) &&
397                        !WEXITSTATUS(status);
398         }
400         return TRUE;
403 static bool
404 start_io(struct io *io)
406         int pipefds[2] = { -1, -1 };
408         if (io->type == IO_FD)
409                 return TRUE;
411         if ((io->type == IO_RD || io->type == IO_WR) &&
412             pipe(pipefds) < 0)
413                 return FALSE;
415         if ((io->pid = fork())) {
416                 if (pipefds[!(io->type == IO_WR)] != -1)
417                         close(pipefds[!(io->type == IO_WR)]);
418                 if (io->pid != -1) {
419                         io->pipe = pipefds[!!(io->type == IO_WR)];
420                         return TRUE;
421                 }
423         } else {
424                 if (io->type != IO_FG) {
425                         int devnull = open("/dev/null", O_RDWR);
426                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
427                         int writefd = io->type == IO_RD ? pipefds[1] : devnull;
429                         dup2(readfd,  STDIN_FILENO);
430                         dup2(writefd, STDOUT_FILENO);
431                         dup2(devnull, STDERR_FILENO);
433                         close(devnull);
434                         if (pipefds[0] != -1)
435                                 close(pipefds[0]);
436                         if (pipefds[1] != -1)
437                                 close(pipefds[1]);
438                 }
440                 if (io->dir && *io->dir && chdir(io->dir) == -1)
441                         die("Failed to change directory: %s", strerror(errno));
443                 execvp(io->argv[0], (char *const*) io->argv);
444                 die("Failed to execute program: %s", strerror(errno));
445         }
447         if (pipefds[!!(io->type == IO_WR)] != -1)
448                 close(pipefds[!!(io->type == IO_WR)]);
449         return FALSE;
452 static bool
453 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
455         init_io(io, dir, type);
456         if (!format_argv(io->argv, argv, FORMAT_NONE))
457                 return FALSE;
458         return start_io(io);
461 static int
462 run_io_do(struct io *io)
464         return start_io(io) && done_io(io);
467 static int
468 run_io_bg(const char **argv)
470         struct io io = {};
472         init_io(&io, NULL, IO_BG);
473         if (!format_argv(io.argv, argv, FORMAT_NONE))
474                 return FALSE;
475         return run_io_do(&io);
478 static bool
479 run_io_fg(const char **argv, const char *dir)
481         struct io io = {};
483         init_io(&io, dir, IO_FG);
484         if (!format_argv(io.argv, argv, FORMAT_NONE))
485                 return FALSE;
486         return run_io_do(&io);
489 static bool
490 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
492         return init_io_rd(io, argv, NULL, flags) && start_io(io);
495 static bool
496 io_eof(struct io *io)
498         return io->eof;
501 static int
502 io_error(struct io *io)
504         return io->error;
507 static bool
508 io_strerror(struct io *io)
510         return strerror(io->error);
513 static bool
514 io_can_read(struct io *io)
516         struct timeval tv = { 0, 500 };
517         fd_set fds;
519         FD_ZERO(&fds);
520         FD_SET(io->pipe, &fds);
522         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
525 static ssize_t
526 io_read(struct io *io, void *buf, size_t bufsize)
528         do {
529                 ssize_t readsize = read(io->pipe, buf, bufsize);
531                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
532                         continue;
533                 else if (readsize == -1)
534                         io->error = errno;
535                 else if (readsize == 0)
536                         io->eof = 1;
537                 return readsize;
538         } while (1);
541 static char *
542 io_get(struct io *io, int c, bool can_read)
544         char *eol;
545         ssize_t readsize;
547         if (!io->buf) {
548                 io->buf = io->bufpos = malloc(BUFSIZ);
549                 if (!io->buf)
550                         return NULL;
551                 io->bufalloc = BUFSIZ;
552                 io->bufsize = 0;
553         }
555         while (TRUE) {
556                 if (io->bufsize > 0) {
557                         eol = memchr(io->bufpos, c, io->bufsize);
558                         if (eol) {
559                                 char *line = io->bufpos;
561                                 *eol = 0;
562                                 io->bufpos = eol + 1;
563                                 io->bufsize -= io->bufpos - line;
564                                 return line;
565                         }
566                 }
568                 if (io_eof(io)) {
569                         if (io->bufsize) {
570                                 io->bufpos[io->bufsize] = 0;
571                                 io->bufsize = 0;
572                                 return io->bufpos;
573                         }
574                         return NULL;
575                 }
577                 if (!can_read)
578                         return NULL;
580                 if (io->bufsize > 0 && io->bufpos > io->buf)
581                         memmove(io->buf, io->bufpos, io->bufsize);
583                 io->bufpos = io->buf;
584                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
585                 if (io_error(io))
586                         return NULL;
587                 io->bufsize += readsize;
588         }
591 static bool
592 io_write(struct io *io, const void *buf, size_t bufsize)
594         size_t written = 0;
596         while (!io_error(io) && written < bufsize) {
597                 ssize_t size;
599                 size = write(io->pipe, buf + written, bufsize - written);
600                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
601                         continue;
602                 else if (size == -1)
603                         io->error = errno;
604                 else
605                         written += size;
606         }
608         return written == bufsize;
611 static bool
612 run_io_buf(const char **argv, char buf[], size_t bufsize)
614         struct io io = {};
615         bool error;
617         if (!run_io_rd(&io, argv, FORMAT_NONE))
618                 return FALSE;
620         io.buf = io.bufpos = buf;
621         io.bufalloc = bufsize;
622         error = !io_get(&io, '\n', TRUE) && io_error(&io);
623         io.buf = NULL;
625         return done_io(&io) || error;
628 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
630 /*
631  * User requests
632  */
634 #define REQ_INFO \
635         /* XXX: Keep the view request first and in sync with views[]. */ \
636         REQ_GROUP("View switching") \
637         REQ_(VIEW_MAIN,         "Show main view"), \
638         REQ_(VIEW_DIFF,         "Show diff view"), \
639         REQ_(VIEW_LOG,          "Show log view"), \
640         REQ_(VIEW_TREE,         "Show tree view"), \
641         REQ_(VIEW_BLOB,         "Show blob view"), \
642         REQ_(VIEW_BLAME,        "Show blame view"), \
643         REQ_(VIEW_HELP,         "Show help page"), \
644         REQ_(VIEW_PAGER,        "Show pager view"), \
645         REQ_(VIEW_STATUS,       "Show status view"), \
646         REQ_(VIEW_STAGE,        "Show stage view"), \
647         \
648         REQ_GROUP("View manipulation") \
649         REQ_(ENTER,             "Enter current line and scroll"), \
650         REQ_(NEXT,              "Move to next"), \
651         REQ_(PREVIOUS,          "Move to previous"), \
652         REQ_(VIEW_NEXT,         "Move focus to next view"), \
653         REQ_(REFRESH,           "Reload and refresh"), \
654         REQ_(MAXIMIZE,          "Maximize the current view"), \
655         REQ_(VIEW_CLOSE,        "Close the current view"), \
656         REQ_(QUIT,              "Close all views and quit"), \
657         \
658         REQ_GROUP("View specific requests") \
659         REQ_(STATUS_UPDATE,     "Update file status"), \
660         REQ_(STATUS_REVERT,     "Revert file changes"), \
661         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
662         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
663         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
664         \
665         REQ_GROUP("Cursor navigation") \
666         REQ_(MOVE_UP,           "Move cursor one line up"), \
667         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
668         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
669         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
670         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
671         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
672         \
673         REQ_GROUP("Scrolling") \
674         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
675         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
676         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
677         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
678         \
679         REQ_GROUP("Searching") \
680         REQ_(SEARCH,            "Search the view"), \
681         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
682         REQ_(FIND_NEXT,         "Find next search match"), \
683         REQ_(FIND_PREV,         "Find previous search match"), \
684         \
685         REQ_GROUP("Option manipulation") \
686         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
687         REQ_(TOGGLE_DATE,       "Toggle date display"), \
688         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
689         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
690         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
691         \
692         REQ_GROUP("Misc") \
693         REQ_(PROMPT,            "Bring up the prompt"), \
694         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
695         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
696         REQ_(SHOW_VERSION,      "Show version information"), \
697         REQ_(STOP_LOADING,      "Stop all loading views"), \
698         REQ_(EDIT,              "Open in editor"), \
699         REQ_(NONE,              "Do nothing")
702 /* User action requests. */
703 enum request {
704 #define REQ_GROUP(help)
705 #define REQ_(req, help) REQ_##req
707         /* Offset all requests to avoid conflicts with ncurses getch values. */
708         REQ_OFFSET = KEY_MAX + 1,
709         REQ_INFO
711 #undef  REQ_GROUP
712 #undef  REQ_
713 };
715 struct request_info {
716         enum request request;
717         const char *name;
718         int namelen;
719         const char *help;
720 };
722 static struct request_info req_info[] = {
723 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
724 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
725         REQ_INFO
726 #undef  REQ_GROUP
727 #undef  REQ_
728 };
730 static enum request
731 get_request(const char *name)
733         int namelen = strlen(name);
734         int i;
736         for (i = 0; i < ARRAY_SIZE(req_info); i++)
737                 if (req_info[i].namelen == namelen &&
738                     !string_enum_compare(req_info[i].name, name, namelen))
739                         return req_info[i].request;
741         return REQ_NONE;
745 /*
746  * Options
747  */
749 static const char usage[] =
750 "tig " TIG_VERSION " (" __DATE__ ")\n"
751 "\n"
752 "Usage: tig        [options] [revs] [--] [paths]\n"
753 "   or: tig show   [options] [revs] [--] [paths]\n"
754 "   or: tig blame  [rev] path\n"
755 "   or: tig status\n"
756 "   or: tig <      [git command output]\n"
757 "\n"
758 "Options:\n"
759 "  -v, --version   Show version and exit\n"
760 "  -h, --help      Show help message and exit";
762 /* Option and state variables. */
763 static bool opt_date                    = TRUE;
764 static bool opt_author                  = TRUE;
765 static bool opt_line_number             = FALSE;
766 static bool opt_line_graphics           = TRUE;
767 static bool opt_rev_graph               = FALSE;
768 static bool opt_show_refs               = TRUE;
769 static int opt_num_interval             = NUMBER_INTERVAL;
770 static int opt_tab_size                 = TAB_SIZE;
771 static int opt_author_cols              = AUTHOR_COLS-1;
772 static char opt_path[SIZEOF_STR]        = "";
773 static char opt_file[SIZEOF_STR]        = "";
774 static char opt_ref[SIZEOF_REF]         = "";
775 static char opt_head[SIZEOF_REF]        = "";
776 static char opt_head_rev[SIZEOF_REV]    = "";
777 static char opt_remote[SIZEOF_REF]      = "";
778 static char opt_encoding[20]            = "UTF-8";
779 static bool opt_utf8                    = TRUE;
780 static char opt_codeset[20]             = "UTF-8";
781 static iconv_t opt_iconv                = ICONV_NONE;
782 static char opt_search[SIZEOF_STR]      = "";
783 static char opt_cdup[SIZEOF_STR]        = "";
784 static char opt_git_dir[SIZEOF_STR]     = "";
785 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
786 static char opt_editor[SIZEOF_STR]      = "";
787 static FILE *opt_tty                    = NULL;
789 #define is_initial_commit()     (!*opt_head_rev)
790 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
792 static enum request
793 parse_options(int argc, const char *argv[], const char ***run_argv)
795         enum request request = REQ_VIEW_MAIN;
796         const char *subcommand;
797         bool seen_dashdash = FALSE;
798         /* XXX: This is vulnerable to the user overriding options
799          * required for the main view parser. */
800         const char *custom_argv[SIZEOF_ARG] = {
801                 "git", "log", "--no-color", "--pretty=raw", "--parents",
802                         "--topo-order", NULL
803         };
804         int i, j = 6;
806         if (!isatty(STDIN_FILENO))
807                 return REQ_VIEW_PAGER;
809         if (argc <= 1)
810                 return REQ_VIEW_MAIN;
812         subcommand = argv[1];
813         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
814                 if (!strcmp(subcommand, "-S"))
815                         warn("`-S' has been deprecated; use `tig status' instead");
816                 if (argc > 2)
817                         warn("ignoring arguments after `%s'", subcommand);
818                 return REQ_VIEW_STATUS;
820         } else if (!strcmp(subcommand, "blame")) {
821                 if (argc <= 2 || argc > 4)
822                         die("invalid number of options to blame\n\n%s", usage);
824                 i = 2;
825                 if (argc == 4) {
826                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
827                         i++;
828                 }
830                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
831                 return REQ_VIEW_BLAME;
833         } else if (!strcmp(subcommand, "show")) {
834                 request = REQ_VIEW_DIFF;
836         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
837                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
838                 warn("`tig %s' has been deprecated", subcommand);
840         } else {
841                 subcommand = NULL;
842         }
844         if (subcommand) {
845                 custom_argv[1] = subcommand;
846                 j = 2;
847         }
849         for (i = 1 + !!subcommand; i < argc; i++) {
850                 const char *opt = argv[i];
852                 if (seen_dashdash || !strcmp(opt, "--")) {
853                         seen_dashdash = TRUE;
855                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
856                         printf("tig version %s\n", TIG_VERSION);
857                         return REQ_NONE;
859                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
860                         printf("%s\n", usage);
861                         return REQ_NONE;
862                 }
864                 custom_argv[j++] = opt;
865                 if (j >= ARRAY_SIZE(custom_argv))
866                         die("command too long");
867         }
869         custom_argv[j] = NULL;
870         *run_argv = custom_argv;
872         return request;
876 /*
877  * Line-oriented content detection.
878  */
880 #define LINE_INFO \
881 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
882 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
883 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
884 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
885 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
886 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
887 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
888 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
889 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
890 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
891 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
892 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
893 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
894 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
895 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
896 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
897 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
898 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
899 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
900 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
901 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
902 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
903 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
904 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
905 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
906 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
907 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
908 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
910 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
911 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
912 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
913 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
914 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
915 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
916 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
917 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
918 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
919 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
920 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
921 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
922 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
923 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
924 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
925 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
926 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
927 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
928 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
929 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
930 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
931 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
932 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
933 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
934 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
936 enum line_type {
937 #define LINE(type, line, fg, bg, attr) \
938         LINE_##type
939         LINE_INFO,
940         LINE_NONE
941 #undef  LINE
942 };
944 struct line_info {
945         const char *name;       /* Option name. */
946         int namelen;            /* Size of option name. */
947         const char *line;       /* The start of line to match. */
948         int linelen;            /* Size of string to match. */
949         int fg, bg, attr;       /* Color and text attributes for the lines. */
950 };
952 static struct line_info line_info[] = {
953 #define LINE(type, line, fg, bg, attr) \
954         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
955         LINE_INFO
956 #undef  LINE
957 };
959 static enum line_type
960 get_line_type(const char *line)
962         int linelen = strlen(line);
963         enum line_type type;
965         for (type = 0; type < ARRAY_SIZE(line_info); type++)
966                 /* Case insensitive search matches Signed-off-by lines better. */
967                 if (linelen >= line_info[type].linelen &&
968                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
969                         return type;
971         return LINE_DEFAULT;
974 static inline int
975 get_line_attr(enum line_type type)
977         assert(type < ARRAY_SIZE(line_info));
978         return COLOR_PAIR(type) | line_info[type].attr;
981 static struct line_info *
982 get_line_info(const char *name)
984         size_t namelen = strlen(name);
985         enum line_type type;
987         for (type = 0; type < ARRAY_SIZE(line_info); type++)
988                 if (namelen == line_info[type].namelen &&
989                     !string_enum_compare(line_info[type].name, name, namelen))
990                         return &line_info[type];
992         return NULL;
995 static void
996 init_colors(void)
998         int default_bg = line_info[LINE_DEFAULT].bg;
999         int default_fg = line_info[LINE_DEFAULT].fg;
1000         enum line_type type;
1002         start_color();
1004         if (assume_default_colors(default_fg, default_bg) == ERR) {
1005                 default_bg = COLOR_BLACK;
1006                 default_fg = COLOR_WHITE;
1007         }
1009         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1010                 struct line_info *info = &line_info[type];
1011                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1012                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1014                 init_pair(type, fg, bg);
1015         }
1018 struct line {
1019         enum line_type type;
1021         /* State flags */
1022         unsigned int selected:1;
1023         unsigned int dirty:1;
1025         void *data;             /* User data */
1026 };
1029 /*
1030  * Keys
1031  */
1033 struct keybinding {
1034         int alias;
1035         enum request request;
1036 };
1038 static struct keybinding default_keybindings[] = {
1039         /* View switching */
1040         { 'm',          REQ_VIEW_MAIN },
1041         { 'd',          REQ_VIEW_DIFF },
1042         { 'l',          REQ_VIEW_LOG },
1043         { 't',          REQ_VIEW_TREE },
1044         { 'f',          REQ_VIEW_BLOB },
1045         { 'B',          REQ_VIEW_BLAME },
1046         { 'p',          REQ_VIEW_PAGER },
1047         { 'h',          REQ_VIEW_HELP },
1048         { 'S',          REQ_VIEW_STATUS },
1049         { 'c',          REQ_VIEW_STAGE },
1051         /* View manipulation */
1052         { 'q',          REQ_VIEW_CLOSE },
1053         { KEY_TAB,      REQ_VIEW_NEXT },
1054         { KEY_RETURN,   REQ_ENTER },
1055         { KEY_UP,       REQ_PREVIOUS },
1056         { KEY_DOWN,     REQ_NEXT },
1057         { 'R',          REQ_REFRESH },
1058         { KEY_F(5),     REQ_REFRESH },
1059         { 'O',          REQ_MAXIMIZE },
1061         /* Cursor navigation */
1062         { 'k',          REQ_MOVE_UP },
1063         { 'j',          REQ_MOVE_DOWN },
1064         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1065         { KEY_END,      REQ_MOVE_LAST_LINE },
1066         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1067         { ' ',          REQ_MOVE_PAGE_DOWN },
1068         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1069         { 'b',          REQ_MOVE_PAGE_UP },
1070         { '-',          REQ_MOVE_PAGE_UP },
1072         /* Scrolling */
1073         { KEY_IC,       REQ_SCROLL_LINE_UP },
1074         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1075         { 'w',          REQ_SCROLL_PAGE_UP },
1076         { 's',          REQ_SCROLL_PAGE_DOWN },
1078         /* Searching */
1079         { '/',          REQ_SEARCH },
1080         { '?',          REQ_SEARCH_BACK },
1081         { 'n',          REQ_FIND_NEXT },
1082         { 'N',          REQ_FIND_PREV },
1084         /* Misc */
1085         { 'Q',          REQ_QUIT },
1086         { 'z',          REQ_STOP_LOADING },
1087         { 'v',          REQ_SHOW_VERSION },
1088         { 'r',          REQ_SCREEN_REDRAW },
1089         { '.',          REQ_TOGGLE_LINENO },
1090         { 'D',          REQ_TOGGLE_DATE },
1091         { 'A',          REQ_TOGGLE_AUTHOR },
1092         { 'g',          REQ_TOGGLE_REV_GRAPH },
1093         { 'F',          REQ_TOGGLE_REFS },
1094         { ':',          REQ_PROMPT },
1095         { 'u',          REQ_STATUS_UPDATE },
1096         { '!',          REQ_STATUS_REVERT },
1097         { 'M',          REQ_STATUS_MERGE },
1098         { '@',          REQ_STAGE_NEXT },
1099         { ',',          REQ_TREE_PARENT },
1100         { 'e',          REQ_EDIT },
1102         /* Using the ncurses SIGWINCH handler. */
1103         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1104 };
1106 #define KEYMAP_INFO \
1107         KEYMAP_(GENERIC), \
1108         KEYMAP_(MAIN), \
1109         KEYMAP_(DIFF), \
1110         KEYMAP_(LOG), \
1111         KEYMAP_(TREE), \
1112         KEYMAP_(BLOB), \
1113         KEYMAP_(BLAME), \
1114         KEYMAP_(PAGER), \
1115         KEYMAP_(HELP), \
1116         KEYMAP_(STATUS), \
1117         KEYMAP_(STAGE)
1119 enum keymap {
1120 #define KEYMAP_(name) KEYMAP_##name
1121         KEYMAP_INFO
1122 #undef  KEYMAP_
1123 };
1125 static struct int_map keymap_table[] = {
1126 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1127         KEYMAP_INFO
1128 #undef  KEYMAP_
1129 };
1131 #define set_keymap(map, name) \
1132         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1134 struct keybinding_table {
1135         struct keybinding *data;
1136         size_t size;
1137 };
1139 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1141 static void
1142 add_keybinding(enum keymap keymap, enum request request, int key)
1144         struct keybinding_table *table = &keybindings[keymap];
1146         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1147         if (!table->data)
1148                 die("Failed to allocate keybinding");
1149         table->data[table->size].alias = key;
1150         table->data[table->size++].request = request;
1153 /* Looks for a key binding first in the given map, then in the generic map, and
1154  * lastly in the default keybindings. */
1155 static enum request
1156 get_keybinding(enum keymap keymap, int key)
1158         size_t i;
1160         for (i = 0; i < keybindings[keymap].size; i++)
1161                 if (keybindings[keymap].data[i].alias == key)
1162                         return keybindings[keymap].data[i].request;
1164         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1165                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1166                         return keybindings[KEYMAP_GENERIC].data[i].request;
1168         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1169                 if (default_keybindings[i].alias == key)
1170                         return default_keybindings[i].request;
1172         return (enum request) key;
1176 struct key {
1177         const char *name;
1178         int value;
1179 };
1181 static struct key key_table[] = {
1182         { "Enter",      KEY_RETURN },
1183         { "Space",      ' ' },
1184         { "Backspace",  KEY_BACKSPACE },
1185         { "Tab",        KEY_TAB },
1186         { "Escape",     KEY_ESC },
1187         { "Left",       KEY_LEFT },
1188         { "Right",      KEY_RIGHT },
1189         { "Up",         KEY_UP },
1190         { "Down",       KEY_DOWN },
1191         { "Insert",     KEY_IC },
1192         { "Delete",     KEY_DC },
1193         { "Hash",       '#' },
1194         { "Home",       KEY_HOME },
1195         { "End",        KEY_END },
1196         { "PageUp",     KEY_PPAGE },
1197         { "PageDown",   KEY_NPAGE },
1198         { "F1",         KEY_F(1) },
1199         { "F2",         KEY_F(2) },
1200         { "F3",         KEY_F(3) },
1201         { "F4",         KEY_F(4) },
1202         { "F5",         KEY_F(5) },
1203         { "F6",         KEY_F(6) },
1204         { "F7",         KEY_F(7) },
1205         { "F8",         KEY_F(8) },
1206         { "F9",         KEY_F(9) },
1207         { "F10",        KEY_F(10) },
1208         { "F11",        KEY_F(11) },
1209         { "F12",        KEY_F(12) },
1210 };
1212 static int
1213 get_key_value(const char *name)
1215         int i;
1217         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1218                 if (!strcasecmp(key_table[i].name, name))
1219                         return key_table[i].value;
1221         if (strlen(name) == 1 && isprint(*name))
1222                 return (int) *name;
1224         return ERR;
1227 static const char *
1228 get_key_name(int key_value)
1230         static char key_char[] = "'X'";
1231         const char *seq = NULL;
1232         int key;
1234         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1235                 if (key_table[key].value == key_value)
1236                         seq = key_table[key].name;
1238         if (seq == NULL &&
1239             key_value < 127 &&
1240             isprint(key_value)) {
1241                 key_char[1] = (char) key_value;
1242                 seq = key_char;
1243         }
1245         return seq ? seq : "(no key)";
1248 static const char *
1249 get_key(enum request request)
1251         static char buf[BUFSIZ];
1252         size_t pos = 0;
1253         char *sep = "";
1254         int i;
1256         buf[pos] = 0;
1258         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1259                 struct keybinding *keybinding = &default_keybindings[i];
1261                 if (keybinding->request != request)
1262                         continue;
1264                 if (!string_format_from(buf, &pos, "%s%s", sep,
1265                                         get_key_name(keybinding->alias)))
1266                         return "Too many keybindings!";
1267                 sep = ", ";
1268         }
1270         return buf;
1273 struct run_request {
1274         enum keymap keymap;
1275         int key;
1276         const char *argv[SIZEOF_ARG];
1277 };
1279 static struct run_request *run_request;
1280 static size_t run_requests;
1282 static enum request
1283 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1285         struct run_request *req;
1287         if (argc >= ARRAY_SIZE(req->argv) - 1)
1288                 return REQ_NONE;
1290         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1291         if (!req)
1292                 return REQ_NONE;
1294         run_request = req;
1295         req = &run_request[run_requests];
1296         req->keymap = keymap;
1297         req->key = key;
1298         req->argv[0] = NULL;
1300         if (!format_argv(req->argv, argv, FORMAT_NONE))
1301                 return REQ_NONE;
1303         return REQ_NONE + ++run_requests;
1306 static struct run_request *
1307 get_run_request(enum request request)
1309         if (request <= REQ_NONE)
1310                 return NULL;
1311         return &run_request[request - REQ_NONE - 1];
1314 static void
1315 add_builtin_run_requests(void)
1317         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1318         const char *gc[] = { "git", "gc", NULL };
1319         struct {
1320                 enum keymap keymap;
1321                 int key;
1322                 int argc;
1323                 const char **argv;
1324         } reqs[] = {
1325                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1326                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1327         };
1328         int i;
1330         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1331                 enum request req;
1333                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1334                 if (req != REQ_NONE)
1335                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1336         }
1339 /*
1340  * User config file handling.
1341  */
1343 static struct int_map color_map[] = {
1344 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1345         COLOR_MAP(DEFAULT),
1346         COLOR_MAP(BLACK),
1347         COLOR_MAP(BLUE),
1348         COLOR_MAP(CYAN),
1349         COLOR_MAP(GREEN),
1350         COLOR_MAP(MAGENTA),
1351         COLOR_MAP(RED),
1352         COLOR_MAP(WHITE),
1353         COLOR_MAP(YELLOW),
1354 };
1356 #define set_color(color, name) \
1357         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1359 static struct int_map attr_map[] = {
1360 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1361         ATTR_MAP(NORMAL),
1362         ATTR_MAP(BLINK),
1363         ATTR_MAP(BOLD),
1364         ATTR_MAP(DIM),
1365         ATTR_MAP(REVERSE),
1366         ATTR_MAP(STANDOUT),
1367         ATTR_MAP(UNDERLINE),
1368 };
1370 #define set_attribute(attr, name) \
1371         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1373 static int   config_lineno;
1374 static bool  config_errors;
1375 static const char *config_msg;
1377 /* Wants: object fgcolor bgcolor [attr] */
1378 static int
1379 option_color_command(int argc, const char *argv[])
1381         struct line_info *info;
1383         if (argc != 3 && argc != 4) {
1384                 config_msg = "Wrong number of arguments given to color command";
1385                 return ERR;
1386         }
1388         info = get_line_info(argv[0]);
1389         if (!info) {
1390                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1391                         info = get_line_info("delimiter");
1393                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1394                         info = get_line_info("date");
1396                 } else {
1397                         config_msg = "Unknown color name";
1398                         return ERR;
1399                 }
1400         }
1402         if (set_color(&info->fg, argv[1]) == ERR ||
1403             set_color(&info->bg, argv[2]) == ERR) {
1404                 config_msg = "Unknown color";
1405                 return ERR;
1406         }
1408         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1409                 config_msg = "Unknown attribute";
1410                 return ERR;
1411         }
1413         return OK;
1416 static bool parse_bool(const char *s)
1418         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1419                 !strcmp(s, "yes")) ? TRUE : FALSE;
1422 static int
1423 parse_int(const char *s, int default_value, int min, int max)
1425         int value = atoi(s);
1427         return (value < min || value > max) ? default_value : value;
1430 /* Wants: name = value */
1431 static int
1432 option_set_command(int argc, const char *argv[])
1434         if (argc != 3) {
1435                 config_msg = "Wrong number of arguments given to set command";
1436                 return ERR;
1437         }
1439         if (strcmp(argv[1], "=")) {
1440                 config_msg = "No value assigned";
1441                 return ERR;
1442         }
1444         if (!strcmp(argv[0], "show-author")) {
1445                 opt_author = parse_bool(argv[2]);
1446                 return OK;
1447         }
1449         if (!strcmp(argv[0], "show-date")) {
1450                 opt_date = parse_bool(argv[2]);
1451                 return OK;
1452         }
1454         if (!strcmp(argv[0], "show-rev-graph")) {
1455                 opt_rev_graph = parse_bool(argv[2]);
1456                 return OK;
1457         }
1459         if (!strcmp(argv[0], "show-refs")) {
1460                 opt_show_refs = parse_bool(argv[2]);
1461                 return OK;
1462         }
1464         if (!strcmp(argv[0], "show-line-numbers")) {
1465                 opt_line_number = parse_bool(argv[2]);
1466                 return OK;
1467         }
1469         if (!strcmp(argv[0], "line-graphics")) {
1470                 opt_line_graphics = parse_bool(argv[2]);
1471                 return OK;
1472         }
1474         if (!strcmp(argv[0], "line-number-interval")) {
1475                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1476                 return OK;
1477         }
1479         if (!strcmp(argv[0], "author-width")) {
1480                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1481                 return OK;
1482         }
1484         if (!strcmp(argv[0], "tab-size")) {
1485                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1486                 return OK;
1487         }
1489         if (!strcmp(argv[0], "commit-encoding")) {
1490                 const char *arg = argv[2];
1491                 int arglen = strlen(arg);
1493                 switch (arg[0]) {
1494                 case '"':
1495                 case '\'':
1496                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1497                                 config_msg = "Unmatched quotation";
1498                                 return ERR;
1499                         }
1500                         arg += 1; arglen -= 2;
1501                 default:
1502                         string_ncopy(opt_encoding, arg, strlen(arg));
1503                         return OK;
1504                 }
1505         }
1507         config_msg = "Unknown variable name";
1508         return ERR;
1511 /* Wants: mode request key */
1512 static int
1513 option_bind_command(int argc, const char *argv[])
1515         enum request request;
1516         int keymap;
1517         int key;
1519         if (argc < 3) {
1520                 config_msg = "Wrong number of arguments given to bind command";
1521                 return ERR;
1522         }
1524         if (set_keymap(&keymap, argv[0]) == ERR) {
1525                 config_msg = "Unknown key map";
1526                 return ERR;
1527         }
1529         key = get_key_value(argv[1]);
1530         if (key == ERR) {
1531                 config_msg = "Unknown key";
1532                 return ERR;
1533         }
1535         request = get_request(argv[2]);
1536         if (request == REQ_NONE) {
1537                 const char *obsolete[] = { "cherry-pick" };
1538                 size_t namelen = strlen(argv[2]);
1539                 int i;
1541                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1542                         if (namelen == strlen(obsolete[i]) &&
1543                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1544                                 config_msg = "Obsolete request name";
1545                                 return ERR;
1546                         }
1547                 }
1548         }
1549         if (request == REQ_NONE && *argv[2]++ == '!')
1550                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1551         if (request == REQ_NONE) {
1552                 config_msg = "Unknown request name";
1553                 return ERR;
1554         }
1556         add_keybinding(keymap, request, key);
1558         return OK;
1561 static int
1562 set_option(const char *opt, char *value)
1564         const char *argv[SIZEOF_ARG];
1565         int argc = 0;
1567         if (!argv_from_string(argv, &argc, value)) {
1568                 config_msg = "Too many option arguments";
1569                 return ERR;
1570         }
1572         if (!strcmp(opt, "color"))
1573                 return option_color_command(argc, argv);
1575         if (!strcmp(opt, "set"))
1576                 return option_set_command(argc, argv);
1578         if (!strcmp(opt, "bind"))
1579                 return option_bind_command(argc, argv);
1581         config_msg = "Unknown option command";
1582         return ERR;
1585 static int
1586 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1588         int status = OK;
1590         config_lineno++;
1591         config_msg = "Internal error";
1593         /* Check for comment markers, since read_properties() will
1594          * only ensure opt and value are split at first " \t". */
1595         optlen = strcspn(opt, "#");
1596         if (optlen == 0)
1597                 return OK;
1599         if (opt[optlen] != 0) {
1600                 config_msg = "No option value";
1601                 status = ERR;
1603         }  else {
1604                 /* Look for comment endings in the value. */
1605                 size_t len = strcspn(value, "#");
1607                 if (len < valuelen) {
1608                         valuelen = len;
1609                         value[valuelen] = 0;
1610                 }
1612                 status = set_option(opt, value);
1613         }
1615         if (status == ERR) {
1616                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1617                         config_lineno, (int) optlen, opt, config_msg);
1618                 config_errors = TRUE;
1619         }
1621         /* Always keep going if errors are encountered. */
1622         return OK;
1625 static void
1626 load_option_file(const char *path)
1628         struct io io = {};
1630         /* It's ok that the file doesn't exist. */
1631         if (!io_open(&io, path))
1632                 return;
1634         config_lineno = 0;
1635         config_errors = FALSE;
1637         if (read_properties(&io, " \t", read_option) == ERR ||
1638             config_errors == TRUE)
1639                 fprintf(stderr, "Errors while loading %s.\n", path);
1642 static int
1643 load_options(void)
1645         const char *home = getenv("HOME");
1646         const char *tigrc_user = getenv("TIGRC_USER");
1647         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1648         char buf[SIZEOF_STR];
1650         add_builtin_run_requests();
1652         if (!tigrc_system) {
1653                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1654                         return ERR;
1655                 tigrc_system = buf;
1656         }
1657         load_option_file(tigrc_system);
1659         if (!tigrc_user) {
1660                 if (!home || !string_format(buf, "%s/.tigrc", home))
1661                         return ERR;
1662                 tigrc_user = buf;
1663         }
1664         load_option_file(tigrc_user);
1666         return OK;
1670 /*
1671  * The viewer
1672  */
1674 struct view;
1675 struct view_ops;
1677 /* The display array of active views and the index of the current view. */
1678 static struct view *display[2];
1679 static unsigned int current_view;
1681 /* Reading from the prompt? */
1682 static bool input_mode = FALSE;
1684 #define foreach_displayed_view(view, i) \
1685         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1687 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1689 /* Current head and commit ID */
1690 static char ref_blob[SIZEOF_REF]        = "";
1691 static char ref_commit[SIZEOF_REF]      = "HEAD";
1692 static char ref_head[SIZEOF_REF]        = "HEAD";
1694 struct view {
1695         const char *name;       /* View name */
1696         const char *cmd_env;    /* Command line set via environment */
1697         const char *id;         /* Points to either of ref_{head,commit,blob} */
1699         struct view_ops *ops;   /* View operations */
1701         enum keymap keymap;     /* What keymap does this view have */
1702         bool git_dir;           /* Whether the view requires a git directory. */
1704         char ref[SIZEOF_REF];   /* Hovered commit reference */
1705         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1707         int height, width;      /* The width and height of the main window */
1708         WINDOW *win;            /* The main window */
1709         WINDOW *title;          /* The title window living below the main window */
1711         /* Navigation */
1712         unsigned long offset;   /* Offset of the window top */
1713         unsigned long lineno;   /* Current line number */
1715         /* Searching */
1716         char grep[SIZEOF_STR];  /* Search string */
1717         regex_t *regex;         /* Pre-compiled regex */
1719         /* If non-NULL, points to the view that opened this view. If this view
1720          * is closed tig will switch back to the parent view. */
1721         struct view *parent;
1723         /* Buffering */
1724         size_t lines;           /* Total number of lines */
1725         struct line *line;      /* Line index */
1726         size_t line_alloc;      /* Total number of allocated lines */
1727         size_t line_size;       /* Total number of used 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 };
1741 struct view_ops {
1742         /* What type of content being displayed. Used in the title bar. */
1743         const char *type;
1744         /* Default command arguments. */
1745         const char **argv;
1746         /* Open and reads in all view content. */
1747         bool (*open)(struct view *view);
1748         /* Read one line; updates view->line. */
1749         bool (*read)(struct view *view, char *data);
1750         /* Draw one line; @lineno must be < view->height. */
1751         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1752         /* Depending on view handle a special requests. */
1753         enum request (*request)(struct view *view, enum request request, struct line *line);
1754         /* Search for regex in a line. */
1755         bool (*grep)(struct view *view, struct line *line);
1756         /* Select line */
1757         void (*select)(struct view *view, struct line *line);
1758 };
1760 static struct view_ops blame_ops;
1761 static struct view_ops blob_ops;
1762 static struct view_ops diff_ops;
1763 static struct view_ops help_ops;
1764 static struct view_ops log_ops;
1765 static struct view_ops main_ops;
1766 static struct view_ops pager_ops;
1767 static struct view_ops stage_ops;
1768 static struct view_ops status_ops;
1769 static struct view_ops tree_ops;
1771 #define VIEW_STR(name, env, ref, ops, map, git) \
1772         { name, #env, ref, ops, map, git }
1774 #define VIEW_(id, name, ops, git, ref) \
1775         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1778 static struct view views[] = {
1779         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1780         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1781         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1782         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1783         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1784         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1785         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1786         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1787         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1788         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1789 };
1791 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1792 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1794 #define foreach_view(view, i) \
1795         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1797 #define view_is_displayed(view) \
1798         (view == display[0] || view == display[1])
1801 enum line_graphic {
1802         LINE_GRAPHIC_VLINE
1803 };
1805 static int line_graphics[] = {
1806         /* LINE_GRAPHIC_VLINE: */ '|'
1807 };
1809 static inline void
1810 set_view_attr(struct view *view, enum line_type type)
1812         if (!view->curline->selected && view->curtype != type) {
1813                 wattrset(view->win, get_line_attr(type));
1814                 wchgat(view->win, -1, 0, type, NULL);
1815                 view->curtype = type;
1816         }
1819 static int
1820 draw_chars(struct view *view, enum line_type type, const char *string,
1821            int max_len, bool use_tilde)
1823         int len = 0;
1824         int col = 0;
1825         int trimmed = FALSE;
1827         if (max_len <= 0)
1828                 return 0;
1830         if (opt_utf8) {
1831                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1832         } else {
1833                 col = len = strlen(string);
1834                 if (len > max_len) {
1835                         if (use_tilde) {
1836                                 max_len -= 1;
1837                         }
1838                         col = len = max_len;
1839                         trimmed = TRUE;
1840                 }
1841         }
1843         set_view_attr(view, type);
1844         waddnstr(view->win, string, len);
1845         if (trimmed && use_tilde) {
1846                 set_view_attr(view, LINE_DELIMITER);
1847                 waddch(view->win, '~');
1848                 col++;
1849         }
1851         return col;
1854 static int
1855 draw_space(struct view *view, enum line_type type, int max, int spaces)
1857         static char space[] = "                    ";
1858         int col = 0;
1860         spaces = MIN(max, spaces);
1862         while (spaces > 0) {
1863                 int len = MIN(spaces, sizeof(space) - 1);
1865                 col += draw_chars(view, type, space, spaces, FALSE);
1866                 spaces -= len;
1867         }
1869         return col;
1872 static bool
1873 draw_lineno(struct view *view, unsigned int lineno)
1875         char number[10];
1876         int digits3 = view->digits < 3 ? 3 : view->digits;
1877         int max_number = MIN(digits3, STRING_SIZE(number));
1878         int max = view->width - view->col;
1879         int col;
1881         if (max < max_number)
1882                 max_number = max;
1884         lineno += view->offset + 1;
1885         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1886                 static char fmt[] = "%1ld";
1888                 if (view->digits <= 9)
1889                         fmt[1] = '0' + digits3;
1891                 if (!string_format(number, fmt, lineno))
1892                         number[0] = 0;
1893                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1894         } else {
1895                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1896         }
1898         if (col < max) {
1899                 set_view_attr(view, LINE_DEFAULT);
1900                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1901                 col++;
1902         }
1904         if (col < max)
1905                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1906         view->col += col;
1908         return view->width - view->col <= 0;
1911 static bool
1912 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1914         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1915         return view->width - view->col <= 0;
1918 static bool
1919 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1921         int max = view->width - view->col;
1922         int i;
1924         if (max < size)
1925                 size = max;
1927         set_view_attr(view, type);
1928         /* Using waddch() instead of waddnstr() ensures that
1929          * they'll be rendered correctly for the cursor line. */
1930         for (i = 0; i < size; i++)
1931                 waddch(view->win, graphic[i]);
1933         view->col += size;
1934         if (size < max) {
1935                 waddch(view->win, ' ');
1936                 view->col++;
1937         }
1939         return view->width - view->col <= 0;
1942 static bool
1943 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1945         int max = MIN(view->width - view->col, len);
1946         int col;
1948         if (text)
1949                 col = draw_chars(view, type, text, max - 1, trim);
1950         else
1951                 col = draw_space(view, type, max - 1, max - 1);
1953         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1954         return view->width - view->col <= 0;
1957 static bool
1958 draw_date(struct view *view, struct tm *time)
1960         char buf[DATE_COLS];
1961         char *date;
1962         int timelen = 0;
1964         if (time)
1965                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1966         date = timelen ? buf : NULL;
1968         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1971 static bool
1972 draw_view_line(struct view *view, unsigned int lineno)
1974         struct line *line;
1975         bool selected = (view->offset + lineno == view->lineno);
1976         bool draw_ok;
1978         assert(view_is_displayed(view));
1980         if (view->offset + lineno >= view->lines)
1981                 return FALSE;
1983         line = &view->line[view->offset + lineno];
1985         wmove(view->win, lineno, 0);
1986         view->col = 0;
1987         view->curline = line;
1988         view->curtype = LINE_NONE;
1989         line->selected = FALSE;
1991         if (selected) {
1992                 set_view_attr(view, LINE_CURSOR);
1993                 line->selected = TRUE;
1994                 view->ops->select(view, line);
1995         } else if (line->selected) {
1996                 wclrtoeol(view->win);
1997         }
1999         scrollok(view->win, FALSE);
2000         draw_ok = view->ops->draw(view, line, lineno);
2001         scrollok(view->win, TRUE);
2003         return draw_ok;
2006 static void
2007 redraw_view_dirty(struct view *view)
2009         bool dirty = FALSE;
2010         int lineno;
2012         for (lineno = 0; lineno < view->height; lineno++) {
2013                 struct line *line = &view->line[view->offset + lineno];
2015                 if (!line->dirty)
2016                         continue;
2017                 line->dirty = 0;
2018                 dirty = TRUE;
2019                 if (!draw_view_line(view, lineno))
2020                         break;
2021         }
2023         if (!dirty)
2024                 return;
2025         redrawwin(view->win);
2026         if (input_mode)
2027                 wnoutrefresh(view->win);
2028         else
2029                 wrefresh(view->win);
2032 static void
2033 redraw_view_from(struct view *view, int lineno)
2035         assert(0 <= lineno && lineno < view->height);
2037         for (; lineno < view->height; lineno++) {
2038                 if (!draw_view_line(view, lineno))
2039                         break;
2040         }
2042         redrawwin(view->win);
2043         if (input_mode)
2044                 wnoutrefresh(view->win);
2045         else
2046                 wrefresh(view->win);
2049 static void
2050 redraw_view(struct view *view)
2052         wclear(view->win);
2053         redraw_view_from(view, 0);
2057 static void
2058 update_view_title(struct view *view)
2060         char buf[SIZEOF_STR];
2061         char state[SIZEOF_STR];
2062         size_t bufpos = 0, statelen = 0;
2064         assert(view_is_displayed(view));
2066         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
2067                 unsigned int view_lines = view->offset + view->height;
2068                 unsigned int lines = view->lines
2069                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2070                                    : 0;
2072                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2073                                    view->ops->type,
2074                                    view->lineno + 1,
2075                                    view->lines,
2076                                    lines);
2078                 if (view->pipe) {
2079                         time_t secs = time(NULL) - view->start_time;
2081                         /* Three git seconds are a long time ... */
2082                         if (secs > 2)
2083                                 string_format_from(state, &statelen, " %lds", secs);
2084                 }
2085         }
2087         string_format_from(buf, &bufpos, "[%s]", view->name);
2088         if (*view->ref && bufpos < view->width) {
2089                 size_t refsize = strlen(view->ref);
2090                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2092                 if (minsize < view->width)
2093                         refsize = view->width - minsize + 7;
2094                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2095         }
2097         if (statelen && bufpos < view->width) {
2098                 string_format_from(buf, &bufpos, " %s", state);
2099         }
2101         if (view == display[current_view])
2102                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2103         else
2104                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2106         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2107         wclrtoeol(view->title);
2108         wmove(view->title, 0, view->width - 1);
2110         if (input_mode)
2111                 wnoutrefresh(view->title);
2112         else
2113                 wrefresh(view->title);
2116 static void
2117 resize_display(void)
2119         int offset, i;
2120         struct view *base = display[0];
2121         struct view *view = display[1] ? display[1] : display[0];
2123         /* Setup window dimensions */
2125         getmaxyx(stdscr, base->height, base->width);
2127         /* Make room for the status window. */
2128         base->height -= 1;
2130         if (view != base) {
2131                 /* Horizontal split. */
2132                 view->width   = base->width;
2133                 view->height  = SCALE_SPLIT_VIEW(base->height);
2134                 base->height -= view->height;
2136                 /* Make room for the title bar. */
2137                 view->height -= 1;
2138         }
2140         /* Make room for the title bar. */
2141         base->height -= 1;
2143         offset = 0;
2145         foreach_displayed_view (view, i) {
2146                 if (!view->win) {
2147                         view->win = newwin(view->height, 0, offset, 0);
2148                         if (!view->win)
2149                                 die("Failed to create %s view", view->name);
2151                         scrollok(view->win, TRUE);
2153                         view->title = newwin(1, 0, offset + view->height, 0);
2154                         if (!view->title)
2155                                 die("Failed to create title window");
2157                 } else {
2158                         wresize(view->win, view->height, view->width);
2159                         mvwin(view->win,   offset, 0);
2160                         mvwin(view->title, offset + view->height, 0);
2161                 }
2163                 offset += view->height + 1;
2164         }
2167 static void
2168 redraw_display(void)
2170         struct view *view;
2171         int i;
2173         foreach_displayed_view (view, i) {
2174                 redraw_view(view);
2175                 update_view_title(view);
2176         }
2179 static void
2180 update_display_cursor(struct view *view)
2182         /* Move the cursor to the right-most column of the cursor line.
2183          *
2184          * XXX: This could turn out to be a bit expensive, but it ensures that
2185          * the cursor does not jump around. */
2186         if (view->lines) {
2187                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2188                 wrefresh(view->win);
2189         }
2192 /*
2193  * Navigation
2194  */
2196 /* Scrolling backend */
2197 static void
2198 do_scroll_view(struct view *view, int lines)
2200         bool redraw_current_line = FALSE;
2202         /* The rendering expects the new offset. */
2203         view->offset += lines;
2205         assert(0 <= view->offset && view->offset < view->lines);
2206         assert(lines);
2208         /* Move current line into the view. */
2209         if (view->lineno < view->offset) {
2210                 view->lineno = view->offset;
2211                 redraw_current_line = TRUE;
2212         } else if (view->lineno >= view->offset + view->height) {
2213                 view->lineno = view->offset + view->height - 1;
2214                 redraw_current_line = TRUE;
2215         }
2217         assert(view->offset <= view->lineno && view->lineno < view->lines);
2219         /* Redraw the whole screen if scrolling is pointless. */
2220         if (view->height < ABS(lines)) {
2221                 redraw_view(view);
2223         } else {
2224                 int line = lines > 0 ? view->height - lines : 0;
2225                 int end = line + ABS(lines);
2227                 wscrl(view->win, lines);
2229                 for (; line < end; line++) {
2230                         if (!draw_view_line(view, line))
2231                                 break;
2232                 }
2234                 if (redraw_current_line)
2235                         draw_view_line(view, view->lineno - view->offset);
2236         }
2238         redrawwin(view->win);
2239         wrefresh(view->win);
2240         report("");
2243 /* Scroll frontend */
2244 static void
2245 scroll_view(struct view *view, enum request request)
2247         int lines = 1;
2249         assert(view_is_displayed(view));
2251         switch (request) {
2252         case REQ_SCROLL_PAGE_DOWN:
2253                 lines = view->height;
2254         case REQ_SCROLL_LINE_DOWN:
2255                 if (view->offset + lines > view->lines)
2256                         lines = view->lines - view->offset;
2258                 if (lines == 0 || view->offset + view->height >= view->lines) {
2259                         report("Cannot scroll beyond the last line");
2260                         return;
2261                 }
2262                 break;
2264         case REQ_SCROLL_PAGE_UP:
2265                 lines = view->height;
2266         case REQ_SCROLL_LINE_UP:
2267                 if (lines > view->offset)
2268                         lines = view->offset;
2270                 if (lines == 0) {
2271                         report("Cannot scroll beyond the first line");
2272                         return;
2273                 }
2275                 lines = -lines;
2276                 break;
2278         default:
2279                 die("request %d not handled in switch", request);
2280         }
2282         do_scroll_view(view, lines);
2285 /* Cursor moving */
2286 static void
2287 move_view(struct view *view, enum request request)
2289         int scroll_steps = 0;
2290         int steps;
2292         switch (request) {
2293         case REQ_MOVE_FIRST_LINE:
2294                 steps = -view->lineno;
2295                 break;
2297         case REQ_MOVE_LAST_LINE:
2298                 steps = view->lines - view->lineno - 1;
2299                 break;
2301         case REQ_MOVE_PAGE_UP:
2302                 steps = view->height > view->lineno
2303                       ? -view->lineno : -view->height;
2304                 break;
2306         case REQ_MOVE_PAGE_DOWN:
2307                 steps = view->lineno + view->height >= view->lines
2308                       ? view->lines - view->lineno - 1 : view->height;
2309                 break;
2311         case REQ_MOVE_UP:
2312                 steps = -1;
2313                 break;
2315         case REQ_MOVE_DOWN:
2316                 steps = 1;
2317                 break;
2319         default:
2320                 die("request %d not handled in switch", request);
2321         }
2323         if (steps <= 0 && view->lineno == 0) {
2324                 report("Cannot move beyond the first line");
2325                 return;
2327         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2328                 report("Cannot move beyond the last line");
2329                 return;
2330         }
2332         /* Move the current line */
2333         view->lineno += steps;
2334         assert(0 <= view->lineno && view->lineno < view->lines);
2336         /* Check whether the view needs to be scrolled */
2337         if (view->lineno < view->offset ||
2338             view->lineno >= view->offset + view->height) {
2339                 scroll_steps = steps;
2340                 if (steps < 0 && -steps > view->offset) {
2341                         scroll_steps = -view->offset;
2343                 } else if (steps > 0) {
2344                         if (view->lineno == view->lines - 1 &&
2345                             view->lines > view->height) {
2346                                 scroll_steps = view->lines - view->offset - 1;
2347                                 if (scroll_steps >= view->height)
2348                                         scroll_steps -= view->height - 1;
2349                         }
2350                 }
2351         }
2353         if (!view_is_displayed(view)) {
2354                 view->offset += scroll_steps;
2355                 assert(0 <= view->offset && view->offset < view->lines);
2356                 view->ops->select(view, &view->line[view->lineno]);
2357                 return;
2358         }
2360         /* Repaint the old "current" line if we be scrolling */
2361         if (ABS(steps) < view->height)
2362                 draw_view_line(view, view->lineno - steps - view->offset);
2364         if (scroll_steps) {
2365                 do_scroll_view(view, scroll_steps);
2366                 return;
2367         }
2369         /* Draw the current line */
2370         draw_view_line(view, view->lineno - view->offset);
2372         redrawwin(view->win);
2373         wrefresh(view->win);
2374         report("");
2378 /*
2379  * Searching
2380  */
2382 static void search_view(struct view *view, enum request request);
2384 static bool
2385 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2387         assert(view_is_displayed(view));
2389         if (!view->ops->grep(view, line))
2390                 return FALSE;
2392         if (lineno - view->offset >= view->height) {
2393                 view->offset = lineno;
2394                 view->lineno = lineno;
2395                 redraw_view(view);
2397         } else {
2398                 unsigned long old_lineno = view->lineno - view->offset;
2400                 view->lineno = lineno;
2401                 draw_view_line(view, old_lineno);
2403                 draw_view_line(view, view->lineno - view->offset);
2404                 redrawwin(view->win);
2405                 wrefresh(view->win);
2406         }
2408         report("Line %ld matches '%s'", lineno + 1, view->grep);
2409         return TRUE;
2412 static void
2413 find_next(struct view *view, enum request request)
2415         unsigned long lineno = view->lineno;
2416         int direction;
2418         if (!*view->grep) {
2419                 if (!*opt_search)
2420                         report("No previous search");
2421                 else
2422                         search_view(view, request);
2423                 return;
2424         }
2426         switch (request) {
2427         case REQ_SEARCH:
2428         case REQ_FIND_NEXT:
2429                 direction = 1;
2430                 break;
2432         case REQ_SEARCH_BACK:
2433         case REQ_FIND_PREV:
2434                 direction = -1;
2435                 break;
2437         default:
2438                 return;
2439         }
2441         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2442                 lineno += direction;
2444         /* Note, lineno is unsigned long so will wrap around in which case it
2445          * will become bigger than view->lines. */
2446         for (; lineno < view->lines; lineno += direction) {
2447                 struct line *line = &view->line[lineno];
2449                 if (find_next_line(view, lineno, line))
2450                         return;
2451         }
2453         report("No match found for '%s'", view->grep);
2456 static void
2457 search_view(struct view *view, enum request request)
2459         int regex_err;
2461         if (view->regex) {
2462                 regfree(view->regex);
2463                 *view->grep = 0;
2464         } else {
2465                 view->regex = calloc(1, sizeof(*view->regex));
2466                 if (!view->regex)
2467                         return;
2468         }
2470         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2471         if (regex_err != 0) {
2472                 char buf[SIZEOF_STR] = "unknown error";
2474                 regerror(regex_err, view->regex, buf, sizeof(buf));
2475                 report("Search failed: %s", buf);
2476                 return;
2477         }
2479         string_copy(view->grep, opt_search);
2481         find_next(view, request);
2484 /*
2485  * Incremental updating
2486  */
2488 static void
2489 reset_view(struct view *view)
2491         int i;
2493         for (i = 0; i < view->lines; i++)
2494                 free(view->line[i].data);
2495         free(view->line);
2497         view->line = NULL;
2498         view->offset = 0;
2499         view->lines  = 0;
2500         view->lineno = 0;
2501         view->line_size = 0;
2502         view->line_alloc = 0;
2503         view->vid[0] = 0;
2506 static void
2507 free_argv(const char *argv[])
2509         int argc;
2511         for (argc = 0; argv[argc]; argc++)
2512                 free((void *) argv[argc]);
2515 static bool
2516 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2518         char buf[SIZEOF_STR];
2519         int argc;
2520         bool noreplace = flags == FORMAT_NONE;
2522         free_argv(dst_argv);
2524         for (argc = 0; src_argv[argc]; argc++) {
2525                 const char *arg = src_argv[argc];
2526                 size_t bufpos = 0;
2528                 while (arg) {
2529                         char *next = strstr(arg, "%(");
2530                         int len = next - arg;
2531                         const char *value;
2533                         if (!next || noreplace) {
2534                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2535                                         noreplace = TRUE;
2536                                 len = strlen(arg);
2537                                 value = "";
2539                         } else if (!prefixcmp(next, "%(directory)")) {
2540                                 value = opt_path;
2542                         } else if (!prefixcmp(next, "%(file)")) {
2543                                 value = opt_file;
2545                         } else if (!prefixcmp(next, "%(ref)")) {
2546                                 value = *opt_ref ? opt_ref : "HEAD";
2548                         } else if (!prefixcmp(next, "%(head)")) {
2549                                 value = ref_head;
2551                         } else if (!prefixcmp(next, "%(commit)")) {
2552                                 value = ref_commit;
2554                         } else if (!prefixcmp(next, "%(blob)")) {
2555                                 value = ref_blob;
2557                         } else {
2558                                 report("Unknown replacement: `%s`", next);
2559                                 return FALSE;
2560                         }
2562                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2563                                 return FALSE;
2565                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2566                 }
2568                 dst_argv[argc] = strdup(buf);
2569                 if (!dst_argv[argc])
2570                         break;
2571         }
2573         dst_argv[argc] = NULL;
2575         return src_argv[argc] == NULL;
2578 static void
2579 end_update(struct view *view, bool force)
2581         if (!view->pipe)
2582                 return;
2583         while (!view->ops->read(view, NULL))
2584                 if (!force)
2585                         return;
2586         set_nonblocking_input(FALSE);
2587         if (force)
2588                 kill_io(view->pipe);
2589         done_io(view->pipe);
2590         view->pipe = NULL;
2593 static void
2594 setup_update(struct view *view, const char *vid)
2596         set_nonblocking_input(TRUE);
2597         reset_view(view);
2598         string_copy_rev(view->vid, vid);
2599         view->pipe = &view->io;
2600         view->start_time = time(NULL);
2603 static bool
2604 prepare_update(struct view *view, const char *argv[], const char *dir,
2605                enum format_flags flags)
2607         if (view->pipe)
2608                 end_update(view, TRUE);
2609         return init_io_rd(&view->io, argv, dir, flags);
2612 static bool
2613 prepare_update_file(struct view *view, const char *name)
2615         if (view->pipe)
2616                 end_update(view, TRUE);
2617         return io_open(&view->io, name);
2620 static bool
2621 begin_update(struct view *view, bool refresh)
2623         if (refresh) {
2624                 if (!start_io(&view->io))
2625                         return FALSE;
2627         } else {
2628                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2629                         opt_path[0] = 0;
2631                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2632                         return FALSE;
2634                 /* Put the current ref_* value to the view title ref
2635                  * member. This is needed by the blob view. Most other
2636                  * views sets it automatically after loading because the
2637                  * first line is a commit line. */
2638                 string_copy_rev(view->ref, view->id);
2639         }
2641         setup_update(view, view->id);
2643         return TRUE;
2646 #define ITEM_CHUNK_SIZE 256
2647 static void *
2648 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2650         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2651         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2653         if (mem == NULL || num_chunks != num_chunks_new) {
2654                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2655                 mem = realloc(mem, *size * item_size);
2656         }
2658         return mem;
2661 static struct line *
2662 realloc_lines(struct view *view, size_t line_size)
2664         size_t alloc = view->line_alloc;
2665         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2666                                          sizeof(*view->line));
2668         if (!tmp)
2669                 return NULL;
2671         view->line = tmp;
2672         view->line_alloc = alloc;
2673         view->line_size = line_size;
2674         return view->line;
2677 static bool
2678 update_view(struct view *view)
2680         char out_buffer[BUFSIZ * 2];
2681         char *line;
2682         int redraw_from = -1;
2683         bool can_read = TRUE;
2685         if (!view->pipe)
2686                 return TRUE;
2688         if (!io_can_read(view->pipe))
2689                 return TRUE;
2691         /* Only redraw if lines are visible. */
2692         if (view->offset + view->height >= view->lines)
2693                 redraw_from = view->lines - view->offset;
2695         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2696                 size_t linelen = strlen(line);
2698                 if (opt_iconv != ICONV_NONE) {
2699                         ICONV_CONST char *inbuf = line;
2700                         size_t inlen = linelen;
2702                         char *outbuf = out_buffer;
2703                         size_t outlen = sizeof(out_buffer);
2705                         size_t ret;
2707                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2708                         if (ret != (size_t) -1) {
2709                                 line = out_buffer;
2710                                 linelen = strlen(out_buffer);
2711                         }
2712                 }
2714                 if (!realloc_lines(view, view->lines + 1) ||
2715                     !view->ops->read(view, line))
2716                         goto alloc_error;
2717         }
2719         {
2720                 unsigned long lines = view->lines;
2721                 int digits;
2723                 for (digits = 0; lines; digits++)
2724                         lines /= 10;
2726                 /* Keep the displayed view in sync with line number scaling. */
2727                 if (digits != view->digits) {
2728                         view->digits = digits;
2729                         redraw_from = 0;
2730                 }
2731         }
2733         if (io_error(view->pipe)) {
2734                 report("Failed to read: %s", io_strerror(view->pipe));
2735                 end_update(view, TRUE);
2737         } else if (io_eof(view->pipe)) {
2738                 report("");
2739                 end_update(view, FALSE);
2740         }
2742         if (!view_is_displayed(view))
2743                 return TRUE;
2745         if (view == VIEW(REQ_VIEW_TREE)) {
2746                 /* Clear the view and redraw everything since the tree sorting
2747                  * might have rearranged things. */
2748                 redraw_view(view);
2750         } else if (redraw_from >= 0) {
2751                 /* If this is an incremental update, redraw the previous line
2752                  * since for commits some members could have changed when
2753                  * loading the main view. */
2754                 if (redraw_from > 0)
2755                         redraw_from--;
2757                 /* Since revision graph visualization requires knowledge
2758                  * about the parent commit, it causes a further one-off
2759                  * needed to be redrawn for incremental updates. */
2760                 if (redraw_from > 0 && opt_rev_graph)
2761                         redraw_from--;
2763                 /* Incrementally draw avoids flickering. */
2764                 redraw_view_from(view, redraw_from);
2765         }
2767         if (view == VIEW(REQ_VIEW_BLAME))
2768                 redraw_view_dirty(view);
2770         /* Update the title _after_ the redraw so that if the redraw picks up a
2771          * commit reference in view->ref it'll be available here. */
2772         update_view_title(view);
2773         return TRUE;
2775 alloc_error:
2776         report("Allocation failure");
2777         end_update(view, TRUE);
2778         return FALSE;
2781 static struct line *
2782 add_line_data(struct view *view, void *data, enum line_type type)
2784         struct line *line = &view->line[view->lines++];
2786         memset(line, 0, sizeof(*line));
2787         line->type = type;
2788         line->data = data;
2790         return line;
2793 static struct line *
2794 add_line_text(struct view *view, const char *text, enum line_type type)
2796         char *data = text ? strdup(text) : NULL;
2798         return data ? add_line_data(view, data, type) : NULL;
2802 /*
2803  * View opening
2804  */
2806 enum open_flags {
2807         OPEN_DEFAULT = 0,       /* Use default view switching. */
2808         OPEN_SPLIT = 1,         /* Split current view. */
2809         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2810         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2811         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2812         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2813         OPEN_PREPARED = 32,     /* Open already prepared command. */
2814 };
2816 static void
2817 open_view(struct view *prev, enum request request, enum open_flags flags)
2819         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2820         bool split = !!(flags & OPEN_SPLIT);
2821         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2822         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2823         struct view *view = VIEW(request);
2824         int nviews = displayed_views();
2825         struct view *base_view = display[0];
2827         if (view == prev && nviews == 1 && !reload) {
2828                 report("Already in %s view", view->name);
2829                 return;
2830         }
2832         if (view->git_dir && !opt_git_dir[0]) {
2833                 report("The %s view is disabled in pager view", view->name);
2834                 return;
2835         }
2837         if (split) {
2838                 display[1] = view;
2839                 if (!backgrounded)
2840                         current_view = 1;
2841         } else if (!nomaximize) {
2842                 /* Maximize the current view. */
2843                 memset(display, 0, sizeof(display));
2844                 current_view = 0;
2845                 display[current_view] = view;
2846         }
2848         /* Resize the view when switching between split- and full-screen,
2849          * or when switching between two different full-screen views. */
2850         if (nviews != displayed_views() ||
2851             (nviews == 1 && base_view != display[0]))
2852                 resize_display();
2854         if (view->pipe)
2855                 end_update(view, TRUE);
2857         if (view->ops->open) {
2858                 if (!view->ops->open(view)) {
2859                         report("Failed to load %s view", view->name);
2860                         return;
2861                 }
2863         } else if ((reload || strcmp(view->vid, view->id)) &&
2864                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2865                 report("Failed to load %s view", view->name);
2866                 return;
2867         }
2869         if (split && prev->lineno - prev->offset >= prev->height) {
2870                 /* Take the title line into account. */
2871                 int lines = prev->lineno - prev->offset - prev->height + 1;
2873                 /* Scroll the view that was split if the current line is
2874                  * outside the new limited view. */
2875                 do_scroll_view(prev, lines);
2876         }
2878         if (prev && view != prev) {
2879                 if (split && !backgrounded) {
2880                         /* "Blur" the previous view. */
2881                         update_view_title(prev);
2882                 }
2884                 view->parent = prev;
2885         }
2887         if (view->pipe && view->lines == 0) {
2888                 /* Clear the old view and let the incremental updating refill
2889                  * the screen. */
2890                 werase(view->win);
2891                 report("");
2892         } else if (view_is_displayed(view)) {
2893                 redraw_view(view);
2894                 report("");
2895         }
2897         /* If the view is backgrounded the above calls to report()
2898          * won't redraw the view title. */
2899         if (backgrounded)
2900                 update_view_title(view);
2903 static void
2904 open_external_viewer(const char *argv[], const char *dir)
2906         def_prog_mode();           /* save current tty modes */
2907         endwin();                  /* restore original tty modes */
2908         run_io_fg(argv, dir);
2909         fprintf(stderr, "Press Enter to continue");
2910         getc(opt_tty);
2911         reset_prog_mode();
2912         redraw_display();
2915 static void
2916 open_mergetool(const char *file)
2918         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2920         open_external_viewer(mergetool_argv, NULL);
2923 static void
2924 open_editor(bool from_root, const char *file)
2926         const char *editor_argv[] = { "vi", file, NULL };
2927         const char *editor;
2929         editor = getenv("GIT_EDITOR");
2930         if (!editor && *opt_editor)
2931                 editor = opt_editor;
2932         if (!editor)
2933                 editor = getenv("VISUAL");
2934         if (!editor)
2935                 editor = getenv("EDITOR");
2936         if (!editor)
2937                 editor = "vi";
2939         editor_argv[0] = editor;
2940         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2943 static void
2944 open_run_request(enum request request)
2946         struct run_request *req = get_run_request(request);
2947         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2949         if (!req) {
2950                 report("Unknown run request");
2951                 return;
2952         }
2954         if (format_argv(argv, req->argv, FORMAT_ALL))
2955                 open_external_viewer(argv, NULL);
2956         free_argv(argv);
2959 /*
2960  * User request switch noodle
2961  */
2963 static int
2964 view_driver(struct view *view, enum request request)
2966         int i;
2968         if (request == REQ_NONE) {
2969                 doupdate();
2970                 return TRUE;
2971         }
2973         if (request > REQ_NONE) {
2974                 open_run_request(request);
2975                 /* FIXME: When all views can refresh always do this. */
2976                 if (view == VIEW(REQ_VIEW_STATUS) ||
2977                     view == VIEW(REQ_VIEW_MAIN) ||
2978                     view == VIEW(REQ_VIEW_LOG) ||
2979                     view == VIEW(REQ_VIEW_STAGE))
2980                         request = REQ_REFRESH;
2981                 else
2982                         return TRUE;
2983         }
2985         if (view && view->lines) {
2986                 request = view->ops->request(view, request, &view->line[view->lineno]);
2987                 if (request == REQ_NONE)
2988                         return TRUE;
2989         }
2991         switch (request) {
2992         case REQ_MOVE_UP:
2993         case REQ_MOVE_DOWN:
2994         case REQ_MOVE_PAGE_UP:
2995         case REQ_MOVE_PAGE_DOWN:
2996         case REQ_MOVE_FIRST_LINE:
2997         case REQ_MOVE_LAST_LINE:
2998                 move_view(view, request);
2999                 break;
3001         case REQ_SCROLL_LINE_DOWN:
3002         case REQ_SCROLL_LINE_UP:
3003         case REQ_SCROLL_PAGE_DOWN:
3004         case REQ_SCROLL_PAGE_UP:
3005                 scroll_view(view, request);
3006                 break;
3008         case REQ_VIEW_BLAME:
3009                 if (!opt_file[0]) {
3010                         report("No file chosen, press %s to open tree view",
3011                                get_key(REQ_VIEW_TREE));
3012                         break;
3013                 }
3014                 open_view(view, request, OPEN_DEFAULT);
3015                 break;
3017         case REQ_VIEW_BLOB:
3018                 if (!ref_blob[0]) {
3019                         report("No file chosen, press %s to open tree view",
3020                                get_key(REQ_VIEW_TREE));
3021                         break;
3022                 }
3023                 open_view(view, request, OPEN_DEFAULT);
3024                 break;
3026         case REQ_VIEW_PAGER:
3027                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3028                         report("No pager content, press %s to run command from prompt",
3029                                get_key(REQ_PROMPT));
3030                         break;
3031                 }
3032                 open_view(view, request, OPEN_DEFAULT);
3033                 break;
3035         case REQ_VIEW_STAGE:
3036                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3037                         report("No stage content, press %s to open the status view and choose file",
3038                                get_key(REQ_VIEW_STATUS));
3039                         break;
3040                 }
3041                 open_view(view, request, OPEN_DEFAULT);
3042                 break;
3044         case REQ_VIEW_STATUS:
3045                 if (opt_is_inside_work_tree == FALSE) {
3046                         report("The status view requires a working tree");
3047                         break;
3048                 }
3049                 open_view(view, request, OPEN_DEFAULT);
3050                 break;
3052         case REQ_VIEW_MAIN:
3053         case REQ_VIEW_DIFF:
3054         case REQ_VIEW_LOG:
3055         case REQ_VIEW_TREE:
3056         case REQ_VIEW_HELP:
3057                 open_view(view, request, OPEN_DEFAULT);
3058                 break;
3060         case REQ_NEXT:
3061         case REQ_PREVIOUS:
3062                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3064                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3065                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3066                    (view == VIEW(REQ_VIEW_DIFF) &&
3067                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3068                    (view == VIEW(REQ_VIEW_STAGE) &&
3069                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3070                    (view == VIEW(REQ_VIEW_BLOB) &&
3071                      view->parent == VIEW(REQ_VIEW_TREE))) {
3072                         int line;
3074                         view = view->parent;
3075                         line = view->lineno;
3076                         move_view(view, request);
3077                         if (view_is_displayed(view))
3078                                 update_view_title(view);
3079                         if (line != view->lineno)
3080                                 view->ops->request(view, REQ_ENTER,
3081                                                    &view->line[view->lineno]);
3083                 } else {
3084                         move_view(view, request);
3085                 }
3086                 break;
3088         case REQ_VIEW_NEXT:
3089         {
3090                 int nviews = displayed_views();
3091                 int next_view = (current_view + 1) % nviews;
3093                 if (next_view == current_view) {
3094                         report("Only one view is displayed");
3095                         break;
3096                 }
3098                 current_view = next_view;
3099                 /* Blur out the title of the previous view. */
3100                 update_view_title(view);
3101                 report("");
3102                 break;
3103         }
3104         case REQ_REFRESH:
3105                 report("Refreshing is not yet supported for the %s view", view->name);
3106                 break;
3108         case REQ_MAXIMIZE:
3109                 if (displayed_views() == 2)
3110                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3111                 break;
3113         case REQ_TOGGLE_LINENO:
3114                 opt_line_number = !opt_line_number;
3115                 redraw_display();
3116                 break;
3118         case REQ_TOGGLE_DATE:
3119                 opt_date = !opt_date;
3120                 redraw_display();
3121                 break;
3123         case REQ_TOGGLE_AUTHOR:
3124                 opt_author = !opt_author;
3125                 redraw_display();
3126                 break;
3128         case REQ_TOGGLE_REV_GRAPH:
3129                 opt_rev_graph = !opt_rev_graph;
3130                 redraw_display();
3131                 break;
3133         case REQ_TOGGLE_REFS:
3134                 opt_show_refs = !opt_show_refs;
3135                 redraw_display();
3136                 break;
3138         case REQ_SEARCH:
3139         case REQ_SEARCH_BACK:
3140                 search_view(view, request);
3141                 break;
3143         case REQ_FIND_NEXT:
3144         case REQ_FIND_PREV:
3145                 find_next(view, request);
3146                 break;
3148         case REQ_STOP_LOADING:
3149                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3150                         view = &views[i];
3151                         if (view->pipe)
3152                                 report("Stopped loading the %s view", view->name),
3153                         end_update(view, TRUE);
3154                 }
3155                 break;
3157         case REQ_SHOW_VERSION:
3158                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3159                 return TRUE;
3161         case REQ_SCREEN_RESIZE:
3162                 resize_display();
3163                 /* Fall-through */
3164         case REQ_SCREEN_REDRAW:
3165                 redraw_display();
3166                 break;
3168         case REQ_EDIT:
3169                 report("Nothing to edit");
3170                 break;
3172         case REQ_ENTER:
3173                 report("Nothing to enter");
3174                 break;
3176         case REQ_VIEW_CLOSE:
3177                 /* XXX: Mark closed views by letting view->parent point to the
3178                  * view itself. Parents to closed view should never be
3179                  * followed. */
3180                 if (view->parent &&
3181                     view->parent->parent != view->parent) {
3182                         memset(display, 0, sizeof(display));
3183                         current_view = 0;
3184                         display[current_view] = view->parent;
3185                         view->parent = view;
3186                         resize_display();
3187                         redraw_display();
3188                         report("");
3189                         break;
3190                 }
3191                 /* Fall-through */
3192         case REQ_QUIT:
3193                 return FALSE;
3195         default:
3196                 report("Unknown key, press 'h' for help");
3197                 return TRUE;
3198         }
3200         return TRUE;
3204 /*
3205  * Pager backend
3206  */
3208 static bool
3209 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3211         char *text = line->data;
3213         if (opt_line_number && draw_lineno(view, lineno))
3214                 return TRUE;
3216         draw_text(view, line->type, text, TRUE);
3217         return TRUE;
3220 static bool
3221 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3223         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3224         char refbuf[SIZEOF_STR];
3225         char *ref = NULL;
3227         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3228                 ref = chomp_string(refbuf);
3230         if (!ref || !*ref)
3231                 return TRUE;
3233         /* This is the only fatal call, since it can "corrupt" the buffer. */
3234         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3235                 return FALSE;
3237         return TRUE;
3240 static void
3241 add_pager_refs(struct view *view, struct line *line)
3243         char buf[SIZEOF_STR];
3244         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3245         struct ref **refs;
3246         size_t bufpos = 0, refpos = 0;
3247         const char *sep = "Refs: ";
3248         bool is_tag = FALSE;
3250         assert(line->type == LINE_COMMIT);
3252         refs = get_refs(commit_id);
3253         if (!refs) {
3254                 if (view == VIEW(REQ_VIEW_DIFF))
3255                         goto try_add_describe_ref;
3256                 return;
3257         }
3259         do {
3260                 struct ref *ref = refs[refpos];
3261                 const char *fmt = ref->tag    ? "%s[%s]" :
3262                                   ref->remote ? "%s<%s>" : "%s%s";
3264                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3265                         return;
3266                 sep = ", ";
3267                 if (ref->tag)
3268                         is_tag = TRUE;
3269         } while (refs[refpos++]->next);
3271         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3272 try_add_describe_ref:
3273                 /* Add <tag>-g<commit_id> "fake" reference. */
3274                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3275                         return;
3276         }
3278         if (bufpos == 0)
3279                 return;
3281         if (!realloc_lines(view, view->line_size + 1))
3282                 return;
3284         add_line_text(view, buf, LINE_PP_REFS);
3287 static bool
3288 pager_read(struct view *view, char *data)
3290         struct line *line;
3292         if (!data)
3293                 return TRUE;
3295         line = add_line_text(view, data, get_line_type(data));
3296         if (!line)
3297                 return FALSE;
3299         if (line->type == LINE_COMMIT &&
3300             (view == VIEW(REQ_VIEW_DIFF) ||
3301              view == VIEW(REQ_VIEW_LOG)))
3302                 add_pager_refs(view, line);
3304         return TRUE;
3307 static enum request
3308 pager_request(struct view *view, enum request request, struct line *line)
3310         int split = 0;
3312         if (request != REQ_ENTER)
3313                 return request;
3315         if (line->type == LINE_COMMIT &&
3316            (view == VIEW(REQ_VIEW_LOG) ||
3317             view == VIEW(REQ_VIEW_PAGER))) {
3318                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3319                 split = 1;
3320         }
3322         /* Always scroll the view even if it was split. That way
3323          * you can use Enter to scroll through the log view and
3324          * split open each commit diff. */
3325         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3327         /* FIXME: A minor workaround. Scrolling the view will call report("")
3328          * but if we are scrolling a non-current view this won't properly
3329          * update the view title. */
3330         if (split)
3331                 update_view_title(view);
3333         return REQ_NONE;
3336 static bool
3337 pager_grep(struct view *view, struct line *line)
3339         regmatch_t pmatch;
3340         char *text = line->data;
3342         if (!*text)
3343                 return FALSE;
3345         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3346                 return FALSE;
3348         return TRUE;
3351 static void
3352 pager_select(struct view *view, struct line *line)
3354         if (line->type == LINE_COMMIT) {
3355                 char *text = (char *)line->data + STRING_SIZE("commit ");
3357                 if (view != VIEW(REQ_VIEW_PAGER))
3358                         string_copy_rev(view->ref, text);
3359                 string_copy_rev(ref_commit, text);
3360         }
3363 static struct view_ops pager_ops = {
3364         "line",
3365         NULL,
3366         NULL,
3367         pager_read,
3368         pager_draw,
3369         pager_request,
3370         pager_grep,
3371         pager_select,
3372 };
3374 static const char *log_argv[SIZEOF_ARG] = {
3375         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3376 };
3378 static enum request
3379 log_request(struct view *view, enum request request, struct line *line)
3381         switch (request) {
3382         case REQ_REFRESH:
3383                 load_refs();
3384                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3385                 return REQ_NONE;
3386         default:
3387                 return pager_request(view, request, line);
3388         }
3391 static struct view_ops log_ops = {
3392         "line",
3393         log_argv,
3394         NULL,
3395         pager_read,
3396         pager_draw,
3397         log_request,
3398         pager_grep,
3399         pager_select,
3400 };
3402 static const char *diff_argv[SIZEOF_ARG] = {
3403         "git", "show", "--pretty=fuller", "--no-color", "--root",
3404                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3405 };
3407 static struct view_ops diff_ops = {
3408         "line",
3409         diff_argv,
3410         NULL,
3411         pager_read,
3412         pager_draw,
3413         pager_request,
3414         pager_grep,
3415         pager_select,
3416 };
3418 /*
3419  * Help backend
3420  */
3422 static bool
3423 help_open(struct view *view)
3425         char buf[BUFSIZ];
3426         int lines = ARRAY_SIZE(req_info) + 2;
3427         int i;
3429         if (view->lines > 0)
3430                 return TRUE;
3432         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3433                 if (!req_info[i].request)
3434                         lines++;
3436         lines += run_requests + 1;
3438         view->line = calloc(lines, sizeof(*view->line));
3439         if (!view->line)
3440                 return FALSE;
3442         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3444         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3445                 const char *key;
3447                 if (req_info[i].request == REQ_NONE)
3448                         continue;
3450                 if (!req_info[i].request) {
3451                         add_line_text(view, "", LINE_DEFAULT);
3452                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3453                         continue;
3454                 }
3456                 key = get_key(req_info[i].request);
3457                 if (!*key)
3458                         key = "(no key defined)";
3460                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3461                         continue;
3463                 add_line_text(view, buf, LINE_DEFAULT);
3464         }
3466         if (run_requests) {
3467                 add_line_text(view, "", LINE_DEFAULT);
3468                 add_line_text(view, "External commands:", LINE_DEFAULT);
3469         }
3471         for (i = 0; i < run_requests; i++) {
3472                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3473                 const char *key;
3474                 char cmd[SIZEOF_STR];
3475                 size_t bufpos;
3476                 int argc;
3478                 if (!req)
3479                         continue;
3481                 key = get_key_name(req->key);
3482                 if (!*key)
3483                         key = "(no key defined)";
3485                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3486                         if (!string_format_from(cmd, &bufpos, "%s%s",
3487                                                 argc ? " " : "", req->argv[argc]))
3488                                 return REQ_NONE;
3490                 if (!string_format(buf, "    %-10s %-14s `%s`",
3491                                    keymap_table[req->keymap].name, key, cmd))
3492                         continue;
3494                 add_line_text(view, buf, LINE_DEFAULT);
3495         }
3497         return TRUE;
3500 static struct view_ops help_ops = {
3501         "line",
3502         NULL,
3503         help_open,
3504         NULL,
3505         pager_draw,
3506         pager_request,
3507         pager_grep,
3508         pager_select,
3509 };
3512 /*
3513  * Tree backend
3514  */
3516 struct tree_stack_entry {
3517         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3518         unsigned long lineno;           /* Line number to restore */
3519         char *name;                     /* Position of name in opt_path */
3520 };
3522 /* The top of the path stack. */
3523 static struct tree_stack_entry *tree_stack = NULL;
3524 unsigned long tree_lineno = 0;
3526 static void
3527 pop_tree_stack_entry(void)
3529         struct tree_stack_entry *entry = tree_stack;
3531         tree_lineno = entry->lineno;
3532         entry->name[0] = 0;
3533         tree_stack = entry->prev;
3534         free(entry);
3537 static void
3538 push_tree_stack_entry(const char *name, unsigned long lineno)
3540         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3541         size_t pathlen = strlen(opt_path);
3543         if (!entry)
3544                 return;
3546         entry->prev = tree_stack;
3547         entry->name = opt_path + pathlen;
3548         tree_stack = entry;
3550         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3551                 pop_tree_stack_entry();
3552                 return;
3553         }
3555         /* Move the current line to the first tree entry. */
3556         tree_lineno = 1;
3557         entry->lineno = lineno;
3560 /* Parse output from git-ls-tree(1):
3561  *
3562  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3563  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3564  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3565  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3566  */
3568 #define SIZEOF_TREE_ATTR \
3569         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3571 #define TREE_UP_FORMAT "040000 tree %s\t.."
3573 static int
3574 tree_compare_entry(enum line_type type1, const char *name1,
3575                    enum line_type type2, const char *name2)
3577         if (type1 != type2) {
3578                 if (type1 == LINE_TREE_DIR)
3579                         return -1;
3580                 return 1;
3581         }
3583         return strcmp(name1, name2);
3586 static const char *
3587 tree_path(struct line *line)
3589         const char *path = line->data;
3591         return path + SIZEOF_TREE_ATTR;
3594 static bool
3595 tree_read(struct view *view, char *text)
3597         size_t textlen = text ? strlen(text) : 0;
3598         char buf[SIZEOF_STR];
3599         unsigned long pos;
3600         enum line_type type;
3601         bool first_read = view->lines == 0;
3603         if (!text)
3604                 return TRUE;
3605         if (textlen <= SIZEOF_TREE_ATTR)
3606                 return FALSE;
3608         type = text[STRING_SIZE("100644 ")] == 't'
3609              ? LINE_TREE_DIR : LINE_TREE_FILE;
3611         if (first_read) {
3612                 /* Add path info line */
3613                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3614                     !realloc_lines(view, view->line_size + 1) ||
3615                     !add_line_text(view, buf, LINE_DEFAULT))
3616                         return FALSE;
3618                 /* Insert "link" to parent directory. */
3619                 if (*opt_path) {
3620                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3621                             !realloc_lines(view, view->line_size + 1) ||
3622                             !add_line_text(view, buf, LINE_TREE_DIR))
3623                                 return FALSE;
3624                 }
3625         }
3627         /* Strip the path part ... */
3628         if (*opt_path) {
3629                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3630                 size_t striplen = strlen(opt_path);
3631                 char *path = text + SIZEOF_TREE_ATTR;
3633                 if (pathlen > striplen)
3634                         memmove(path, path + striplen,
3635                                 pathlen - striplen + 1);
3636         }
3638         /* Skip "Directory ..." and ".." line. */
3639         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3640                 struct line *line = &view->line[pos];
3641                 const char *path1 = tree_path(line);
3642                 char *path2 = text + SIZEOF_TREE_ATTR;
3643                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3645                 if (cmp <= 0)
3646                         continue;
3648                 text = strdup(text);
3649                 if (!text)
3650                         return FALSE;
3652                 if (view->lines > pos)
3653                         memmove(&view->line[pos + 1], &view->line[pos],
3654                                 (view->lines - pos) * sizeof(*line));
3656                 line = &view->line[pos];
3657                 line->data = text;
3658                 line->type = type;
3659                 view->lines++;
3660                 return TRUE;
3661         }
3663         if (!add_line_text(view, text, type))
3664                 return FALSE;
3666         if (tree_lineno > view->lineno) {
3667                 view->lineno = tree_lineno;
3668                 tree_lineno = 0;
3669         }
3671         return TRUE;
3674 static enum request
3675 tree_request(struct view *view, enum request request, struct line *line)
3677         enum open_flags flags;
3679         switch (request) {
3680         case REQ_VIEW_BLAME:
3681                 if (line->type != LINE_TREE_FILE) {
3682                         report("Blame only supported for files");
3683                         return REQ_NONE;
3684                 }
3686                 string_copy(opt_ref, view->vid);
3687                 return request;
3689         case REQ_EDIT:
3690                 if (line->type != LINE_TREE_FILE) {
3691                         report("Edit only supported for files");
3692                 } else if (!is_head_commit(view->vid)) {
3693                         report("Edit only supported for files in the current work tree");
3694                 } else {
3695                         open_editor(TRUE, opt_file);
3696                 }
3697                 return REQ_NONE;
3699         case REQ_TREE_PARENT:
3700                 if (!*opt_path) {
3701                         /* quit view if at top of tree */
3702                         return REQ_VIEW_CLOSE;
3703                 }
3704                 /* fake 'cd  ..' */
3705                 line = &view->line[1];
3706                 break;
3708         case REQ_ENTER:
3709                 break;
3711         default:
3712                 return request;
3713         }
3715         /* Cleanup the stack if the tree view is at a different tree. */
3716         while (!*opt_path && tree_stack)
3717                 pop_tree_stack_entry();
3719         switch (line->type) {
3720         case LINE_TREE_DIR:
3721                 /* Depending on whether it is a subdir or parent (updir?) link
3722                  * mangle the path buffer. */
3723                 if (line == &view->line[1] && *opt_path) {
3724                         pop_tree_stack_entry();
3726                 } else {
3727                         const char *basename = tree_path(line);
3729                         push_tree_stack_entry(basename, view->lineno);
3730                 }
3732                 /* Trees and subtrees share the same ID, so they are not not
3733                  * unique like blobs. */
3734                 flags = OPEN_RELOAD;
3735                 request = REQ_VIEW_TREE;
3736                 break;
3738         case LINE_TREE_FILE:
3739                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3740                 request = REQ_VIEW_BLOB;
3741                 break;
3743         default:
3744                 return TRUE;
3745         }
3747         open_view(view, request, flags);
3748         if (request == REQ_VIEW_TREE) {
3749                 view->lineno = tree_lineno;
3750         }
3752         return REQ_NONE;
3755 static void
3756 tree_select(struct view *view, struct line *line)
3758         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3760         if (line->type == LINE_TREE_FILE) {
3761                 string_copy_rev(ref_blob, text);
3762                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3764         } else if (line->type != LINE_TREE_DIR) {
3765                 return;
3766         }
3768         string_copy_rev(view->ref, text);
3771 static const char *tree_argv[SIZEOF_ARG] = {
3772         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3773 };
3775 static struct view_ops tree_ops = {
3776         "file",
3777         tree_argv,
3778         NULL,
3779         tree_read,
3780         pager_draw,
3781         tree_request,
3782         pager_grep,
3783         tree_select,
3784 };
3786 static bool
3787 blob_read(struct view *view, char *line)
3789         if (!line)
3790                 return TRUE;
3791         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3794 static const char *blob_argv[SIZEOF_ARG] = {
3795         "git", "cat-file", "blob", "%(blob)", NULL
3796 };
3798 static struct view_ops blob_ops = {
3799         "line",
3800         blob_argv,
3801         NULL,
3802         blob_read,
3803         pager_draw,
3804         pager_request,
3805         pager_grep,
3806         pager_select,
3807 };
3809 /*
3810  * Blame backend
3811  *
3812  * Loading the blame view is a two phase job:
3813  *
3814  *  1. File content is read either using opt_file from the
3815  *     filesystem or using git-cat-file.
3816  *  2. Then blame information is incrementally added by
3817  *     reading output from git-blame.
3818  */
3820 static const char *blame_head_argv[] = {
3821         "git", "blame", "--incremental", "--", "%(file)", NULL
3822 };
3824 static const char *blame_ref_argv[] = {
3825         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3826 };
3828 static const char *blame_cat_file_argv[] = {
3829         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3830 };
3832 struct blame_commit {
3833         char id[SIZEOF_REV];            /* SHA1 ID. */
3834         char title[128];                /* First line of the commit message. */
3835         char author[75];                /* Author of the commit. */
3836         struct tm time;                 /* Date from the author ident. */
3837         char filename[128];             /* Name of file. */
3838 };
3840 struct blame {
3841         struct blame_commit *commit;
3842         char text[1];
3843 };
3845 static bool
3846 blame_open(struct view *view)
3848         if (*opt_ref || !io_open(&view->io, opt_file)) {
3849                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3850                         return FALSE;
3851         }
3853         setup_update(view, opt_file);
3854         string_format(view->ref, "%s ...", opt_file);
3856         return TRUE;
3859 static struct blame_commit *
3860 get_blame_commit(struct view *view, const char *id)
3862         size_t i;
3864         for (i = 0; i < view->lines; i++) {
3865                 struct blame *blame = view->line[i].data;
3867                 if (!blame->commit)
3868                         continue;
3870                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3871                         return blame->commit;
3872         }
3874         {
3875                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3877                 if (commit)
3878                         string_ncopy(commit->id, id, SIZEOF_REV);
3879                 return commit;
3880         }
3883 static bool
3884 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3886         const char *pos = *posref;
3888         *posref = NULL;
3889         pos = strchr(pos + 1, ' ');
3890         if (!pos || !isdigit(pos[1]))
3891                 return FALSE;
3892         *number = atoi(pos + 1);
3893         if (*number < min || *number > max)
3894                 return FALSE;
3896         *posref = pos;
3897         return TRUE;
3900 static struct blame_commit *
3901 parse_blame_commit(struct view *view, const char *text, int *blamed)
3903         struct blame_commit *commit;
3904         struct blame *blame;
3905         const char *pos = text + SIZEOF_REV - 1;
3906         size_t lineno;
3907         size_t group;
3909         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3910                 return NULL;
3912         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3913             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3914                 return NULL;
3916         commit = get_blame_commit(view, text);
3917         if (!commit)
3918                 return NULL;
3920         *blamed += group;
3921         while (group--) {
3922                 struct line *line = &view->line[lineno + group - 1];
3924                 blame = line->data;
3925                 blame->commit = commit;
3926                 line->dirty = 1;
3927         }
3929         return commit;
3932 static bool
3933 blame_read_file(struct view *view, const char *line, bool *read_file)
3935         if (!line) {
3936                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3937                 struct io io = {};
3939                 if (view->lines == 0 && !view->parent)
3940                         die("No blame exist for %s", view->vid);
3942                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3943                         report("Failed to load blame data");
3944                         return TRUE;
3945                 }
3947                 done_io(view->pipe);
3948                 view->io = io;
3949                 *read_file = FALSE;
3950                 return FALSE;
3952         } else {
3953                 size_t linelen = strlen(line);
3954                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3956                 blame->commit = NULL;
3957                 strncpy(blame->text, line, linelen);
3958                 blame->text[linelen] = 0;
3959                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3960         }
3963 static bool
3964 match_blame_header(const char *name, char **line)
3966         size_t namelen = strlen(name);
3967         bool matched = !strncmp(name, *line, namelen);
3969         if (matched)
3970                 *line += namelen;
3972         return matched;
3975 static bool
3976 blame_read(struct view *view, char *line)
3978         static struct blame_commit *commit = NULL;
3979         static int blamed = 0;
3980         static time_t author_time;
3981         static bool read_file = TRUE;
3983         if (read_file)
3984                 return blame_read_file(view, line, &read_file);
3986         if (!line) {
3987                 /* Reset all! */
3988                 commit = NULL;
3989                 blamed = 0;
3990                 read_file = TRUE;
3991                 string_format(view->ref, "%s", view->vid);
3992                 if (view_is_displayed(view)) {
3993                         update_view_title(view);
3994                         redraw_view_from(view, 0);
3995                 }
3996                 return TRUE;
3997         }
3999         if (!commit) {
4000                 commit = parse_blame_commit(view, line, &blamed);
4001                 string_format(view->ref, "%s %2d%%", view->vid,
4002                               blamed * 100 / view->lines);
4004         } else if (match_blame_header("author ", &line)) {
4005                 string_ncopy(commit->author, line, strlen(line));
4007         } else if (match_blame_header("author-time ", &line)) {
4008                 author_time = (time_t) atol(line);
4010         } else if (match_blame_header("author-tz ", &line)) {
4011                 long tz;
4013                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4014                 tz += ('0' - line[2]) * 60 * 60;
4015                 tz += ('0' - line[3]) * 60;
4016                 tz += ('0' - line[4]) * 60;
4018                 if (line[0] == '-')
4019                         tz = -tz;
4021                 author_time -= tz;
4022                 gmtime_r(&author_time, &commit->time);
4024         } else if (match_blame_header("summary ", &line)) {
4025                 string_ncopy(commit->title, line, strlen(line));
4027         } else if (match_blame_header("filename ", &line)) {
4028                 string_ncopy(commit->filename, line, strlen(line));
4029                 commit = NULL;
4030         }
4032         return TRUE;
4035 static bool
4036 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4038         struct blame *blame = line->data;
4039         struct tm *time = NULL;
4040         const char *id = NULL, *author = NULL;
4042         if (blame->commit && *blame->commit->filename) {
4043                 id = blame->commit->id;
4044                 author = blame->commit->author;
4045                 time = &blame->commit->time;
4046         }
4048         if (opt_date && draw_date(view, time))
4049                 return TRUE;
4051         if (opt_author &&
4052             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4053                 return TRUE;
4055         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4056                 return TRUE;
4058         if (draw_lineno(view, lineno))
4059                 return TRUE;
4061         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4062         return TRUE;
4065 static enum request
4066 blame_request(struct view *view, enum request request, struct line *line)
4068         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4069         struct blame *blame = line->data;
4071         switch (request) {
4072         case REQ_VIEW_BLAME:
4073                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4074                         report("Commit ID unknown");
4075                         break;
4076                 }
4077                 string_copy(opt_ref, blame->commit->id);
4078                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4079                 return request;
4081         case REQ_ENTER:
4082                 if (!blame->commit) {
4083                         report("No commit loaded yet");
4084                         break;
4085                 }
4087                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4088                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4089                         break;
4091                 if (!strcmp(blame->commit->id, NULL_ID)) {
4092                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4093                         const char *diff_index_argv[] = {
4094                                 "git", "diff-index", "--root", "--cached",
4095                                         "--patch-with-stat", "-C", "-M",
4096                                         "HEAD", "--", view->vid, NULL
4097                         };
4099                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4100                                 report("Failed to allocate diff command");
4101                                 break;
4102                         }
4103                         flags |= OPEN_PREPARED;
4104                 }
4106                 open_view(view, REQ_VIEW_DIFF, flags);
4107                 break;
4109         default:
4110                 return request;
4111         }
4113         return REQ_NONE;
4116 static bool
4117 blame_grep(struct view *view, struct line *line)
4119         struct blame *blame = line->data;
4120         struct blame_commit *commit = blame->commit;
4121         regmatch_t pmatch;
4123 #define MATCH(text, on)                                                 \
4124         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4126         if (commit) {
4127                 char buf[DATE_COLS + 1];
4129                 if (MATCH(commit->title, 1) ||
4130                     MATCH(commit->author, opt_author) ||
4131                     MATCH(commit->id, opt_date))
4132                         return TRUE;
4134                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4135                     MATCH(buf, 1))
4136                         return TRUE;
4137         }
4139         return MATCH(blame->text, 1);
4141 #undef MATCH
4144 static void
4145 blame_select(struct view *view, struct line *line)
4147         struct blame *blame = line->data;
4148         struct blame_commit *commit = blame->commit;
4150         if (!commit)
4151                 return;
4153         if (!strcmp(commit->id, NULL_ID))
4154                 string_ncopy(ref_commit, "HEAD", 4);
4155         else
4156                 string_copy_rev(ref_commit, commit->id);
4159 static struct view_ops blame_ops = {
4160         "line",
4161         NULL,
4162         blame_open,
4163         blame_read,
4164         blame_draw,
4165         blame_request,
4166         blame_grep,
4167         blame_select,
4168 };
4170 /*
4171  * Status backend
4172  */
4174 struct status {
4175         char status;
4176         struct {
4177                 mode_t mode;
4178                 char rev[SIZEOF_REV];
4179                 char name[SIZEOF_STR];
4180         } old;
4181         struct {
4182                 mode_t mode;
4183                 char rev[SIZEOF_REV];
4184                 char name[SIZEOF_STR];
4185         } new;
4186 };
4188 static char status_onbranch[SIZEOF_STR];
4189 static struct status stage_status;
4190 static enum line_type stage_line_type;
4191 static size_t stage_chunks;
4192 static int *stage_chunk;
4194 /* This should work even for the "On branch" line. */
4195 static inline bool
4196 status_has_none(struct view *view, struct line *line)
4198         return line < view->line + view->lines && !line[1].data;
4201 /* Get fields from the diff line:
4202  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4203  */
4204 static inline bool
4205 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4207         const char *old_mode = buf +  1;
4208         const char *new_mode = buf +  8;
4209         const char *old_rev  = buf + 15;
4210         const char *new_rev  = buf + 56;
4211         const char *status   = buf + 97;
4213         if (bufsize < 98 ||
4214             old_mode[-1] != ':' ||
4215             new_mode[-1] != ' ' ||
4216             old_rev[-1]  != ' ' ||
4217             new_rev[-1]  != ' ' ||
4218             status[-1]   != ' ')
4219                 return FALSE;
4221         file->status = *status;
4223         string_copy_rev(file->old.rev, old_rev);
4224         string_copy_rev(file->new.rev, new_rev);
4226         file->old.mode = strtoul(old_mode, NULL, 8);
4227         file->new.mode = strtoul(new_mode, NULL, 8);
4229         file->old.name[0] = file->new.name[0] = 0;
4231         return TRUE;
4234 static bool
4235 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4237         struct status *file = NULL;
4238         struct status *unmerged = NULL;
4239         char *buf;
4240         struct io io = {};
4242         if (!run_io(&io, argv, NULL, IO_RD))
4243                 return FALSE;
4245         add_line_data(view, NULL, type);
4247         while ((buf = io_get(&io, 0, TRUE))) {
4248                 if (!file) {
4249                         if (!realloc_lines(view, view->line_size + 1))
4250                                 goto error_out;
4252                         file = calloc(1, sizeof(*file));
4253                         if (!file)
4254                                 goto error_out;
4256                         add_line_data(view, file, type);
4257                 }
4259                 /* Parse diff info part. */
4260                 if (status) {
4261                         file->status = status;
4262                         if (status == 'A')
4263                                 string_copy(file->old.rev, NULL_ID);
4265                 } else if (!file->status) {
4266                         if (!status_get_diff(file, buf, strlen(buf)))
4267                                 goto error_out;
4269                         buf = io_get(&io, 0, TRUE);
4270                         if (!buf)
4271                                 break;
4273                         /* Collapse all 'M'odified entries that follow a
4274                          * associated 'U'nmerged entry. */
4275                         if (file->status == 'U') {
4276                                 unmerged = file;
4278                         } else if (unmerged) {
4279                                 int collapse = !strcmp(buf, unmerged->new.name);
4281                                 unmerged = NULL;
4282                                 if (collapse) {
4283                                         free(file);
4284                                         view->lines--;
4285                                         continue;
4286                                 }
4287                         }
4288                 }
4290                 /* Grab the old name for rename/copy. */
4291                 if (!*file->old.name &&
4292                     (file->status == 'R' || file->status == 'C')) {
4293                         string_ncopy(file->old.name, buf, strlen(buf));
4295                         buf = io_get(&io, 0, TRUE);
4296                         if (!buf)
4297                                 break;
4298                 }
4300                 /* git-ls-files just delivers a NUL separated list of
4301                  * file names similar to the second half of the
4302                  * git-diff-* output. */
4303                 string_ncopy(file->new.name, buf, strlen(buf));
4304                 if (!*file->old.name)
4305                         string_copy(file->old.name, file->new.name);
4306                 file = NULL;
4307         }
4309         if (io_error(&io)) {
4310 error_out:
4311                 done_io(&io);
4312                 return FALSE;
4313         }
4315         if (!view->line[view->lines - 1].data)
4316                 add_line_data(view, NULL, LINE_STAT_NONE);
4318         done_io(&io);
4319         return TRUE;
4322 /* Don't show unmerged entries in the staged section. */
4323 static const char *status_diff_index_argv[] = {
4324         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4325                              "--cached", "-M", "HEAD", NULL
4326 };
4328 static const char *status_diff_files_argv[] = {
4329         "git", "diff-files", "-z", NULL
4330 };
4332 static const char *status_list_other_argv[] = {
4333         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4334 };
4336 static const char *status_list_no_head_argv[] = {
4337         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4338 };
4340 static const char *update_index_argv[] = {
4341         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4342 };
4344 /* First parse staged info using git-diff-index(1), then parse unstaged
4345  * info using git-diff-files(1), and finally untracked files using
4346  * git-ls-files(1). */
4347 static bool
4348 status_open(struct view *view)
4350         unsigned long prev_lineno = view->lineno;
4352         reset_view(view);
4354         if (!realloc_lines(view, view->line_size + 7))
4355                 return FALSE;
4357         add_line_data(view, NULL, LINE_STAT_HEAD);
4358         if (is_initial_commit())
4359                 string_copy(status_onbranch, "Initial commit");
4360         else if (!*opt_head)
4361                 string_copy(status_onbranch, "Not currently on any branch");
4362         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4363                 return FALSE;
4365         run_io_bg(update_index_argv);
4367         if (is_initial_commit()) {
4368                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4369                         return FALSE;
4370         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4371                 return FALSE;
4372         }
4374         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4375             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4376                 return FALSE;
4378         /* If all went well restore the previous line number to stay in
4379          * the context or select a line with something that can be
4380          * updated. */
4381         if (prev_lineno >= view->lines)
4382                 prev_lineno = view->lines - 1;
4383         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4384                 prev_lineno++;
4385         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4386                 prev_lineno--;
4388         /* If the above fails, always skip the "On branch" line. */
4389         if (prev_lineno < view->lines)
4390                 view->lineno = prev_lineno;
4391         else
4392                 view->lineno = 1;
4394         if (view->lineno < view->offset)
4395                 view->offset = view->lineno;
4396         else if (view->offset + view->height <= view->lineno)
4397                 view->offset = view->lineno - view->height + 1;
4399         return TRUE;
4402 static bool
4403 status_draw(struct view *view, struct line *line, unsigned int lineno)
4405         struct status *status = line->data;
4406         enum line_type type;
4407         const char *text;
4409         if (!status) {
4410                 switch (line->type) {
4411                 case LINE_STAT_STAGED:
4412                         type = LINE_STAT_SECTION;
4413                         text = "Changes to be committed:";
4414                         break;
4416                 case LINE_STAT_UNSTAGED:
4417                         type = LINE_STAT_SECTION;
4418                         text = "Changed but not updated:";
4419                         break;
4421                 case LINE_STAT_UNTRACKED:
4422                         type = LINE_STAT_SECTION;
4423                         text = "Untracked files:";
4424                         break;
4426                 case LINE_STAT_NONE:
4427                         type = LINE_DEFAULT;
4428                         text = "    (no files)";
4429                         break;
4431                 case LINE_STAT_HEAD:
4432                         type = LINE_STAT_HEAD;
4433                         text = status_onbranch;
4434                         break;
4436                 default:
4437                         return FALSE;
4438                 }
4439         } else {
4440                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4442                 buf[0] = status->status;
4443                 if (draw_text(view, line->type, buf, TRUE))
4444                         return TRUE;
4445                 type = LINE_DEFAULT;
4446                 text = status->new.name;
4447         }
4449         draw_text(view, type, text, TRUE);
4450         return TRUE;
4453 static enum request
4454 status_enter(struct view *view, struct line *line)
4456         struct status *status = line->data;
4457         const char *oldpath = status ? status->old.name : NULL;
4458         /* Diffs for unmerged entries are empty when passing the new
4459          * path, so leave it empty. */
4460         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4461         const char *info;
4462         enum open_flags split;
4463         struct view *stage = VIEW(REQ_VIEW_STAGE);
4465         if (line->type == LINE_STAT_NONE ||
4466             (!status && line[1].type == LINE_STAT_NONE)) {
4467                 report("No file to diff");
4468                 return REQ_NONE;
4469         }
4471         switch (line->type) {
4472         case LINE_STAT_STAGED:
4473                 if (is_initial_commit()) {
4474                         const char *no_head_diff_argv[] = {
4475                                 "git", "diff", "--no-color", "--patch-with-stat",
4476                                         "--", "/dev/null", newpath, NULL
4477                         };
4479                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4480                                 return REQ_QUIT;
4481                 } else {
4482                         const char *index_show_argv[] = {
4483                                 "git", "diff-index", "--root", "--patch-with-stat",
4484                                         "-C", "-M", "--cached", "HEAD", "--",
4485                                         oldpath, newpath, NULL
4486                         };
4488                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4489                                 return REQ_QUIT;
4490                 }
4492                 if (status)
4493                         info = "Staged changes to %s";
4494                 else
4495                         info = "Staged changes";
4496                 break;
4498         case LINE_STAT_UNSTAGED:
4499         {
4500                 const char *files_show_argv[] = {
4501                         "git", "diff-files", "--root", "--patch-with-stat",
4502                                 "-C", "-M", "--", oldpath, newpath, NULL
4503                 };
4505                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4506                         return REQ_QUIT;
4507                 if (status)
4508                         info = "Unstaged changes to %s";
4509                 else
4510                         info = "Unstaged changes";
4511                 break;
4512         }
4513         case LINE_STAT_UNTRACKED:
4514                 if (!newpath) {
4515                         report("No file to show");
4516                         return REQ_NONE;
4517                 }
4519                 if (!suffixcmp(status->new.name, -1, "/")) {
4520                         report("Cannot display a directory");
4521                         return REQ_NONE;
4522                 }
4524                 if (!prepare_update_file(stage, newpath))
4525                         return REQ_QUIT;
4526                 info = "Untracked file %s";
4527                 break;
4529         case LINE_STAT_HEAD:
4530                 return REQ_NONE;
4532         default:
4533                 die("line type %d not handled in switch", line->type);
4534         }
4536         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4537         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4538         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4539                 if (status) {
4540                         stage_status = *status;
4541                 } else {
4542                         memset(&stage_status, 0, sizeof(stage_status));
4543                 }
4545                 stage_line_type = line->type;
4546                 stage_chunks = 0;
4547                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4548         }
4550         return REQ_NONE;
4553 static bool
4554 status_exists(struct status *status, enum line_type type)
4556         struct view *view = VIEW(REQ_VIEW_STATUS);
4557         struct line *line;
4559         for (line = view->line; line < view->line + view->lines; line++) {
4560                 struct status *pos = line->data;
4562                 if (line->type == type && pos &&
4563                     !strcmp(status->new.name, pos->new.name))
4564                         return TRUE;
4565         }
4567         return FALSE;
4571 static bool
4572 status_update_prepare(struct io *io, enum line_type type)
4574         const char *staged_argv[] = {
4575                 "git", "update-index", "-z", "--index-info", NULL
4576         };
4577         const char *others_argv[] = {
4578                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4579         };
4581         switch (type) {
4582         case LINE_STAT_STAGED:
4583                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4585         case LINE_STAT_UNSTAGED:
4586                 return run_io(io, others_argv, opt_cdup, IO_WR);
4588         case LINE_STAT_UNTRACKED:
4589                 return run_io(io, others_argv, NULL, IO_WR);
4591         default:
4592                 die("line type %d not handled in switch", type);
4593                 return FALSE;
4594         }
4597 static bool
4598 status_update_write(struct io *io, struct status *status, enum line_type type)
4600         char buf[SIZEOF_STR];
4601         size_t bufsize = 0;
4603         switch (type) {
4604         case LINE_STAT_STAGED:
4605                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4606                                         status->old.mode,
4607                                         status->old.rev,
4608                                         status->old.name, 0))
4609                         return FALSE;
4610                 break;
4612         case LINE_STAT_UNSTAGED:
4613         case LINE_STAT_UNTRACKED:
4614                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4615                         return FALSE;
4616                 break;
4618         default:
4619                 die("line type %d not handled in switch", type);
4620         }
4622         return io_write(io, buf, bufsize);
4625 static bool
4626 status_update_file(struct status *status, enum line_type type)
4628         struct io io = {};
4629         bool result;
4631         if (!status_update_prepare(&io, type))
4632                 return FALSE;
4634         result = status_update_write(&io, status, type);
4635         done_io(&io);
4636         return result;
4639 static bool
4640 status_update_files(struct view *view, struct line *line)
4642         struct io io = {};
4643         bool result = TRUE;
4644         struct line *pos = view->line + view->lines;
4645         int files = 0;
4646         int file, done;
4648         if (!status_update_prepare(&io, line->type))
4649                 return FALSE;
4651         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4652                 files++;
4654         for (file = 0, done = 0; result && file < files; line++, file++) {
4655                 int almost_done = file * 100 / files;
4657                 if (almost_done > done) {
4658                         done = almost_done;
4659                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4660                                       file, files, done);
4661                         update_view_title(view);
4662                 }
4663                 result = status_update_write(&io, line->data, line->type);
4664         }
4666         done_io(&io);
4667         return result;
4670 static bool
4671 status_update(struct view *view)
4673         struct line *line = &view->line[view->lineno];
4675         assert(view->lines);
4677         if (!line->data) {
4678                 /* This should work even for the "On branch" line. */
4679                 if (line < view->line + view->lines && !line[1].data) {
4680                         report("Nothing to update");
4681                         return FALSE;
4682                 }
4684                 if (!status_update_files(view, line + 1)) {
4685                         report("Failed to update file status");
4686                         return FALSE;
4687                 }
4689         } else if (!status_update_file(line->data, line->type)) {
4690                 report("Failed to update file status");
4691                 return FALSE;
4692         }
4694         return TRUE;
4697 static bool
4698 status_revert(struct status *status, enum line_type type, bool has_none)
4700         if (!status || type != LINE_STAT_UNSTAGED) {
4701                 if (type == LINE_STAT_STAGED) {
4702                         report("Cannot revert changes to staged files");
4703                 } else if (type == LINE_STAT_UNTRACKED) {
4704                         report("Cannot revert changes to untracked files");
4705                 } else if (has_none) {
4706                         report("Nothing to revert");
4707                 } else {
4708                         report("Cannot revert changes to multiple files");
4709                 }
4710                 return FALSE;
4712         } else {
4713                 const char *checkout_argv[] = {
4714                         "git", "checkout", "--", status->old.name, NULL
4715                 };
4717                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4718                         return FALSE;
4719                 return run_io_fg(checkout_argv, opt_cdup);
4720         }
4723 static enum request
4724 status_request(struct view *view, enum request request, struct line *line)
4726         struct status *status = line->data;
4728         switch (request) {
4729         case REQ_STATUS_UPDATE:
4730                 if (!status_update(view))
4731                         return REQ_NONE;
4732                 break;
4734         case REQ_STATUS_REVERT:
4735                 if (!status_revert(status, line->type, status_has_none(view, line)))
4736                         return REQ_NONE;
4737                 break;
4739         case REQ_STATUS_MERGE:
4740                 if (!status || status->status != 'U') {
4741                         report("Merging only possible for files with unmerged status ('U').");
4742                         return REQ_NONE;
4743                 }
4744                 open_mergetool(status->new.name);
4745                 break;
4747         case REQ_EDIT:
4748                 if (!status)
4749                         return request;
4750                 if (status->status == 'D') {
4751                         report("File has been deleted.");
4752                         return REQ_NONE;
4753                 }
4755                 open_editor(status->status != '?', status->new.name);
4756                 break;
4758         case REQ_VIEW_BLAME:
4759                 if (status) {
4760                         string_copy(opt_file, status->new.name);
4761                         opt_ref[0] = 0;
4762                 }
4763                 return request;
4765         case REQ_ENTER:
4766                 /* After returning the status view has been split to
4767                  * show the stage view. No further reloading is
4768                  * necessary. */
4769                 status_enter(view, line);
4770                 return REQ_NONE;
4772         case REQ_REFRESH:
4773                 /* Simply reload the view. */
4774                 break;
4776         default:
4777                 return request;
4778         }
4780         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4782         return REQ_NONE;
4785 static void
4786 status_select(struct view *view, struct line *line)
4788         struct status *status = line->data;
4789         char file[SIZEOF_STR] = "all files";
4790         const char *text;
4791         const char *key;
4793         if (status && !string_format(file, "'%s'", status->new.name))
4794                 return;
4796         if (!status && line[1].type == LINE_STAT_NONE)
4797                 line++;
4799         switch (line->type) {
4800         case LINE_STAT_STAGED:
4801                 text = "Press %s to unstage %s for commit";
4802                 break;
4804         case LINE_STAT_UNSTAGED:
4805                 text = "Press %s to stage %s for commit";
4806                 break;
4808         case LINE_STAT_UNTRACKED:
4809                 text = "Press %s to stage %s for addition";
4810                 break;
4812         case LINE_STAT_HEAD:
4813         case LINE_STAT_NONE:
4814                 text = "Nothing to update";
4815                 break;
4817         default:
4818                 die("line type %d not handled in switch", line->type);
4819         }
4821         if (status && status->status == 'U') {
4822                 text = "Press %s to resolve conflict in %s";
4823                 key = get_key(REQ_STATUS_MERGE);
4825         } else {
4826                 key = get_key(REQ_STATUS_UPDATE);
4827         }
4829         string_format(view->ref, text, key, file);
4832 static bool
4833 status_grep(struct view *view, struct line *line)
4835         struct status *status = line->data;
4836         enum { S_STATUS, S_NAME, S_END } state;
4837         char buf[2] = "?";
4838         regmatch_t pmatch;
4840         if (!status)
4841                 return FALSE;
4843         for (state = S_STATUS; state < S_END; state++) {
4844                 const char *text;
4846                 switch (state) {
4847                 case S_NAME:    text = status->new.name;        break;
4848                 case S_STATUS:
4849                         buf[0] = status->status;
4850                         text = buf;
4851                         break;
4853                 default:
4854                         return FALSE;
4855                 }
4857                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4858                         return TRUE;
4859         }
4861         return FALSE;
4864 static struct view_ops status_ops = {
4865         "file",
4866         NULL,
4867         status_open,
4868         NULL,
4869         status_draw,
4870         status_request,
4871         status_grep,
4872         status_select,
4873 };
4876 static bool
4877 stage_diff_write(struct io *io, struct line *line, struct line *end)
4879         while (line < end) {
4880                 if (!io_write(io, line->data, strlen(line->data)) ||
4881                     !io_write(io, "\n", 1))
4882                         return FALSE;
4883                 line++;
4884                 if (line->type == LINE_DIFF_CHUNK ||
4885                     line->type == LINE_DIFF_HEADER)
4886                         break;
4887         }
4889         return TRUE;
4892 static struct line *
4893 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4895         for (; view->line < line; line--)
4896                 if (line->type == type)
4897                         return line;
4899         return NULL;
4902 static bool
4903 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4905         const char *apply_argv[SIZEOF_ARG] = {
4906                 "git", "apply", "--whitespace=nowarn", NULL
4907         };
4908         struct line *diff_hdr;
4909         struct io io = {};
4910         int argc = 3;
4912         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4913         if (!diff_hdr)
4914                 return FALSE;
4916         if (!revert)
4917                 apply_argv[argc++] = "--cached";
4918         if (revert || stage_line_type == LINE_STAT_STAGED)
4919                 apply_argv[argc++] = "-R";
4920         apply_argv[argc++] = "-";
4921         apply_argv[argc++] = NULL;
4922         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4923                 return FALSE;
4925         if (!stage_diff_write(&io, diff_hdr, chunk) ||
4926             !stage_diff_write(&io, chunk, view->line + view->lines))
4927                 chunk = NULL;
4929         done_io(&io);
4930         run_io_bg(update_index_argv);
4932         return chunk ? TRUE : FALSE;
4935 static bool
4936 stage_update(struct view *view, struct line *line)
4938         struct line *chunk = NULL;
4940         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4941                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4943         if (chunk) {
4944                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4945                         report("Failed to apply chunk");
4946                         return FALSE;
4947                 }
4949         } else if (!stage_status.status) {
4950                 view = VIEW(REQ_VIEW_STATUS);
4952                 for (line = view->line; line < view->line + view->lines; line++)
4953                         if (line->type == stage_line_type)
4954                                 break;
4956                 if (!status_update_files(view, line + 1)) {
4957                         report("Failed to update files");
4958                         return FALSE;
4959                 }
4961         } else if (!status_update_file(&stage_status, stage_line_type)) {
4962                 report("Failed to update file");
4963                 return FALSE;
4964         }
4966         return TRUE;
4969 static bool
4970 stage_revert(struct view *view, struct line *line)
4972         struct line *chunk = NULL;
4974         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4975                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4977         if (chunk) {
4978                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4979                         return FALSE;
4981                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4982                         report("Failed to revert chunk");
4983                         return FALSE;
4984                 }
4985                 return TRUE;
4987         } else {
4988                 return status_revert(stage_status.status ? &stage_status : NULL,
4989                                      stage_line_type, FALSE);
4990         }
4994 static void
4995 stage_next(struct view *view, struct line *line)
4997         int i;
4999         if (!stage_chunks) {
5000                 static size_t alloc = 0;
5001                 int *tmp;
5003                 for (line = view->line; line < view->line + view->lines; line++) {
5004                         if (line->type != LINE_DIFF_CHUNK)
5005                                 continue;
5007                         tmp = realloc_items(stage_chunk, &alloc,
5008                                             stage_chunks, sizeof(*tmp));
5009                         if (!tmp) {
5010                                 report("Allocation failure");
5011                                 return;
5012                         }
5014                         stage_chunk = tmp;
5015                         stage_chunk[stage_chunks++] = line - view->line;
5016                 }
5017         }
5019         for (i = 0; i < stage_chunks; i++) {
5020                 if (stage_chunk[i] > view->lineno) {
5021                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5022                         report("Chunk %d of %d", i + 1, stage_chunks);
5023                         return;
5024                 }
5025         }
5027         report("No next chunk found");
5030 static enum request
5031 stage_request(struct view *view, enum request request, struct line *line)
5033         switch (request) {
5034         case REQ_STATUS_UPDATE:
5035                 if (!stage_update(view, line))
5036                         return REQ_NONE;
5037                 break;
5039         case REQ_STATUS_REVERT:
5040                 if (!stage_revert(view, line))
5041                         return REQ_NONE;
5042                 break;
5044         case REQ_STAGE_NEXT:
5045                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5046                         report("File is untracked; press %s to add",
5047                                get_key(REQ_STATUS_UPDATE));
5048                         return REQ_NONE;
5049                 }
5050                 stage_next(view, line);
5051                 return REQ_NONE;
5053         case REQ_EDIT:
5054                 if (!stage_status.new.name[0])
5055                         return request;
5056                 if (stage_status.status == 'D') {
5057                         report("File has been deleted.");
5058                         return REQ_NONE;
5059                 }
5061                 open_editor(stage_status.status != '?', stage_status.new.name);
5062                 break;
5064         case REQ_REFRESH:
5065                 /* Reload everything ... */
5066                 break;
5068         case REQ_VIEW_BLAME:
5069                 if (stage_status.new.name[0]) {
5070                         string_copy(opt_file, stage_status.new.name);
5071                         opt_ref[0] = 0;
5072                 }
5073                 return request;
5075         case REQ_ENTER:
5076                 return pager_request(view, request, line);
5078         default:
5079                 return request;
5080         }
5082         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5084         /* Check whether the staged entry still exists, and close the
5085          * stage view if it doesn't. */
5086         if (!status_exists(&stage_status, stage_line_type))
5087                 return REQ_VIEW_CLOSE;
5089         if (stage_line_type == LINE_STAT_UNTRACKED) {
5090                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5091                         report("Cannot display a directory");
5092                         return REQ_NONE;
5093                 }
5095                 if (!prepare_update_file(view, stage_status.new.name)) {
5096                         report("Failed to open file: %s", strerror(errno));
5097                         return REQ_NONE;
5098                 }
5099         }
5100         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5102         return REQ_NONE;
5105 static struct view_ops stage_ops = {
5106         "line",
5107         NULL,
5108         NULL,
5109         pager_read,
5110         pager_draw,
5111         stage_request,
5112         pager_grep,
5113         pager_select,
5114 };
5117 /*
5118  * Revision graph
5119  */
5121 struct commit {
5122         char id[SIZEOF_REV];            /* SHA1 ID. */
5123         char title[128];                /* First line of the commit message. */
5124         char author[75];                /* Author of the commit. */
5125         struct tm time;                 /* Date from the author ident. */
5126         struct ref **refs;              /* Repository references. */
5127         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5128         size_t graph_size;              /* The width of the graph array. */
5129         bool has_parents;               /* Rewritten --parents seen. */
5130 };
5132 /* Size of rev graph with no  "padding" columns */
5133 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5135 struct rev_graph {
5136         struct rev_graph *prev, *next, *parents;
5137         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5138         size_t size;
5139         struct commit *commit;
5140         size_t pos;
5141         unsigned int boundary:1;
5142 };
5144 /* Parents of the commit being visualized. */
5145 static struct rev_graph graph_parents[4];
5147 /* The current stack of revisions on the graph. */
5148 static struct rev_graph graph_stacks[4] = {
5149         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5150         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5151         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5152         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5153 };
5155 static inline bool
5156 graph_parent_is_merge(struct rev_graph *graph)
5158         return graph->parents->size > 1;
5161 static inline void
5162 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5164         struct commit *commit = graph->commit;
5166         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5167                 commit->graph[commit->graph_size++] = symbol;
5170 static void
5171 clear_rev_graph(struct rev_graph *graph)
5173         graph->boundary = 0;
5174         graph->size = graph->pos = 0;
5175         graph->commit = NULL;
5176         memset(graph->parents, 0, sizeof(*graph->parents));
5179 static void
5180 done_rev_graph(struct rev_graph *graph)
5182         if (graph_parent_is_merge(graph) &&
5183             graph->pos < graph->size - 1 &&
5184             graph->next->size == graph->size + graph->parents->size - 1) {
5185                 size_t i = graph->pos + graph->parents->size - 1;
5187                 graph->commit->graph_size = i * 2;
5188                 while (i < graph->next->size - 1) {
5189                         append_to_rev_graph(graph, ' ');
5190                         append_to_rev_graph(graph, '\\');
5191                         i++;
5192                 }
5193         }
5195         clear_rev_graph(graph);
5198 static void
5199 push_rev_graph(struct rev_graph *graph, const char *parent)
5201         int i;
5203         /* "Collapse" duplicate parents lines.
5204          *
5205          * FIXME: This needs to also update update the drawn graph but
5206          * for now it just serves as a method for pruning graph lines. */
5207         for (i = 0; i < graph->size; i++)
5208                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5209                         return;
5211         if (graph->size < SIZEOF_REVITEMS) {
5212                 string_copy_rev(graph->rev[graph->size++], parent);
5213         }
5216 static chtype
5217 get_rev_graph_symbol(struct rev_graph *graph)
5219         chtype symbol;
5221         if (graph->boundary)
5222                 symbol = REVGRAPH_BOUND;
5223         else if (graph->parents->size == 0)
5224                 symbol = REVGRAPH_INIT;
5225         else if (graph_parent_is_merge(graph))
5226                 symbol = REVGRAPH_MERGE;
5227         else if (graph->pos >= graph->size)
5228                 symbol = REVGRAPH_BRANCH;
5229         else
5230                 symbol = REVGRAPH_COMMIT;
5232         return symbol;
5235 static void
5236 draw_rev_graph(struct rev_graph *graph)
5238         struct rev_filler {
5239                 chtype separator, line;
5240         };
5241         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5242         static struct rev_filler fillers[] = {
5243                 { ' ',  '|' },
5244                 { '`',  '.' },
5245                 { '\'', ' ' },
5246                 { '/',  ' ' },
5247         };
5248         chtype symbol = get_rev_graph_symbol(graph);
5249         struct rev_filler *filler;
5250         size_t i;
5252         if (opt_line_graphics)
5253                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5255         filler = &fillers[DEFAULT];
5257         for (i = 0; i < graph->pos; i++) {
5258                 append_to_rev_graph(graph, filler->line);
5259                 if (graph_parent_is_merge(graph->prev) &&
5260                     graph->prev->pos == i)
5261                         filler = &fillers[RSHARP];
5263                 append_to_rev_graph(graph, filler->separator);
5264         }
5266         /* Place the symbol for this revision. */
5267         append_to_rev_graph(graph, symbol);
5269         if (graph->prev->size > graph->size)
5270                 filler = &fillers[RDIAG];
5271         else
5272                 filler = &fillers[DEFAULT];
5274         i++;
5276         for (; i < graph->size; i++) {
5277                 append_to_rev_graph(graph, filler->separator);
5278                 append_to_rev_graph(graph, filler->line);
5279                 if (graph_parent_is_merge(graph->prev) &&
5280                     i < graph->prev->pos + graph->parents->size)
5281                         filler = &fillers[RSHARP];
5282                 if (graph->prev->size > graph->size)
5283                         filler = &fillers[LDIAG];
5284         }
5286         if (graph->prev->size > graph->size) {
5287                 append_to_rev_graph(graph, filler->separator);
5288                 if (filler->line != ' ')
5289                         append_to_rev_graph(graph, filler->line);
5290         }
5293 /* Prepare the next rev graph */
5294 static void
5295 prepare_rev_graph(struct rev_graph *graph)
5297         size_t i;
5299         /* First, traverse all lines of revisions up to the active one. */
5300         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5301                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5302                         break;
5304                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5305         }
5307         /* Interleave the new revision parent(s). */
5308         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5309                 push_rev_graph(graph->next, graph->parents->rev[i]);
5311         /* Lastly, put any remaining revisions. */
5312         for (i = graph->pos + 1; i < graph->size; i++)
5313                 push_rev_graph(graph->next, graph->rev[i]);
5316 static void
5317 update_rev_graph(struct rev_graph *graph)
5319         /* If this is the finalizing update ... */
5320         if (graph->commit)
5321                 prepare_rev_graph(graph);
5323         /* Graph visualization needs a one rev look-ahead,
5324          * so the first update doesn't visualize anything. */
5325         if (!graph->prev->commit)
5326                 return;
5328         draw_rev_graph(graph->prev);
5329         done_rev_graph(graph->prev->prev);
5333 /*
5334  * Main view backend
5335  */
5337 static const char *main_argv[SIZEOF_ARG] = {
5338         "git", "log", "--no-color", "--pretty=raw", "--parents",
5339                       "--topo-order", "%(head)", NULL
5340 };
5342 static bool
5343 main_draw(struct view *view, struct line *line, unsigned int lineno)
5345         struct commit *commit = line->data;
5347         if (!*commit->author)
5348                 return FALSE;
5350         if (opt_date && draw_date(view, &commit->time))
5351                 return TRUE;
5353         if (opt_author &&
5354             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5355                 return TRUE;
5357         if (opt_rev_graph && commit->graph_size &&
5358             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5359                 return TRUE;
5361         if (opt_show_refs && commit->refs) {
5362                 size_t i = 0;
5364                 do {
5365                         enum line_type type;
5367                         if (commit->refs[i]->head)
5368                                 type = LINE_MAIN_HEAD;
5369                         else if (commit->refs[i]->ltag)
5370                                 type = LINE_MAIN_LOCAL_TAG;
5371                         else if (commit->refs[i]->tag)
5372                                 type = LINE_MAIN_TAG;
5373                         else if (commit->refs[i]->tracked)
5374                                 type = LINE_MAIN_TRACKED;
5375                         else if (commit->refs[i]->remote)
5376                                 type = LINE_MAIN_REMOTE;
5377                         else
5378                                 type = LINE_MAIN_REF;
5380                         if (draw_text(view, type, "[", TRUE) ||
5381                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5382                             draw_text(view, type, "]", TRUE))
5383                                 return TRUE;
5385                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5386                                 return TRUE;
5387                 } while (commit->refs[i++]->next);
5388         }
5390         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5391         return TRUE;
5394 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5395 static bool
5396 main_read(struct view *view, char *line)
5398         static struct rev_graph *graph = graph_stacks;
5399         enum line_type type;
5400         struct commit *commit;
5402         if (!line) {
5403                 int i;
5405                 if (!view->lines && !view->parent)
5406                         die("No revisions match the given arguments.");
5407                 if (view->lines > 0) {
5408                         commit = view->line[view->lines - 1].data;
5409                         if (!*commit->author) {
5410                                 view->lines--;
5411                                 free(commit);
5412                                 graph->commit = NULL;
5413                         }
5414                 }
5415                 update_rev_graph(graph);
5417                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5418                         clear_rev_graph(&graph_stacks[i]);
5419                 return TRUE;
5420         }
5422         type = get_line_type(line);
5423         if (type == LINE_COMMIT) {
5424                 commit = calloc(1, sizeof(struct commit));
5425                 if (!commit)
5426                         return FALSE;
5428                 line += STRING_SIZE("commit ");
5429                 if (*line == '-') {
5430                         graph->boundary = 1;
5431                         line++;
5432                 }
5434                 string_copy_rev(commit->id, line);
5435                 commit->refs = get_refs(commit->id);
5436                 graph->commit = commit;
5437                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5439                 while ((line = strchr(line, ' '))) {
5440                         line++;
5441                         push_rev_graph(graph->parents, line);
5442                         commit->has_parents = TRUE;
5443                 }
5444                 return TRUE;
5445         }
5447         if (!view->lines)
5448                 return TRUE;
5449         commit = view->line[view->lines - 1].data;
5451         switch (type) {
5452         case LINE_PARENT:
5453                 if (commit->has_parents)
5454                         break;
5455                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5456                 break;
5458         case LINE_AUTHOR:
5459         {
5460                 /* Parse author lines where the name may be empty:
5461                  *      author  <email@address.tld> 1138474660 +0100
5462                  */
5463                 char *ident = line + STRING_SIZE("author ");
5464                 char *nameend = strchr(ident, '<');
5465                 char *emailend = strchr(ident, '>');
5467                 if (!nameend || !emailend)
5468                         break;
5470                 update_rev_graph(graph);
5471                 graph = graph->next;
5473                 *nameend = *emailend = 0;
5474                 ident = chomp_string(ident);
5475                 if (!*ident) {
5476                         ident = chomp_string(nameend + 1);
5477                         if (!*ident)
5478                                 ident = "Unknown";
5479                 }
5481                 string_ncopy(commit->author, ident, strlen(ident));
5483                 /* Parse epoch and timezone */
5484                 if (emailend[1] == ' ') {
5485                         char *secs = emailend + 2;
5486                         char *zone = strchr(secs, ' ');
5487                         time_t time = (time_t) atol(secs);
5489                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5490                                 long tz;
5492                                 zone++;
5493                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5494                                 tz += ('0' - zone[2]) * 60 * 60;
5495                                 tz += ('0' - zone[3]) * 60;
5496                                 tz += ('0' - zone[4]) * 60;
5498                                 if (zone[0] == '-')
5499                                         tz = -tz;
5501                                 time -= tz;
5502                         }
5504                         gmtime_r(&time, &commit->time);
5505                 }
5506                 break;
5507         }
5508         default:
5509                 /* Fill in the commit title if it has not already been set. */
5510                 if (commit->title[0])
5511                         break;
5513                 /* Require titles to start with a non-space character at the
5514                  * offset used by git log. */
5515                 if (strncmp(line, "    ", 4))
5516                         break;
5517                 line += 4;
5518                 /* Well, if the title starts with a whitespace character,
5519                  * try to be forgiving.  Otherwise we end up with no title. */
5520                 while (isspace(*line))
5521                         line++;
5522                 if (*line == '\0')
5523                         break;
5524                 /* FIXME: More graceful handling of titles; append "..." to
5525                  * shortened titles, etc. */
5527                 string_ncopy(commit->title, line, strlen(line));
5528         }
5530         return TRUE;
5533 static enum request
5534 main_request(struct view *view, enum request request, struct line *line)
5536         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5538         switch (request) {
5539         case REQ_ENTER:
5540                 open_view(view, REQ_VIEW_DIFF, flags);
5541                 break;
5542         case REQ_REFRESH:
5543                 load_refs();
5544                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5545                 break;
5546         default:
5547                 return request;
5548         }
5550         return REQ_NONE;
5553 static bool
5554 grep_refs(struct ref **refs, regex_t *regex)
5556         regmatch_t pmatch;
5557         size_t i = 0;
5559         if (!refs)
5560                 return FALSE;
5561         do {
5562                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5563                         return TRUE;
5564         } while (refs[i++]->next);
5566         return FALSE;
5569 static bool
5570 main_grep(struct view *view, struct line *line)
5572         struct commit *commit = line->data;
5573         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5574         char buf[DATE_COLS + 1];
5575         regmatch_t pmatch;
5577         for (state = S_TITLE; state < S_END; state++) {
5578                 char *text;
5580                 switch (state) {
5581                 case S_TITLE:   text = commit->title;   break;
5582                 case S_AUTHOR:
5583                         if (!opt_author)
5584                                 continue;
5585                         text = commit->author;
5586                         break;
5587                 case S_DATE:
5588                         if (!opt_date)
5589                                 continue;
5590                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5591                                 continue;
5592                         text = buf;
5593                         break;
5594                 case S_REFS:
5595                         if (!opt_show_refs)
5596                                 continue;
5597                         if (grep_refs(commit->refs, view->regex) == TRUE)
5598                                 return TRUE;
5599                         continue;
5600                 default:
5601                         return FALSE;
5602                 }
5604                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5605                         return TRUE;
5606         }
5608         return FALSE;
5611 static void
5612 main_select(struct view *view, struct line *line)
5614         struct commit *commit = line->data;
5616         string_copy_rev(view->ref, commit->id);
5617         string_copy_rev(ref_commit, view->ref);
5620 static struct view_ops main_ops = {
5621         "commit",
5622         main_argv,
5623         NULL,
5624         main_read,
5625         main_draw,
5626         main_request,
5627         main_grep,
5628         main_select,
5629 };
5632 /*
5633  * Unicode / UTF-8 handling
5634  *
5635  * NOTE: Much of the following code for dealing with unicode is derived from
5636  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5637  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5638  */
5640 /* I've (over)annotated a lot of code snippets because I am not entirely
5641  * confident that the approach taken by this small UTF-8 interface is correct.
5642  * --jonas */
5644 static inline int
5645 unicode_width(unsigned long c)
5647         if (c >= 0x1100 &&
5648            (c <= 0x115f                         /* Hangul Jamo */
5649             || c == 0x2329
5650             || c == 0x232a
5651             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5652                                                 /* CJK ... Yi */
5653             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5654             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5655             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5656             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5657             || (c >= 0xffe0  && c <= 0xffe6)
5658             || (c >= 0x20000 && c <= 0x2fffd)
5659             || (c >= 0x30000 && c <= 0x3fffd)))
5660                 return 2;
5662         if (c == '\t')
5663                 return opt_tab_size;
5665         return 1;
5668 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5669  * Illegal bytes are set one. */
5670 static const unsigned char utf8_bytes[256] = {
5671         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5672         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5673         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5674         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5675         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,
5676         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,
5677         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,
5678         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,
5679 };
5681 /* Decode UTF-8 multi-byte representation into a unicode character. */
5682 static inline unsigned long
5683 utf8_to_unicode(const char *string, size_t length)
5685         unsigned long unicode;
5687         switch (length) {
5688         case 1:
5689                 unicode  =   string[0];
5690                 break;
5691         case 2:
5692                 unicode  =  (string[0] & 0x1f) << 6;
5693                 unicode +=  (string[1] & 0x3f);
5694                 break;
5695         case 3:
5696                 unicode  =  (string[0] & 0x0f) << 12;
5697                 unicode += ((string[1] & 0x3f) << 6);
5698                 unicode +=  (string[2] & 0x3f);
5699                 break;
5700         case 4:
5701                 unicode  =  (string[0] & 0x0f) << 18;
5702                 unicode += ((string[1] & 0x3f) << 12);
5703                 unicode += ((string[2] & 0x3f) << 6);
5704                 unicode +=  (string[3] & 0x3f);
5705                 break;
5706         case 5:
5707                 unicode  =  (string[0] & 0x0f) << 24;
5708                 unicode += ((string[1] & 0x3f) << 18);
5709                 unicode += ((string[2] & 0x3f) << 12);
5710                 unicode += ((string[3] & 0x3f) << 6);
5711                 unicode +=  (string[4] & 0x3f);
5712                 break;
5713         case 6:
5714                 unicode  =  (string[0] & 0x01) << 30;
5715                 unicode += ((string[1] & 0x3f) << 24);
5716                 unicode += ((string[2] & 0x3f) << 18);
5717                 unicode += ((string[3] & 0x3f) << 12);
5718                 unicode += ((string[4] & 0x3f) << 6);
5719                 unicode +=  (string[5] & 0x3f);
5720                 break;
5721         default:
5722                 die("Invalid unicode length");
5723         }
5725         /* Invalid characters could return the special 0xfffd value but NUL
5726          * should be just as good. */
5727         return unicode > 0xffff ? 0 : unicode;
5730 /* Calculates how much of string can be shown within the given maximum width
5731  * and sets trimmed parameter to non-zero value if all of string could not be
5732  * shown. If the reserve flag is TRUE, it will reserve at least one
5733  * trailing character, which can be useful when drawing a delimiter.
5734  *
5735  * Returns the number of bytes to output from string to satisfy max_width. */
5736 static size_t
5737 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5739         const char *start = string;
5740         const char *end = strchr(string, '\0');
5741         unsigned char last_bytes = 0;
5742         size_t last_ucwidth = 0;
5744         *width = 0;
5745         *trimmed = 0;
5747         while (string < end) {
5748                 int c = *(unsigned char *) string;
5749                 unsigned char bytes = utf8_bytes[c];
5750                 size_t ucwidth;
5751                 unsigned long unicode;
5753                 if (string + bytes > end)
5754                         break;
5756                 /* Change representation to figure out whether
5757                  * it is a single- or double-width character. */
5759                 unicode = utf8_to_unicode(string, bytes);
5760                 /* FIXME: Graceful handling of invalid unicode character. */
5761                 if (!unicode)
5762                         break;
5764                 ucwidth = unicode_width(unicode);
5765                 *width  += ucwidth;
5766                 if (*width > max_width) {
5767                         *trimmed = 1;
5768                         *width -= ucwidth;
5769                         if (reserve && *width == max_width) {
5770                                 string -= last_bytes;
5771                                 *width -= last_ucwidth;
5772                         }
5773                         break;
5774                 }
5776                 string  += bytes;
5777                 last_bytes = bytes;
5778                 last_ucwidth = ucwidth;
5779         }
5781         return string - start;
5785 /*
5786  * Status management
5787  */
5789 /* Whether or not the curses interface has been initialized. */
5790 static bool cursed = FALSE;
5792 /* The status window is used for polling keystrokes. */
5793 static WINDOW *status_win;
5795 static bool status_empty = TRUE;
5797 /* Update status and title window. */
5798 static void
5799 report(const char *msg, ...)
5801         struct view *view = display[current_view];
5803         if (input_mode)
5804                 return;
5806         if (!view) {
5807                 char buf[SIZEOF_STR];
5808                 va_list args;
5810                 va_start(args, msg);
5811                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5812                         buf[sizeof(buf) - 1] = 0;
5813                         buf[sizeof(buf) - 2] = '.';
5814                         buf[sizeof(buf) - 3] = '.';
5815                         buf[sizeof(buf) - 4] = '.';
5816                 }
5817                 va_end(args);
5818                 die("%s", buf);
5819         }
5821         if (!status_empty || *msg) {
5822                 va_list args;
5824                 va_start(args, msg);
5826                 wmove(status_win, 0, 0);
5827                 if (*msg) {
5828                         vwprintw(status_win, msg, args);
5829                         status_empty = FALSE;
5830                 } else {
5831                         status_empty = TRUE;
5832                 }
5833                 wclrtoeol(status_win);
5834                 wrefresh(status_win);
5836                 va_end(args);
5837         }
5839         update_view_title(view);
5840         update_display_cursor(view);
5843 /* Controls when nodelay should be in effect when polling user input. */
5844 static void
5845 set_nonblocking_input(bool loading)
5847         static unsigned int loading_views;
5849         if ((loading == FALSE && loading_views-- == 1) ||
5850             (loading == TRUE  && loading_views++ == 0))
5851                 nodelay(status_win, loading);
5854 static void
5855 init_display(void)
5857         int x, y;
5859         /* Initialize the curses library */
5860         if (isatty(STDIN_FILENO)) {
5861                 cursed = !!initscr();
5862                 opt_tty = stdin;
5863         } else {
5864                 /* Leave stdin and stdout alone when acting as a pager. */
5865                 opt_tty = fopen("/dev/tty", "r+");
5866                 if (!opt_tty)
5867                         die("Failed to open /dev/tty");
5868                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5869         }
5871         if (!cursed)
5872                 die("Failed to initialize curses");
5874         nonl();         /* Tell curses not to do NL->CR/NL on output */
5875         cbreak();       /* Take input chars one at a time, no wait for \n */
5876         noecho();       /* Don't echo input */
5877         leaveok(stdscr, TRUE);
5879         if (has_colors())
5880                 init_colors();
5882         getmaxyx(stdscr, y, x);
5883         status_win = newwin(1, 0, y - 1, 0);
5884         if (!status_win)
5885                 die("Failed to create status window");
5887         /* Enable keyboard mapping */
5888         keypad(status_win, TRUE);
5889         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5891         TABSIZE = opt_tab_size;
5892         if (opt_line_graphics) {
5893                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5894         }
5897 static bool
5898 prompt_yesno(const char *prompt)
5900         enum { WAIT, STOP, CANCEL  } status = WAIT;
5901         bool answer = FALSE;
5903         while (status == WAIT) {
5904                 struct view *view;
5905                 int i, key;
5907                 input_mode = TRUE;
5909                 foreach_view (view, i)
5910                         update_view(view);
5912                 input_mode = FALSE;
5914                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5915                 wclrtoeol(status_win);
5917                 /* Refresh, accept single keystroke of input */
5918                 key = wgetch(status_win);
5919                 switch (key) {
5920                 case ERR:
5921                         break;
5923                 case 'y':
5924                 case 'Y':
5925                         answer = TRUE;
5926                         status = STOP;
5927                         break;
5929                 case KEY_ESC:
5930                 case KEY_RETURN:
5931                 case KEY_ENTER:
5932                 case KEY_BACKSPACE:
5933                 case 'n':
5934                 case 'N':
5935                 case '\n':
5936                 default:
5937                         answer = FALSE;
5938                         status = CANCEL;
5939                 }
5940         }
5942         /* Clear the status window */
5943         status_empty = FALSE;
5944         report("");
5946         return answer;
5949 static char *
5950 read_prompt(const char *prompt)
5952         enum { READING, STOP, CANCEL } status = READING;
5953         static char buf[SIZEOF_STR];
5954         int pos = 0;
5956         while (status == READING) {
5957                 struct view *view;
5958                 int i, key;
5960                 input_mode = TRUE;
5962                 foreach_view (view, i)
5963                         update_view(view);
5965                 input_mode = FALSE;
5967                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5968                 wclrtoeol(status_win);
5970                 /* Refresh, accept single keystroke of input */
5971                 key = wgetch(status_win);
5972                 switch (key) {
5973                 case KEY_RETURN:
5974                 case KEY_ENTER:
5975                 case '\n':
5976                         status = pos ? STOP : CANCEL;
5977                         break;
5979                 case KEY_BACKSPACE:
5980                         if (pos > 0)
5981                                 pos--;
5982                         else
5983                                 status = CANCEL;
5984                         break;
5986                 case KEY_ESC:
5987                         status = CANCEL;
5988                         break;
5990                 case ERR:
5991                         break;
5993                 default:
5994                         if (pos >= sizeof(buf)) {
5995                                 report("Input string too long");
5996                                 return NULL;
5997                         }
5999                         if (isprint(key))
6000                                 buf[pos++] = (char) key;
6001                 }
6002         }
6004         /* Clear the status window */
6005         status_empty = FALSE;
6006         report("");
6008         if (status == CANCEL)
6009                 return NULL;
6011         buf[pos++] = 0;
6013         return buf;
6016 /*
6017  * Repository properties
6018  */
6020 static int
6021 git_properties(const char **argv, const char *separators,
6022                int (*read_property)(char *, size_t, char *, size_t))
6024         struct io io = {};
6026         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6027                 return read_properties(&io, separators, read_property);
6028         return ERR;
6031 static struct ref *refs = NULL;
6032 static size_t refs_alloc = 0;
6033 static size_t refs_size = 0;
6035 /* Id <-> ref store */
6036 static struct ref ***id_refs = NULL;
6037 static size_t id_refs_alloc = 0;
6038 static size_t id_refs_size = 0;
6040 static int
6041 compare_refs(const void *ref1_, const void *ref2_)
6043         const struct ref *ref1 = *(const struct ref **)ref1_;
6044         const struct ref *ref2 = *(const struct ref **)ref2_;
6046         if (ref1->tag != ref2->tag)
6047                 return ref2->tag - ref1->tag;
6048         if (ref1->ltag != ref2->ltag)
6049                 return ref2->ltag - ref2->ltag;
6050         if (ref1->head != ref2->head)
6051                 return ref2->head - ref1->head;
6052         if (ref1->tracked != ref2->tracked)
6053                 return ref2->tracked - ref1->tracked;
6054         if (ref1->remote != ref2->remote)
6055                 return ref2->remote - ref1->remote;
6056         return strcmp(ref1->name, ref2->name);
6059 static struct ref **
6060 get_refs(const char *id)
6062         struct ref ***tmp_id_refs;
6063         struct ref **ref_list = NULL;
6064         size_t ref_list_alloc = 0;
6065         size_t ref_list_size = 0;
6066         size_t i;
6068         for (i = 0; i < id_refs_size; i++)
6069                 if (!strcmp(id, id_refs[i][0]->id))
6070                         return id_refs[i];
6072         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6073                                     sizeof(*id_refs));
6074         if (!tmp_id_refs)
6075                 return NULL;
6077         id_refs = tmp_id_refs;
6079         for (i = 0; i < refs_size; i++) {
6080                 struct ref **tmp;
6082                 if (strcmp(id, refs[i].id))
6083                         continue;
6085                 tmp = realloc_items(ref_list, &ref_list_alloc,
6086                                     ref_list_size + 1, sizeof(*ref_list));
6087                 if (!tmp) {
6088                         if (ref_list)
6089                                 free(ref_list);
6090                         return NULL;
6091                 }
6093                 ref_list = tmp;
6094                 ref_list[ref_list_size] = &refs[i];
6095                 /* XXX: The properties of the commit chains ensures that we can
6096                  * safely modify the shared ref. The repo references will
6097                  * always be similar for the same id. */
6098                 ref_list[ref_list_size]->next = 1;
6100                 ref_list_size++;
6101         }
6103         if (ref_list) {
6104                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6105                 ref_list[ref_list_size - 1]->next = 0;
6106                 id_refs[id_refs_size++] = ref_list;
6107         }
6109         return ref_list;
6112 static int
6113 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6115         struct ref *ref;
6116         bool tag = FALSE;
6117         bool ltag = FALSE;
6118         bool remote = FALSE;
6119         bool tracked = FALSE;
6120         bool check_replace = FALSE;
6121         bool head = FALSE;
6123         if (!prefixcmp(name, "refs/tags/")) {
6124                 if (!suffixcmp(name, namelen, "^{}")) {
6125                         namelen -= 3;
6126                         name[namelen] = 0;
6127                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6128                                 check_replace = TRUE;
6129                 } else {
6130                         ltag = TRUE;
6131                 }
6133                 tag = TRUE;
6134                 namelen -= STRING_SIZE("refs/tags/");
6135                 name    += STRING_SIZE("refs/tags/");
6137         } else if (!prefixcmp(name, "refs/remotes/")) {
6138                 remote = TRUE;
6139                 namelen -= STRING_SIZE("refs/remotes/");
6140                 name    += STRING_SIZE("refs/remotes/");
6141                 tracked  = !strcmp(opt_remote, name);
6143         } else if (!prefixcmp(name, "refs/heads/")) {
6144                 namelen -= STRING_SIZE("refs/heads/");
6145                 name    += STRING_SIZE("refs/heads/");
6146                 head     = !strncmp(opt_head, name, namelen);
6148         } else if (!strcmp(name, "HEAD")) {
6149                 string_ncopy(opt_head_rev, id, idlen);
6150                 return OK;
6151         }
6153         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6154                 /* it's an annotated tag, replace the previous sha1 with the
6155                  * resolved commit id; relies on the fact git-ls-remote lists
6156                  * the commit id of an annotated tag right before the commit id
6157                  * it points to. */
6158                 refs[refs_size - 1].ltag = ltag;
6159                 string_copy_rev(refs[refs_size - 1].id, id);
6161                 return OK;
6162         }
6163         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6164         if (!refs)
6165                 return ERR;
6167         ref = &refs[refs_size++];
6168         ref->name = malloc(namelen + 1);
6169         if (!ref->name)
6170                 return ERR;
6172         strncpy(ref->name, name, namelen);
6173         ref->name[namelen] = 0;
6174         ref->head = head;
6175         ref->tag = tag;
6176         ref->ltag = ltag;
6177         ref->remote = remote;
6178         ref->tracked = tracked;
6179         string_copy_rev(ref->id, id);
6181         return OK;
6184 static int
6185 load_refs(void)
6187         static const char *ls_remote_argv[SIZEOF_ARG] = {
6188                 "git", "ls-remote", ".", NULL
6189         };
6190         static bool init = FALSE;
6192         if (!init) {
6193                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6194                 init = TRUE;
6195         }
6197         if (!*opt_git_dir)
6198                 return OK;
6200         while (refs_size > 0)
6201                 free(refs[--refs_size].name);
6202         while (id_refs_size > 0)
6203                 free(id_refs[--id_refs_size]);
6205         return git_properties(ls_remote_argv, "\t", read_ref);
6208 static int
6209 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6211         if (!strcmp(name, "i18n.commitencoding"))
6212                 string_ncopy(opt_encoding, value, valuelen);
6214         if (!strcmp(name, "core.editor"))
6215                 string_ncopy(opt_editor, value, valuelen);
6217         /* branch.<head>.remote */
6218         if (*opt_head &&
6219             !strncmp(name, "branch.", 7) &&
6220             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6221             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6222                 string_ncopy(opt_remote, value, valuelen);
6224         if (*opt_head && *opt_remote &&
6225             !strncmp(name, "branch.", 7) &&
6226             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6227             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6228                 size_t from = strlen(opt_remote);
6230                 if (!prefixcmp(value, "refs/heads/")) {
6231                         value += STRING_SIZE("refs/heads/");
6232                         valuelen -= STRING_SIZE("refs/heads/");
6233                 }
6235                 if (!string_format_from(opt_remote, &from, "/%s", value))
6236                         opt_remote[0] = 0;
6237         }
6239         return OK;
6242 static int
6243 load_git_config(void)
6245         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6247         return git_properties(config_list_argv, "=", read_repo_config_option);
6250 static int
6251 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6253         if (!opt_git_dir[0]) {
6254                 string_ncopy(opt_git_dir, name, namelen);
6256         } else if (opt_is_inside_work_tree == -1) {
6257                 /* This can be 3 different values depending on the
6258                  * version of git being used. If git-rev-parse does not
6259                  * understand --is-inside-work-tree it will simply echo
6260                  * the option else either "true" or "false" is printed.
6261                  * Default to true for the unknown case. */
6262                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6263         } else {
6264                 string_ncopy(opt_cdup, name, namelen);
6265         }
6267         return OK;
6270 static int
6271 load_repo_info(void)
6273         const char *head_argv[] = {
6274                 "git", "symbolic-ref", "HEAD", NULL
6275         };
6276         const char *rev_parse_argv[] = {
6277                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6278                         "--show-cdup", NULL
6279         };
6281         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6282                 chomp_string(opt_head);
6283                 if (!prefixcmp(opt_head, "refs/heads/")) {
6284                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6286                         memmove(opt_head, offset, strlen(offset) + 1);
6287                 }
6288         }
6290         return git_properties(rev_parse_argv, "=", read_repo_info);
6293 static int
6294 read_properties(struct io *io, const char *separators,
6295                 int (*read_property)(char *, size_t, char *, size_t))
6297         char *name;
6298         int state = OK;
6300         if (!start_io(io))
6301                 return ERR;
6303         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6304                 char *value;
6305                 size_t namelen;
6306                 size_t valuelen;
6308                 name = chomp_string(name);
6309                 namelen = strcspn(name, separators);
6311                 if (name[namelen]) {
6312                         name[namelen] = 0;
6313                         value = chomp_string(name + namelen + 1);
6314                         valuelen = strlen(value);
6316                 } else {
6317                         value = "";
6318                         valuelen = 0;
6319                 }
6321                 state = read_property(name, namelen, value, valuelen);
6322         }
6324         if (state != ERR && io_error(io))
6325                 state = ERR;
6326         done_io(io);
6328         return state;
6332 /*
6333  * Main
6334  */
6336 static void __NORETURN
6337 quit(int sig)
6339         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6340         if (cursed)
6341                 endwin();
6342         exit(0);
6345 static void __NORETURN
6346 die(const char *err, ...)
6348         va_list args;
6350         endwin();
6352         va_start(args, err);
6353         fputs("tig: ", stderr);
6354         vfprintf(stderr, err, args);
6355         fputs("\n", stderr);
6356         va_end(args);
6358         exit(1);
6361 static void
6362 warn(const char *msg, ...)
6364         va_list args;
6366         va_start(args, msg);
6367         fputs("tig warning: ", stderr);
6368         vfprintf(stderr, msg, args);
6369         fputs("\n", stderr);
6370         va_end(args);
6373 int
6374 main(int argc, const char *argv[])
6376         const char **run_argv = NULL;
6377         struct view *view;
6378         enum request request;
6379         size_t i;
6381         signal(SIGINT, quit);
6383         if (setlocale(LC_ALL, "")) {
6384                 char *codeset = nl_langinfo(CODESET);
6386                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6387         }
6389         if (load_repo_info() == ERR)
6390                 die("Failed to load repo info.");
6392         if (load_options() == ERR)
6393                 die("Failed to load user config.");
6395         if (load_git_config() == ERR)
6396                 die("Failed to load repo config.");
6398         request = parse_options(argc, argv, &run_argv);
6399         if (request == REQ_NONE)
6400                 return 0;
6402         /* Require a git repository unless when running in pager mode. */
6403         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6404                 die("Not a git repository");
6406         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6407                 opt_utf8 = FALSE;
6409         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6410                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6411                 if (opt_iconv == ICONV_NONE)
6412                         die("Failed to initialize character set conversion");
6413         }
6415         if (load_refs() == ERR)
6416                 die("Failed to load refs.");
6418         foreach_view (view, i)
6419                 argv_from_env(view->ops->argv, view->cmd_env);
6421         init_display();
6423         if (request == REQ_VIEW_PAGER || run_argv) {
6424                 if (request == REQ_VIEW_PAGER)
6425                         io_open(&VIEW(request)->io, "");
6426                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6427                         die("Failed to format arguments");
6428                 open_view(NULL, request, OPEN_PREPARED);
6429                 request = REQ_NONE;
6430         }
6432         while (view_driver(display[current_view], request)) {
6433                 int key;
6434                 int i;
6436                 foreach_view (view, i)
6437                         update_view(view);
6438                 view = display[current_view];
6440                 /* Refresh, accept single keystroke of input */
6441                 key = wgetch(status_win);
6443                 /* wgetch() with nodelay() enabled returns ERR when there's no
6444                  * input. */
6445                 if (key == ERR) {
6446                         request = REQ_NONE;
6447                         continue;
6448                 }
6450                 request = get_keybinding(view->keymap, key);
6452                 /* Some low-level request handling. This keeps access to
6453                  * status_win restricted. */
6454                 switch (request) {
6455                 case REQ_PROMPT:
6456                 {
6457                         char *cmd = read_prompt(":");
6459                         if (cmd) {
6460                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6461                                 const char *argv[SIZEOF_ARG] = { "git" };
6462                                 int argc = 1;
6464                                 /* When running random commands, initially show the
6465                                  * command in the title. However, it maybe later be
6466                                  * overwritten if a commit line is selected. */
6467                                 string_ncopy(next->ref, cmd, strlen(cmd));
6469                                 if (!argv_from_string(argv, &argc, cmd)) {
6470                                         report("Too many arguments");
6471                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6472                                         report("Failed to format command");
6473                                 } else {
6474                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6475                                 }
6476                         }
6478                         request = REQ_NONE;
6479                         break;
6480                 }
6481                 case REQ_SEARCH:
6482                 case REQ_SEARCH_BACK:
6483                 {
6484                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6485                         char *search = read_prompt(prompt);
6487                         if (search)
6488                                 string_ncopy(opt_search, search, strlen(search));
6489                         else
6490                                 request = REQ_NONE;
6491                         break;
6492                 }
6493                 case REQ_SCREEN_RESIZE:
6494                 {
6495                         int height, width;
6497                         getmaxyx(stdscr, height, width);
6499                         /* Resize the status view and let the view driver take
6500                          * care of resizing the displayed views. */
6501                         wresize(status_win, 1, width);
6502                         mvwin(status_win, height - 1, 0);
6503                         wrefresh(status_win);
6504                         break;
6505                 }
6506                 default:
6507                         break;
6508                 }
6509         }
6511         quit(0);
6513         return 0;