Code

eab037fb7bcceef25029a09eae6674b1c26218a4
[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 <unistd.h>
38 #include <time.h>
39 #include <fcntl.h>
41 #include <regex.h>
43 #include <locale.h>
44 #include <langinfo.h>
45 #include <iconv.h>
47 /* ncurses(3): Must be defined to have extended wide-character functions. */
48 #define _XOPEN_SOURCE_EXTENDED
50 #ifdef HAVE_NCURSESW_NCURSES_H
51 #include <ncursesw/ncurses.h>
52 #else
53 #ifdef HAVE_NCURSES_NCURSES_H
54 #include <ncurses/ncurses.h>
55 #else
56 #include <ncurses.h>
57 #endif
58 #endif
60 #if __GNUC__ >= 3
61 #define __NORETURN __attribute__((__noreturn__))
62 #else
63 #define __NORETURN
64 #endif
66 static void __NORETURN die(const char *err, ...);
67 static void warn(const char *msg, ...);
68 static void report(const char *msg, ...);
69 static void set_nonblocking_input(bool loading);
70 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
71 static bool prompt_yesno(const char *prompt);
72 static int load_refs(void);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB         '\t'
125 #define KEY_RETURN      '\r'
126 #define KEY_ESC         27
129 struct ref {
130         char *name;             /* Ref name; tag or head names are shortened. */
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         unsigned int next:1;    /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143         FORMAT_ALL,             /* Perform replacement in all arguments. */
144         FORMAT_DASH,            /* Perform replacement up until "--". */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
174 /*
175  * String helpers
176  */
178 static inline void
179 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
181         if (srclen > dstlen - 1)
182                 srclen = dstlen - 1;
184         strncpy(dst, src, srclen);
185         dst[srclen] = 0;
188 /* Shorthands for safely copying into a fixed buffer. */
190 #define string_copy(dst, src) \
191         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
193 #define string_ncopy(dst, src, srclen) \
194         string_ncopy_do(dst, sizeof(dst), src, srclen)
196 #define string_copy_rev(dst, src) \
197         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
199 #define string_add(dst, from, src) \
200         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
202 static char *
203 chomp_string(char *name)
205         int namelen;
207         while (isspace(*name))
208                 name++;
210         namelen = strlen(name) - 1;
211         while (namelen > 0 && isspace(name[namelen]))
212                 name[namelen--] = 0;
214         return name;
217 static bool
218 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
220         va_list args;
221         size_t pos = bufpos ? *bufpos : 0;
223         va_start(args, fmt);
224         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
225         va_end(args);
227         if (bufpos)
228                 *bufpos = pos;
230         return pos >= bufsize ? FALSE : TRUE;
233 #define string_format(buf, fmt, args...) \
234         string_nformat(buf, sizeof(buf), NULL, fmt, args)
236 #define string_format_from(buf, from, fmt, args...) \
237         string_nformat(buf, sizeof(buf), from, fmt, args)
239 static int
240 string_enum_compare(const char *str1, const char *str2, int len)
242         size_t i;
244 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
246         /* Diff-Header == DIFF_HEADER */
247         for (i = 0; i < len; i++) {
248                 if (toupper(str1[i]) == toupper(str2[i]))
249                         continue;
251                 if (string_enum_sep(str1[i]) &&
252                     string_enum_sep(str2[i]))
253                         continue;
255                 return str1[i] - str2[i];
256         }
258         return 0;
261 #define prefixcmp(str1, str2) \
262         strncmp(str1, str2, STRING_SIZE(str2))
264 static inline int
265 suffixcmp(const char *str, int slen, const char *suffix)
267         size_t len = slen >= 0 ? slen : strlen(str);
268         size_t suffixlen = strlen(suffix);
270         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
274 static bool
275 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
277         int valuelen;
279         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
280                 bool advance = cmd[valuelen] != 0;
282                 cmd[valuelen] = 0;
283                 argv[(*argc)++] = chomp_string(cmd);
284                 cmd += valuelen + advance;
285         }
287         if (*argc < SIZEOF_ARG)
288                 argv[*argc] = NULL;
289         return *argc < SIZEOF_ARG;
292 static void
293 argv_from_env(const char **argv, const char *name)
295         char *env = argv ? getenv(name) : NULL;
296         int argc = 0;
298         if (env && *env)
299                 env = strdup(env);
300         if (env && !argv_from_string(argv, &argc, env))
301                 die("Too many arguments in the `%s` environment variable", name);
305 /*
306  * Executing external commands.
307  */
309 enum io_type {
310         IO_FD,                  /* File descriptor based IO. */
311         IO_BG,                  /* Execute command in the background. */
312         IO_FG,                  /* Execute command with same std{in,out,err}. */
313         IO_RD,                  /* Read only fork+exec IO. */
314         IO_WR,                  /* Write only fork+exec IO. */
315 };
317 struct io {
318         enum io_type type;      /* The requested type of pipe. */
319         const char *dir;        /* Directory from which to execute. */
320         pid_t pid;              /* Pipe for reading or writing. */
321         int pipe;               /* Pipe end for reading or writing. */
322         int error;              /* Error status. */
323         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
324         char *buf;              /* Read buffer. */
325         size_t bufalloc;        /* Allocated buffer size. */
326         size_t bufsize;         /* Buffer content size. */
327         char *bufpos;           /* Current buffer position. */
328         unsigned int eof:1;     /* Has end of file been reached. */
329 };
331 static void
332 reset_io(struct io *io)
334         io->pipe = -1;
335         io->pid = 0;
336         io->buf = io->bufpos = NULL;
337         io->bufalloc = io->bufsize = 0;
338         io->error = 0;
339         io->eof = 0;
342 static void
343 init_io(struct io *io, const char *dir, enum io_type type)
345         reset_io(io);
346         io->type = type;
347         io->dir = dir;
350 static bool
351 init_io_rd(struct io *io, const char *argv[], const char *dir,
352                 enum format_flags flags)
354         init_io(io, dir, IO_RD);
355         return format_argv(io->argv, argv, flags);
358 static bool
359 io_open(struct io *io, const char *name)
361         init_io(io, NULL, IO_FD);
362         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
363         return io->pipe != -1;
366 static bool
367 kill_io(struct io *io)
369         return kill(io->pid, SIGKILL) != -1;
372 static bool
373 done_io(struct io *io)
375         pid_t pid = io->pid;
377         if (io->pipe != -1)
378                 close(io->pipe);
379         free(io->buf);
380         reset_io(io);
382         while (pid > 0) {
383                 int status;
384                 pid_t waiting = waitpid(pid, &status, 0);
386                 if (waiting < 0) {
387                         if (errno == EINTR)
388                                 continue;
389                         report("waitpid failed (%s)", strerror(errno));
390                         return FALSE;
391                 }
393                 return waiting == pid &&
394                        !WIFSIGNALED(status) &&
395                        WIFEXITED(status) &&
396                        !WEXITSTATUS(status);
397         }
399         return TRUE;
402 static bool
403 start_io(struct io *io)
405         int pipefds[2] = { -1, -1 };
407         if (io->type == IO_FD)
408                 return TRUE;
410         if ((io->type == IO_RD || io->type == IO_WR) &&
411             pipe(pipefds) < 0)
412                 return FALSE;
414         if ((io->pid = fork())) {
415                 if (pipefds[!(io->type == IO_WR)] != -1)
416                         close(pipefds[!(io->type == IO_WR)]);
417                 if (io->pid != -1) {
418                         io->pipe = pipefds[!!(io->type == IO_WR)];
419                         return TRUE;
420                 }
422         } else {
423                 if (io->type != IO_FG) {
424                         int devnull = open("/dev/null", O_RDWR);
425                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
426                         int writefd = io->type == IO_RD ? pipefds[1] : devnull;
428                         dup2(readfd,  STDIN_FILENO);
429                         dup2(writefd, STDOUT_FILENO);
430                         dup2(devnull, STDERR_FILENO);
432                         close(devnull);
433                         if (pipefds[0] != -1)
434                                 close(pipefds[0]);
435                         if (pipefds[1] != -1)
436                                 close(pipefds[1]);
437                 }
439                 if (io->dir && *io->dir && chdir(io->dir) == -1)
440                         die("Failed to change directory: %s", strerror(errno));
442                 execvp(io->argv[0], (char *const*) io->argv);
443                 die("Failed to execute program: %s", strerror(errno));
444         }
446         if (pipefds[!!(io->type == IO_WR)] != -1)
447                 close(pipefds[!!(io->type == IO_WR)]);
448         return FALSE;
451 static bool
452 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
454         init_io(io, dir, type);
455         if (!format_argv(io->argv, argv, FORMAT_NONE))
456                 return FALSE;
457         return start_io(io);
460 static int
461 run_io_do(struct io *io)
463         return start_io(io) && done_io(io);
466 static int
467 run_io_bg(const char **argv)
469         struct io io = {};
471         init_io(&io, NULL, IO_BG);
472         if (!format_argv(io.argv, argv, FORMAT_NONE))
473                 return FALSE;
474         return run_io_do(&io);
477 static bool
478 run_io_fg(const char **argv, const char *dir)
480         struct io io = {};
482         init_io(&io, dir, IO_FG);
483         if (!format_argv(io.argv, argv, FORMAT_NONE))
484                 return FALSE;
485         return run_io_do(&io);
488 static bool
489 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
491         return init_io_rd(io, argv, NULL, flags) && start_io(io);
494 static bool
495 io_eof(struct io *io)
497         return io->eof;
500 static int
501 io_error(struct io *io)
503         return io->error;
506 static bool
507 io_strerror(struct io *io)
509         return strerror(io->error);
512 static ssize_t
513 io_read(struct io *io, void *buf, size_t bufsize)
515         do {
516                 ssize_t readsize = read(io->pipe, buf, bufsize);
518                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
519                         continue;
520                 else if (readsize == -1)
521                         io->error = errno;
522                 else if (readsize == 0)
523                         io->eof = 1;
524                 return readsize;
525         } while (1);
528 static char *
529 io_gets(struct io *io)
531         char *eol;
532         ssize_t readsize;
534         if (!io->buf) {
535                 io->buf = io->bufpos = malloc(BUFSIZ);
536                 if (!io->buf)
537                         return NULL;
538                 io->bufalloc = BUFSIZ;
539                 io->bufsize = 0;
540         }
542         while (TRUE) {
543                 if (io->bufsize > 0) {
544                         eol = memchr(io->bufpos, '\n', io->bufsize);
545                         if (eol) {
546                                 char *line = io->bufpos;
548                                 *eol = 0;
549                                 io->bufpos = eol + 1;
550                                 io->bufsize -= io->bufpos - line;
551                                 return line;
552                         }
553                 }
555                 if (io_eof(io)) {
556                         if (io->bufsize) {
557                                 io->bufpos[io->bufsize] = 0;
558                                 io->bufsize = 0;
559                                 return io->bufpos;
560                         }
561                         return NULL;
562                 }
564                 if (io->bufsize > 0 && io->bufpos > io->buf)
565                         memmove(io->buf, io->bufpos, io->bufsize);
567                 io->bufpos = io->buf;
568                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
569                 if (io_error(io))
570                         return NULL;
571                 io->bufsize += readsize;
572         }
575 static bool
576 io_write(struct io *io, const void *buf, size_t bufsize)
578         size_t written = 0;
580         while (!io_error(io) && written < bufsize) {
581                 ssize_t size;
583                 size = write(io->pipe, buf + written, bufsize - written);
584                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
585                         continue;
586                 else if (size == -1)
587                         io->error = errno;
588                 else
589                         written += size;
590         }
592         return written == bufsize;
595 static bool
596 run_io_buf(const char **argv, char buf[], size_t bufsize)
598         struct io io = {};
599         bool error;
601         if (!run_io_rd(&io, argv, FORMAT_NONE))
602                 return FALSE;
604         io.buf = io.bufpos = buf;
605         io.bufalloc = bufsize;
606         error = !io_gets(&io) && io_error(&io);
607         io.buf = NULL;
609         return done_io(&io) || error;
612 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
614 /*
615  * User requests
616  */
618 #define REQ_INFO \
619         /* XXX: Keep the view request first and in sync with views[]. */ \
620         REQ_GROUP("View switching") \
621         REQ_(VIEW_MAIN,         "Show main view"), \
622         REQ_(VIEW_DIFF,         "Show diff view"), \
623         REQ_(VIEW_LOG,          "Show log view"), \
624         REQ_(VIEW_TREE,         "Show tree view"), \
625         REQ_(VIEW_BLOB,         "Show blob view"), \
626         REQ_(VIEW_BLAME,        "Show blame view"), \
627         REQ_(VIEW_HELP,         "Show help page"), \
628         REQ_(VIEW_PAGER,        "Show pager view"), \
629         REQ_(VIEW_STATUS,       "Show status view"), \
630         REQ_(VIEW_STAGE,        "Show stage view"), \
631         \
632         REQ_GROUP("View manipulation") \
633         REQ_(ENTER,             "Enter current line and scroll"), \
634         REQ_(NEXT,              "Move to next"), \
635         REQ_(PREVIOUS,          "Move to previous"), \
636         REQ_(VIEW_NEXT,         "Move focus to next view"), \
637         REQ_(REFRESH,           "Reload and refresh"), \
638         REQ_(MAXIMIZE,          "Maximize the current view"), \
639         REQ_(VIEW_CLOSE,        "Close the current view"), \
640         REQ_(QUIT,              "Close all views and quit"), \
641         \
642         REQ_GROUP("View specific requests") \
643         REQ_(STATUS_UPDATE,     "Update file status"), \
644         REQ_(STATUS_REVERT,     "Revert file changes"), \
645         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
646         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
647         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
648         \
649         REQ_GROUP("Cursor navigation") \
650         REQ_(MOVE_UP,           "Move cursor one line up"), \
651         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
652         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
653         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
654         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
655         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
656         \
657         REQ_GROUP("Scrolling") \
658         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
659         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
660         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
661         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
662         \
663         REQ_GROUP("Searching") \
664         REQ_(SEARCH,            "Search the view"), \
665         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
666         REQ_(FIND_NEXT,         "Find next search match"), \
667         REQ_(FIND_PREV,         "Find previous search match"), \
668         \
669         REQ_GROUP("Option manipulation") \
670         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
671         REQ_(TOGGLE_DATE,       "Toggle date display"), \
672         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
673         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
674         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
675         \
676         REQ_GROUP("Misc") \
677         REQ_(PROMPT,            "Bring up the prompt"), \
678         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
679         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
680         REQ_(SHOW_VERSION,      "Show version information"), \
681         REQ_(STOP_LOADING,      "Stop all loading views"), \
682         REQ_(EDIT,              "Open in editor"), \
683         REQ_(NONE,              "Do nothing")
686 /* User action requests. */
687 enum request {
688 #define REQ_GROUP(help)
689 #define REQ_(req, help) REQ_##req
691         /* Offset all requests to avoid conflicts with ncurses getch values. */
692         REQ_OFFSET = KEY_MAX + 1,
693         REQ_INFO
695 #undef  REQ_GROUP
696 #undef  REQ_
697 };
699 struct request_info {
700         enum request request;
701         const char *name;
702         int namelen;
703         const char *help;
704 };
706 static struct request_info req_info[] = {
707 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
708 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
709         REQ_INFO
710 #undef  REQ_GROUP
711 #undef  REQ_
712 };
714 static enum request
715 get_request(const char *name)
717         int namelen = strlen(name);
718         int i;
720         for (i = 0; i < ARRAY_SIZE(req_info); i++)
721                 if (req_info[i].namelen == namelen &&
722                     !string_enum_compare(req_info[i].name, name, namelen))
723                         return req_info[i].request;
725         return REQ_NONE;
729 /*
730  * Options
731  */
733 static const char usage[] =
734 "tig " TIG_VERSION " (" __DATE__ ")\n"
735 "\n"
736 "Usage: tig        [options] [revs] [--] [paths]\n"
737 "   or: tig show   [options] [revs] [--] [paths]\n"
738 "   or: tig blame  [rev] path\n"
739 "   or: tig status\n"
740 "   or: tig <      [git command output]\n"
741 "\n"
742 "Options:\n"
743 "  -v, --version   Show version and exit\n"
744 "  -h, --help      Show help message and exit";
746 /* Option and state variables. */
747 static bool opt_date                    = TRUE;
748 static bool opt_author                  = TRUE;
749 static bool opt_line_number             = FALSE;
750 static bool opt_line_graphics           = TRUE;
751 static bool opt_rev_graph               = FALSE;
752 static bool opt_show_refs               = TRUE;
753 static int opt_num_interval             = NUMBER_INTERVAL;
754 static int opt_tab_size                 = TAB_SIZE;
755 static int opt_author_cols              = AUTHOR_COLS-1;
756 static char opt_path[SIZEOF_STR]        = "";
757 static char opt_file[SIZEOF_STR]        = "";
758 static char opt_ref[SIZEOF_REF]         = "";
759 static char opt_head[SIZEOF_REF]        = "";
760 static char opt_head_rev[SIZEOF_REV]    = "";
761 static char opt_remote[SIZEOF_REF]      = "";
762 static char opt_encoding[20]            = "UTF-8";
763 static bool opt_utf8                    = TRUE;
764 static char opt_codeset[20]             = "UTF-8";
765 static iconv_t opt_iconv                = ICONV_NONE;
766 static char opt_search[SIZEOF_STR]      = "";
767 static char opt_cdup[SIZEOF_STR]        = "";
768 static char opt_git_dir[SIZEOF_STR]     = "";
769 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
770 static char opt_editor[SIZEOF_STR]      = "";
771 static FILE *opt_tty                    = NULL;
773 #define is_initial_commit()     (!*opt_head_rev)
774 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
776 static enum request
777 parse_options(int argc, const char *argv[], const char ***run_argv)
779         enum request request = REQ_VIEW_MAIN;
780         const char *subcommand;
781         bool seen_dashdash = FALSE;
782         /* XXX: This is vulnerable to the user overriding options
783          * required for the main view parser. */
784         const char *custom_argv[SIZEOF_ARG] = {
785                 "git", "log", "--no-color", "--pretty=raw", "--parents",
786                         "--topo-order", NULL
787         };
788         int i, j = 6;
790         if (!isatty(STDIN_FILENO))
791                 return REQ_VIEW_PAGER;
793         if (argc <= 1)
794                 return REQ_VIEW_MAIN;
796         subcommand = argv[1];
797         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
798                 if (!strcmp(subcommand, "-S"))
799                         warn("`-S' has been deprecated; use `tig status' instead");
800                 if (argc > 2)
801                         warn("ignoring arguments after `%s'", subcommand);
802                 return REQ_VIEW_STATUS;
804         } else if (!strcmp(subcommand, "blame")) {
805                 if (argc <= 2 || argc > 4)
806                         die("invalid number of options to blame\n\n%s", usage);
808                 i = 2;
809                 if (argc == 4) {
810                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
811                         i++;
812                 }
814                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
815                 return REQ_VIEW_BLAME;
817         } else if (!strcmp(subcommand, "show")) {
818                 request = REQ_VIEW_DIFF;
820         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
821                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
822                 warn("`tig %s' has been deprecated", subcommand);
824         } else {
825                 subcommand = NULL;
826         }
828         if (subcommand) {
829                 custom_argv[1] = subcommand;
830                 j = 2;
831         }
833         for (i = 1 + !!subcommand; i < argc; i++) {
834                 const char *opt = argv[i];
836                 if (seen_dashdash || !strcmp(opt, "--")) {
837                         seen_dashdash = TRUE;
839                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
840                         printf("tig version %s\n", TIG_VERSION);
841                         return REQ_NONE;
843                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
844                         printf("%s\n", usage);
845                         return REQ_NONE;
846                 }
848                 custom_argv[j++] = opt;
849                 if (j >= ARRAY_SIZE(custom_argv))
850                         die("command too long");
851         }
853         custom_argv[j] = NULL;
854         *run_argv = custom_argv;
856         return request;
860 /*
861  * Line-oriented content detection.
862  */
864 #define LINE_INFO \
865 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
866 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
867 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
868 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
869 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
870 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
871 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
872 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
873 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
874 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
875 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
876 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
877 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
878 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
879 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
880 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
881 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
882 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
883 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
884 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
885 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
886 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
887 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
888 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
889 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
890 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
891 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
892 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
893 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
894 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
895 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
896 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
897 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
898 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
899 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
900 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
901 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
902 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
903 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
904 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
905 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
906 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
907 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
908 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
909 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
910 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
911 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
912 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
913 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
914 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
915 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
916 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
917 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
918 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
920 enum line_type {
921 #define LINE(type, line, fg, bg, attr) \
922         LINE_##type
923         LINE_INFO,
924         LINE_NONE
925 #undef  LINE
926 };
928 struct line_info {
929         const char *name;       /* Option name. */
930         int namelen;            /* Size of option name. */
931         const char *line;       /* The start of line to match. */
932         int linelen;            /* Size of string to match. */
933         int fg, bg, attr;       /* Color and text attributes for the lines. */
934 };
936 static struct line_info line_info[] = {
937 #define LINE(type, line, fg, bg, attr) \
938         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
939         LINE_INFO
940 #undef  LINE
941 };
943 static enum line_type
944 get_line_type(const char *line)
946         int linelen = strlen(line);
947         enum line_type type;
949         for (type = 0; type < ARRAY_SIZE(line_info); type++)
950                 /* Case insensitive search matches Signed-off-by lines better. */
951                 if (linelen >= line_info[type].linelen &&
952                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
953                         return type;
955         return LINE_DEFAULT;
958 static inline int
959 get_line_attr(enum line_type type)
961         assert(type < ARRAY_SIZE(line_info));
962         return COLOR_PAIR(type) | line_info[type].attr;
965 static struct line_info *
966 get_line_info(const char *name)
968         size_t namelen = strlen(name);
969         enum line_type type;
971         for (type = 0; type < ARRAY_SIZE(line_info); type++)
972                 if (namelen == line_info[type].namelen &&
973                     !string_enum_compare(line_info[type].name, name, namelen))
974                         return &line_info[type];
976         return NULL;
979 static void
980 init_colors(void)
982         int default_bg = line_info[LINE_DEFAULT].bg;
983         int default_fg = line_info[LINE_DEFAULT].fg;
984         enum line_type type;
986         start_color();
988         if (assume_default_colors(default_fg, default_bg) == ERR) {
989                 default_bg = COLOR_BLACK;
990                 default_fg = COLOR_WHITE;
991         }
993         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
994                 struct line_info *info = &line_info[type];
995                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
996                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
998                 init_pair(type, fg, bg);
999         }
1002 struct line {
1003         enum line_type type;
1005         /* State flags */
1006         unsigned int selected:1;
1007         unsigned int dirty:1;
1009         void *data;             /* User data */
1010 };
1013 /*
1014  * Keys
1015  */
1017 struct keybinding {
1018         int alias;
1019         enum request request;
1020 };
1022 static struct keybinding default_keybindings[] = {
1023         /* View switching */
1024         { 'm',          REQ_VIEW_MAIN },
1025         { 'd',          REQ_VIEW_DIFF },
1026         { 'l',          REQ_VIEW_LOG },
1027         { 't',          REQ_VIEW_TREE },
1028         { 'f',          REQ_VIEW_BLOB },
1029         { 'B',          REQ_VIEW_BLAME },
1030         { 'p',          REQ_VIEW_PAGER },
1031         { 'h',          REQ_VIEW_HELP },
1032         { 'S',          REQ_VIEW_STATUS },
1033         { 'c',          REQ_VIEW_STAGE },
1035         /* View manipulation */
1036         { 'q',          REQ_VIEW_CLOSE },
1037         { KEY_TAB,      REQ_VIEW_NEXT },
1038         { KEY_RETURN,   REQ_ENTER },
1039         { KEY_UP,       REQ_PREVIOUS },
1040         { KEY_DOWN,     REQ_NEXT },
1041         { 'R',          REQ_REFRESH },
1042         { KEY_F(5),     REQ_REFRESH },
1043         { 'O',          REQ_MAXIMIZE },
1045         /* Cursor navigation */
1046         { 'k',          REQ_MOVE_UP },
1047         { 'j',          REQ_MOVE_DOWN },
1048         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1049         { KEY_END,      REQ_MOVE_LAST_LINE },
1050         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1051         { ' ',          REQ_MOVE_PAGE_DOWN },
1052         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1053         { 'b',          REQ_MOVE_PAGE_UP },
1054         { '-',          REQ_MOVE_PAGE_UP },
1056         /* Scrolling */
1057         { KEY_IC,       REQ_SCROLL_LINE_UP },
1058         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1059         { 'w',          REQ_SCROLL_PAGE_UP },
1060         { 's',          REQ_SCROLL_PAGE_DOWN },
1062         /* Searching */
1063         { '/',          REQ_SEARCH },
1064         { '?',          REQ_SEARCH_BACK },
1065         { 'n',          REQ_FIND_NEXT },
1066         { 'N',          REQ_FIND_PREV },
1068         /* Misc */
1069         { 'Q',          REQ_QUIT },
1070         { 'z',          REQ_STOP_LOADING },
1071         { 'v',          REQ_SHOW_VERSION },
1072         { 'r',          REQ_SCREEN_REDRAW },
1073         { '.',          REQ_TOGGLE_LINENO },
1074         { 'D',          REQ_TOGGLE_DATE },
1075         { 'A',          REQ_TOGGLE_AUTHOR },
1076         { 'g',          REQ_TOGGLE_REV_GRAPH },
1077         { 'F',          REQ_TOGGLE_REFS },
1078         { ':',          REQ_PROMPT },
1079         { 'u',          REQ_STATUS_UPDATE },
1080         { '!',          REQ_STATUS_REVERT },
1081         { 'M',          REQ_STATUS_MERGE },
1082         { '@',          REQ_STAGE_NEXT },
1083         { ',',          REQ_TREE_PARENT },
1084         { 'e',          REQ_EDIT },
1086         /* Using the ncurses SIGWINCH handler. */
1087         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1088 };
1090 #define KEYMAP_INFO \
1091         KEYMAP_(GENERIC), \
1092         KEYMAP_(MAIN), \
1093         KEYMAP_(DIFF), \
1094         KEYMAP_(LOG), \
1095         KEYMAP_(TREE), \
1096         KEYMAP_(BLOB), \
1097         KEYMAP_(BLAME), \
1098         KEYMAP_(PAGER), \
1099         KEYMAP_(HELP), \
1100         KEYMAP_(STATUS), \
1101         KEYMAP_(STAGE)
1103 enum keymap {
1104 #define KEYMAP_(name) KEYMAP_##name
1105         KEYMAP_INFO
1106 #undef  KEYMAP_
1107 };
1109 static struct int_map keymap_table[] = {
1110 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1111         KEYMAP_INFO
1112 #undef  KEYMAP_
1113 };
1115 #define set_keymap(map, name) \
1116         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1118 struct keybinding_table {
1119         struct keybinding *data;
1120         size_t size;
1121 };
1123 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1125 static void
1126 add_keybinding(enum keymap keymap, enum request request, int key)
1128         struct keybinding_table *table = &keybindings[keymap];
1130         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1131         if (!table->data)
1132                 die("Failed to allocate keybinding");
1133         table->data[table->size].alias = key;
1134         table->data[table->size++].request = request;
1137 /* Looks for a key binding first in the given map, then in the generic map, and
1138  * lastly in the default keybindings. */
1139 static enum request
1140 get_keybinding(enum keymap keymap, int key)
1142         size_t i;
1144         for (i = 0; i < keybindings[keymap].size; i++)
1145                 if (keybindings[keymap].data[i].alias == key)
1146                         return keybindings[keymap].data[i].request;
1148         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1149                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1150                         return keybindings[KEYMAP_GENERIC].data[i].request;
1152         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1153                 if (default_keybindings[i].alias == key)
1154                         return default_keybindings[i].request;
1156         return (enum request) key;
1160 struct key {
1161         const char *name;
1162         int value;
1163 };
1165 static struct key key_table[] = {
1166         { "Enter",      KEY_RETURN },
1167         { "Space",      ' ' },
1168         { "Backspace",  KEY_BACKSPACE },
1169         { "Tab",        KEY_TAB },
1170         { "Escape",     KEY_ESC },
1171         { "Left",       KEY_LEFT },
1172         { "Right",      KEY_RIGHT },
1173         { "Up",         KEY_UP },
1174         { "Down",       KEY_DOWN },
1175         { "Insert",     KEY_IC },
1176         { "Delete",     KEY_DC },
1177         { "Hash",       '#' },
1178         { "Home",       KEY_HOME },
1179         { "End",        KEY_END },
1180         { "PageUp",     KEY_PPAGE },
1181         { "PageDown",   KEY_NPAGE },
1182         { "F1",         KEY_F(1) },
1183         { "F2",         KEY_F(2) },
1184         { "F3",         KEY_F(3) },
1185         { "F4",         KEY_F(4) },
1186         { "F5",         KEY_F(5) },
1187         { "F6",         KEY_F(6) },
1188         { "F7",         KEY_F(7) },
1189         { "F8",         KEY_F(8) },
1190         { "F9",         KEY_F(9) },
1191         { "F10",        KEY_F(10) },
1192         { "F11",        KEY_F(11) },
1193         { "F12",        KEY_F(12) },
1194 };
1196 static int
1197 get_key_value(const char *name)
1199         int i;
1201         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1202                 if (!strcasecmp(key_table[i].name, name))
1203                         return key_table[i].value;
1205         if (strlen(name) == 1 && isprint(*name))
1206                 return (int) *name;
1208         return ERR;
1211 static const char *
1212 get_key_name(int key_value)
1214         static char key_char[] = "'X'";
1215         const char *seq = NULL;
1216         int key;
1218         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1219                 if (key_table[key].value == key_value)
1220                         seq = key_table[key].name;
1222         if (seq == NULL &&
1223             key_value < 127 &&
1224             isprint(key_value)) {
1225                 key_char[1] = (char) key_value;
1226                 seq = key_char;
1227         }
1229         return seq ? seq : "(no key)";
1232 static const char *
1233 get_key(enum request request)
1235         static char buf[BUFSIZ];
1236         size_t pos = 0;
1237         char *sep = "";
1238         int i;
1240         buf[pos] = 0;
1242         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1243                 struct keybinding *keybinding = &default_keybindings[i];
1245                 if (keybinding->request != request)
1246                         continue;
1248                 if (!string_format_from(buf, &pos, "%s%s", sep,
1249                                         get_key_name(keybinding->alias)))
1250                         return "Too many keybindings!";
1251                 sep = ", ";
1252         }
1254         return buf;
1257 struct run_request {
1258         enum keymap keymap;
1259         int key;
1260         const char *argv[SIZEOF_ARG];
1261 };
1263 static struct run_request *run_request;
1264 static size_t run_requests;
1266 static enum request
1267 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1269         struct run_request *req;
1271         if (argc >= ARRAY_SIZE(req->argv) - 1)
1272                 return REQ_NONE;
1274         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1275         if (!req)
1276                 return REQ_NONE;
1278         run_request = req;
1279         req = &run_request[run_requests];
1280         req->keymap = keymap;
1281         req->key = key;
1282         req->argv[0] = NULL;
1284         if (!format_argv(req->argv, argv, FORMAT_NONE))
1285                 return REQ_NONE;
1287         return REQ_NONE + ++run_requests;
1290 static struct run_request *
1291 get_run_request(enum request request)
1293         if (request <= REQ_NONE)
1294                 return NULL;
1295         return &run_request[request - REQ_NONE - 1];
1298 static void
1299 add_builtin_run_requests(void)
1301         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1302         const char *gc[] = { "git", "gc", NULL };
1303         struct {
1304                 enum keymap keymap;
1305                 int key;
1306                 int argc;
1307                 const char **argv;
1308         } reqs[] = {
1309                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1310                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1311         };
1312         int i;
1314         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1315                 enum request req;
1317                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1318                 if (req != REQ_NONE)
1319                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1320         }
1323 /*
1324  * User config file handling.
1325  */
1327 static struct int_map color_map[] = {
1328 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1329         COLOR_MAP(DEFAULT),
1330         COLOR_MAP(BLACK),
1331         COLOR_MAP(BLUE),
1332         COLOR_MAP(CYAN),
1333         COLOR_MAP(GREEN),
1334         COLOR_MAP(MAGENTA),
1335         COLOR_MAP(RED),
1336         COLOR_MAP(WHITE),
1337         COLOR_MAP(YELLOW),
1338 };
1340 #define set_color(color, name) \
1341         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1343 static struct int_map attr_map[] = {
1344 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1345         ATTR_MAP(NORMAL),
1346         ATTR_MAP(BLINK),
1347         ATTR_MAP(BOLD),
1348         ATTR_MAP(DIM),
1349         ATTR_MAP(REVERSE),
1350         ATTR_MAP(STANDOUT),
1351         ATTR_MAP(UNDERLINE),
1352 };
1354 #define set_attribute(attr, name) \
1355         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1357 static int   config_lineno;
1358 static bool  config_errors;
1359 static const char *config_msg;
1361 /* Wants: object fgcolor bgcolor [attr] */
1362 static int
1363 option_color_command(int argc, const char *argv[])
1365         struct line_info *info;
1367         if (argc != 3 && argc != 4) {
1368                 config_msg = "Wrong number of arguments given to color command";
1369                 return ERR;
1370         }
1372         info = get_line_info(argv[0]);
1373         if (!info) {
1374                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1375                         info = get_line_info("delimiter");
1377                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1378                         info = get_line_info("date");
1380                 } else {
1381                         config_msg = "Unknown color name";
1382                         return ERR;
1383                 }
1384         }
1386         if (set_color(&info->fg, argv[1]) == ERR ||
1387             set_color(&info->bg, argv[2]) == ERR) {
1388                 config_msg = "Unknown color";
1389                 return ERR;
1390         }
1392         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1393                 config_msg = "Unknown attribute";
1394                 return ERR;
1395         }
1397         return OK;
1400 static bool parse_bool(const char *s)
1402         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1403                 !strcmp(s, "yes")) ? TRUE : FALSE;
1406 static int
1407 parse_int(const char *s, int default_value, int min, int max)
1409         int value = atoi(s);
1411         return (value < min || value > max) ? default_value : value;
1414 /* Wants: name = value */
1415 static int
1416 option_set_command(int argc, const char *argv[])
1418         if (argc != 3) {
1419                 config_msg = "Wrong number of arguments given to set command";
1420                 return ERR;
1421         }
1423         if (strcmp(argv[1], "=")) {
1424                 config_msg = "No value assigned";
1425                 return ERR;
1426         }
1428         if (!strcmp(argv[0], "show-author")) {
1429                 opt_author = parse_bool(argv[2]);
1430                 return OK;
1431         }
1433         if (!strcmp(argv[0], "show-date")) {
1434                 opt_date = parse_bool(argv[2]);
1435                 return OK;
1436         }
1438         if (!strcmp(argv[0], "show-rev-graph")) {
1439                 opt_rev_graph = parse_bool(argv[2]);
1440                 return OK;
1441         }
1443         if (!strcmp(argv[0], "show-refs")) {
1444                 opt_show_refs = parse_bool(argv[2]);
1445                 return OK;
1446         }
1448         if (!strcmp(argv[0], "show-line-numbers")) {
1449                 opt_line_number = parse_bool(argv[2]);
1450                 return OK;
1451         }
1453         if (!strcmp(argv[0], "line-graphics")) {
1454                 opt_line_graphics = parse_bool(argv[2]);
1455                 return OK;
1456         }
1458         if (!strcmp(argv[0], "line-number-interval")) {
1459                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1460                 return OK;
1461         }
1463         if (!strcmp(argv[0], "author-width")) {
1464                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1465                 return OK;
1466         }
1468         if (!strcmp(argv[0], "tab-size")) {
1469                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1470                 return OK;
1471         }
1473         if (!strcmp(argv[0], "commit-encoding")) {
1474                 const char *arg = argv[2];
1475                 int arglen = strlen(arg);
1477                 switch (arg[0]) {
1478                 case '"':
1479                 case '\'':
1480                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1481                                 config_msg = "Unmatched quotation";
1482                                 return ERR;
1483                         }
1484                         arg += 1; arglen -= 2;
1485                 default:
1486                         string_ncopy(opt_encoding, arg, strlen(arg));
1487                         return OK;
1488                 }
1489         }
1491         config_msg = "Unknown variable name";
1492         return ERR;
1495 /* Wants: mode request key */
1496 static int
1497 option_bind_command(int argc, const char *argv[])
1499         enum request request;
1500         int keymap;
1501         int key;
1503         if (argc < 3) {
1504                 config_msg = "Wrong number of arguments given to bind command";
1505                 return ERR;
1506         }
1508         if (set_keymap(&keymap, argv[0]) == ERR) {
1509                 config_msg = "Unknown key map";
1510                 return ERR;
1511         }
1513         key = get_key_value(argv[1]);
1514         if (key == ERR) {
1515                 config_msg = "Unknown key";
1516                 return ERR;
1517         }
1519         request = get_request(argv[2]);
1520         if (request == REQ_NONE) {
1521                 const char *obsolete[] = { "cherry-pick" };
1522                 size_t namelen = strlen(argv[2]);
1523                 int i;
1525                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1526                         if (namelen == strlen(obsolete[i]) &&
1527                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1528                                 config_msg = "Obsolete request name";
1529                                 return ERR;
1530                         }
1531                 }
1532         }
1533         if (request == REQ_NONE && *argv[2]++ == '!')
1534                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1535         if (request == REQ_NONE) {
1536                 config_msg = "Unknown request name";
1537                 return ERR;
1538         }
1540         add_keybinding(keymap, request, key);
1542         return OK;
1545 static int
1546 set_option(const char *opt, char *value)
1548         const char *argv[SIZEOF_ARG];
1549         int argc = 0;
1551         if (!argv_from_string(argv, &argc, value)) {
1552                 config_msg = "Too many option arguments";
1553                 return ERR;
1554         }
1556         if (!strcmp(opt, "color"))
1557                 return option_color_command(argc, argv);
1559         if (!strcmp(opt, "set"))
1560                 return option_set_command(argc, argv);
1562         if (!strcmp(opt, "bind"))
1563                 return option_bind_command(argc, argv);
1565         config_msg = "Unknown option command";
1566         return ERR;
1569 static int
1570 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1572         int status = OK;
1574         config_lineno++;
1575         config_msg = "Internal error";
1577         /* Check for comment markers, since read_properties() will
1578          * only ensure opt and value are split at first " \t". */
1579         optlen = strcspn(opt, "#");
1580         if (optlen == 0)
1581                 return OK;
1583         if (opt[optlen] != 0) {
1584                 config_msg = "No option value";
1585                 status = ERR;
1587         }  else {
1588                 /* Look for comment endings in the value. */
1589                 size_t len = strcspn(value, "#");
1591                 if (len < valuelen) {
1592                         valuelen = len;
1593                         value[valuelen] = 0;
1594                 }
1596                 status = set_option(opt, value);
1597         }
1599         if (status == ERR) {
1600                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1601                         config_lineno, (int) optlen, opt, config_msg);
1602                 config_errors = TRUE;
1603         }
1605         /* Always keep going if errors are encountered. */
1606         return OK;
1609 static void
1610 load_option_file(const char *path)
1612         struct io io = {};
1614         /* It's ok that the file doesn't exist. */
1615         if (!io_open(&io, path))
1616                 return;
1618         config_lineno = 0;
1619         config_errors = FALSE;
1621         if (read_properties(&io, " \t", read_option) == ERR ||
1622             config_errors == TRUE)
1623                 fprintf(stderr, "Errors while loading %s.\n", path);
1626 static int
1627 load_options(void)
1629         const char *home = getenv("HOME");
1630         const char *tigrc_user = getenv("TIGRC_USER");
1631         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1632         char buf[SIZEOF_STR];
1634         add_builtin_run_requests();
1636         if (!tigrc_system) {
1637                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1638                         return ERR;
1639                 tigrc_system = buf;
1640         }
1641         load_option_file(tigrc_system);
1643         if (!tigrc_user) {
1644                 if (!home || !string_format(buf, "%s/.tigrc", home))
1645                         return ERR;
1646                 tigrc_user = buf;
1647         }
1648         load_option_file(tigrc_user);
1650         return OK;
1654 /*
1655  * The viewer
1656  */
1658 struct view;
1659 struct view_ops;
1661 /* The display array of active views and the index of the current view. */
1662 static struct view *display[2];
1663 static unsigned int current_view;
1665 /* Reading from the prompt? */
1666 static bool input_mode = FALSE;
1668 #define foreach_displayed_view(view, i) \
1669         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1671 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1673 /* Current head and commit ID */
1674 static char ref_blob[SIZEOF_REF]        = "";
1675 static char ref_commit[SIZEOF_REF]      = "HEAD";
1676 static char ref_head[SIZEOF_REF]        = "HEAD";
1678 struct view {
1679         const char *name;       /* View name */
1680         const char *cmd_env;    /* Command line set via environment */
1681         const char *id;         /* Points to either of ref_{head,commit,blob} */
1683         struct view_ops *ops;   /* View operations */
1685         enum keymap keymap;     /* What keymap does this view have */
1686         bool git_dir;           /* Whether the view requires a git directory. */
1688         char ref[SIZEOF_REF];   /* Hovered commit reference */
1689         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1691         int height, width;      /* The width and height of the main window */
1692         WINDOW *win;            /* The main window */
1693         WINDOW *title;          /* The title window living below the main window */
1695         /* Navigation */
1696         unsigned long offset;   /* Offset of the window top */
1697         unsigned long lineno;   /* Current line number */
1699         /* Searching */
1700         char grep[SIZEOF_STR];  /* Search string */
1701         regex_t *regex;         /* Pre-compiled regex */
1703         /* If non-NULL, points to the view that opened this view. If this view
1704          * is closed tig will switch back to the parent view. */
1705         struct view *parent;
1707         /* Buffering */
1708         size_t lines;           /* Total number of lines */
1709         struct line *line;      /* Line index */
1710         size_t line_alloc;      /* Total number of allocated lines */
1711         size_t line_size;       /* Total number of used lines */
1712         unsigned int digits;    /* Number of digits in the lines member. */
1714         /* Drawing */
1715         struct line *curline;   /* Line currently being drawn. */
1716         enum line_type curtype; /* Attribute currently used for drawing. */
1717         unsigned long col;      /* Column when drawing. */
1719         /* Loading */
1720         struct io io;
1721         struct io *pipe;
1722         time_t start_time;
1723 };
1725 struct view_ops {
1726         /* What type of content being displayed. Used in the title bar. */
1727         const char *type;
1728         /* Default command arguments. */
1729         const char **argv;
1730         /* Open and reads in all view content. */
1731         bool (*open)(struct view *view);
1732         /* Read one line; updates view->line. */
1733         bool (*read)(struct view *view, char *data);
1734         /* Draw one line; @lineno must be < view->height. */
1735         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1736         /* Depending on view handle a special requests. */
1737         enum request (*request)(struct view *view, enum request request, struct line *line);
1738         /* Search for regex in a line. */
1739         bool (*grep)(struct view *view, struct line *line);
1740         /* Select line */
1741         void (*select)(struct view *view, struct line *line);
1742 };
1744 static struct view_ops blame_ops;
1745 static struct view_ops blob_ops;
1746 static struct view_ops diff_ops;
1747 static struct view_ops help_ops;
1748 static struct view_ops log_ops;
1749 static struct view_ops main_ops;
1750 static struct view_ops pager_ops;
1751 static struct view_ops stage_ops;
1752 static struct view_ops status_ops;
1753 static struct view_ops tree_ops;
1755 #define VIEW_STR(name, env, ref, ops, map, git) \
1756         { name, #env, ref, ops, map, git }
1758 #define VIEW_(id, name, ops, git, ref) \
1759         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1762 static struct view views[] = {
1763         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1764         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1765         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1766         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1767         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1768         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1769         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1770         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1771         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1772         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1773 };
1775 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1776 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1778 #define foreach_view(view, i) \
1779         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1781 #define view_is_displayed(view) \
1782         (view == display[0] || view == display[1])
1785 enum line_graphic {
1786         LINE_GRAPHIC_VLINE
1787 };
1789 static int line_graphics[] = {
1790         /* LINE_GRAPHIC_VLINE: */ '|'
1791 };
1793 static inline void
1794 set_view_attr(struct view *view, enum line_type type)
1796         if (!view->curline->selected && view->curtype != type) {
1797                 wattrset(view->win, get_line_attr(type));
1798                 wchgat(view->win, -1, 0, type, NULL);
1799                 view->curtype = type;
1800         }
1803 static int
1804 draw_chars(struct view *view, enum line_type type, const char *string,
1805            int max_len, bool use_tilde)
1807         int len = 0;
1808         int col = 0;
1809         int trimmed = FALSE;
1811         if (max_len <= 0)
1812                 return 0;
1814         if (opt_utf8) {
1815                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1816         } else {
1817                 col = len = strlen(string);
1818                 if (len > max_len) {
1819                         if (use_tilde) {
1820                                 max_len -= 1;
1821                         }
1822                         col = len = max_len;
1823                         trimmed = TRUE;
1824                 }
1825         }
1827         set_view_attr(view, type);
1828         waddnstr(view->win, string, len);
1829         if (trimmed && use_tilde) {
1830                 set_view_attr(view, LINE_DELIMITER);
1831                 waddch(view->win, '~');
1832                 col++;
1833         }
1835         return col;
1838 static int
1839 draw_space(struct view *view, enum line_type type, int max, int spaces)
1841         static char space[] = "                    ";
1842         int col = 0;
1844         spaces = MIN(max, spaces);
1846         while (spaces > 0) {
1847                 int len = MIN(spaces, sizeof(space) - 1);
1849                 col += draw_chars(view, type, space, spaces, FALSE);
1850                 spaces -= len;
1851         }
1853         return col;
1856 static bool
1857 draw_lineno(struct view *view, unsigned int lineno)
1859         char number[10];
1860         int digits3 = view->digits < 3 ? 3 : view->digits;
1861         int max_number = MIN(digits3, STRING_SIZE(number));
1862         int max = view->width - view->col;
1863         int col;
1865         if (max < max_number)
1866                 max_number = max;
1868         lineno += view->offset + 1;
1869         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1870                 static char fmt[] = "%1ld";
1872                 if (view->digits <= 9)
1873                         fmt[1] = '0' + digits3;
1875                 if (!string_format(number, fmt, lineno))
1876                         number[0] = 0;
1877                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1878         } else {
1879                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1880         }
1882         if (col < max) {
1883                 set_view_attr(view, LINE_DEFAULT);
1884                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1885                 col++;
1886         }
1888         if (col < max)
1889                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1890         view->col += col;
1892         return view->width - view->col <= 0;
1895 static bool
1896 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1898         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1899         return view->width - view->col <= 0;
1902 static bool
1903 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1905         int max = view->width - view->col;
1906         int i;
1908         if (max < size)
1909                 size = max;
1911         set_view_attr(view, type);
1912         /* Using waddch() instead of waddnstr() ensures that
1913          * they'll be rendered correctly for the cursor line. */
1914         for (i = 0; i < size; i++)
1915                 waddch(view->win, graphic[i]);
1917         view->col += size;
1918         if (size < max) {
1919                 waddch(view->win, ' ');
1920                 view->col++;
1921         }
1923         return view->width - view->col <= 0;
1926 static bool
1927 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1929         int max = MIN(view->width - view->col, len);
1930         int col;
1932         if (text)
1933                 col = draw_chars(view, type, text, max - 1, trim);
1934         else
1935                 col = draw_space(view, type, max - 1, max - 1);
1937         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1938         return view->width - view->col <= 0;
1941 static bool
1942 draw_date(struct view *view, struct tm *time)
1944         char buf[DATE_COLS];
1945         char *date;
1946         int timelen = 0;
1948         if (time)
1949                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1950         date = timelen ? buf : NULL;
1952         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1955 static bool
1956 draw_view_line(struct view *view, unsigned int lineno)
1958         struct line *line;
1959         bool selected = (view->offset + lineno == view->lineno);
1960         bool draw_ok;
1962         assert(view_is_displayed(view));
1964         if (view->offset + lineno >= view->lines)
1965                 return FALSE;
1967         line = &view->line[view->offset + lineno];
1969         wmove(view->win, lineno, 0);
1970         view->col = 0;
1971         view->curline = line;
1972         view->curtype = LINE_NONE;
1973         line->selected = FALSE;
1975         if (selected) {
1976                 set_view_attr(view, LINE_CURSOR);
1977                 line->selected = TRUE;
1978                 view->ops->select(view, line);
1979         } else if (line->selected) {
1980                 wclrtoeol(view->win);
1981         }
1983         scrollok(view->win, FALSE);
1984         draw_ok = view->ops->draw(view, line, lineno);
1985         scrollok(view->win, TRUE);
1987         return draw_ok;
1990 static void
1991 redraw_view_dirty(struct view *view)
1993         bool dirty = FALSE;
1994         int lineno;
1996         for (lineno = 0; lineno < view->height; lineno++) {
1997                 struct line *line = &view->line[view->offset + lineno];
1999                 if (!line->dirty)
2000                         continue;
2001                 line->dirty = 0;
2002                 dirty = TRUE;
2003                 if (!draw_view_line(view, lineno))
2004                         break;
2005         }
2007         if (!dirty)
2008                 return;
2009         redrawwin(view->win);
2010         if (input_mode)
2011                 wnoutrefresh(view->win);
2012         else
2013                 wrefresh(view->win);
2016 static void
2017 redraw_view_from(struct view *view, int lineno)
2019         assert(0 <= lineno && lineno < view->height);
2021         for (; lineno < view->height; lineno++) {
2022                 if (!draw_view_line(view, lineno))
2023                         break;
2024         }
2026         redrawwin(view->win);
2027         if (input_mode)
2028                 wnoutrefresh(view->win);
2029         else
2030                 wrefresh(view->win);
2033 static void
2034 redraw_view(struct view *view)
2036         wclear(view->win);
2037         redraw_view_from(view, 0);
2041 static void
2042 update_view_title(struct view *view)
2044         char buf[SIZEOF_STR];
2045         char state[SIZEOF_STR];
2046         size_t bufpos = 0, statelen = 0;
2048         assert(view_is_displayed(view));
2050         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
2051                 unsigned int view_lines = view->offset + view->height;
2052                 unsigned int lines = view->lines
2053                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2054                                    : 0;
2056                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2057                                    view->ops->type,
2058                                    view->lineno + 1,
2059                                    view->lines,
2060                                    lines);
2062                 if (view->pipe) {
2063                         time_t secs = time(NULL) - view->start_time;
2065                         /* Three git seconds are a long time ... */
2066                         if (secs > 2)
2067                                 string_format_from(state, &statelen, " %lds", secs);
2068                 }
2069         }
2071         string_format_from(buf, &bufpos, "[%s]", view->name);
2072         if (*view->ref && bufpos < view->width) {
2073                 size_t refsize = strlen(view->ref);
2074                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2076                 if (minsize < view->width)
2077                         refsize = view->width - minsize + 7;
2078                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2079         }
2081         if (statelen && bufpos < view->width) {
2082                 string_format_from(buf, &bufpos, " %s", state);
2083         }
2085         if (view == display[current_view])
2086                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2087         else
2088                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2090         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2091         wclrtoeol(view->title);
2092         wmove(view->title, 0, view->width - 1);
2094         if (input_mode)
2095                 wnoutrefresh(view->title);
2096         else
2097                 wrefresh(view->title);
2100 static void
2101 resize_display(void)
2103         int offset, i;
2104         struct view *base = display[0];
2105         struct view *view = display[1] ? display[1] : display[0];
2107         /* Setup window dimensions */
2109         getmaxyx(stdscr, base->height, base->width);
2111         /* Make room for the status window. */
2112         base->height -= 1;
2114         if (view != base) {
2115                 /* Horizontal split. */
2116                 view->width   = base->width;
2117                 view->height  = SCALE_SPLIT_VIEW(base->height);
2118                 base->height -= view->height;
2120                 /* Make room for the title bar. */
2121                 view->height -= 1;
2122         }
2124         /* Make room for the title bar. */
2125         base->height -= 1;
2127         offset = 0;
2129         foreach_displayed_view (view, i) {
2130                 if (!view->win) {
2131                         view->win = newwin(view->height, 0, offset, 0);
2132                         if (!view->win)
2133                                 die("Failed to create %s view", view->name);
2135                         scrollok(view->win, TRUE);
2137                         view->title = newwin(1, 0, offset + view->height, 0);
2138                         if (!view->title)
2139                                 die("Failed to create title window");
2141                 } else {
2142                         wresize(view->win, view->height, view->width);
2143                         mvwin(view->win,   offset, 0);
2144                         mvwin(view->title, offset + view->height, 0);
2145                 }
2147                 offset += view->height + 1;
2148         }
2151 static void
2152 redraw_display(void)
2154         struct view *view;
2155         int i;
2157         foreach_displayed_view (view, i) {
2158                 redraw_view(view);
2159                 update_view_title(view);
2160         }
2163 static void
2164 update_display_cursor(struct view *view)
2166         /* Move the cursor to the right-most column of the cursor line.
2167          *
2168          * XXX: This could turn out to be a bit expensive, but it ensures that
2169          * the cursor does not jump around. */
2170         if (view->lines) {
2171                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2172                 wrefresh(view->win);
2173         }
2176 /*
2177  * Navigation
2178  */
2180 /* Scrolling backend */
2181 static void
2182 do_scroll_view(struct view *view, int lines)
2184         bool redraw_current_line = FALSE;
2186         /* The rendering expects the new offset. */
2187         view->offset += lines;
2189         assert(0 <= view->offset && view->offset < view->lines);
2190         assert(lines);
2192         /* Move current line into the view. */
2193         if (view->lineno < view->offset) {
2194                 view->lineno = view->offset;
2195                 redraw_current_line = TRUE;
2196         } else if (view->lineno >= view->offset + view->height) {
2197                 view->lineno = view->offset + view->height - 1;
2198                 redraw_current_line = TRUE;
2199         }
2201         assert(view->offset <= view->lineno && view->lineno < view->lines);
2203         /* Redraw the whole screen if scrolling is pointless. */
2204         if (view->height < ABS(lines)) {
2205                 redraw_view(view);
2207         } else {
2208                 int line = lines > 0 ? view->height - lines : 0;
2209                 int end = line + ABS(lines);
2211                 wscrl(view->win, lines);
2213                 for (; line < end; line++) {
2214                         if (!draw_view_line(view, line))
2215                                 break;
2216                 }
2218                 if (redraw_current_line)
2219                         draw_view_line(view, view->lineno - view->offset);
2220         }
2222         redrawwin(view->win);
2223         wrefresh(view->win);
2224         report("");
2227 /* Scroll frontend */
2228 static void
2229 scroll_view(struct view *view, enum request request)
2231         int lines = 1;
2233         assert(view_is_displayed(view));
2235         switch (request) {
2236         case REQ_SCROLL_PAGE_DOWN:
2237                 lines = view->height;
2238         case REQ_SCROLL_LINE_DOWN:
2239                 if (view->offset + lines > view->lines)
2240                         lines = view->lines - view->offset;
2242                 if (lines == 0 || view->offset + view->height >= view->lines) {
2243                         report("Cannot scroll beyond the last line");
2244                         return;
2245                 }
2246                 break;
2248         case REQ_SCROLL_PAGE_UP:
2249                 lines = view->height;
2250         case REQ_SCROLL_LINE_UP:
2251                 if (lines > view->offset)
2252                         lines = view->offset;
2254                 if (lines == 0) {
2255                         report("Cannot scroll beyond the first line");
2256                         return;
2257                 }
2259                 lines = -lines;
2260                 break;
2262         default:
2263                 die("request %d not handled in switch", request);
2264         }
2266         do_scroll_view(view, lines);
2269 /* Cursor moving */
2270 static void
2271 move_view(struct view *view, enum request request)
2273         int scroll_steps = 0;
2274         int steps;
2276         switch (request) {
2277         case REQ_MOVE_FIRST_LINE:
2278                 steps = -view->lineno;
2279                 break;
2281         case REQ_MOVE_LAST_LINE:
2282                 steps = view->lines - view->lineno - 1;
2283                 break;
2285         case REQ_MOVE_PAGE_UP:
2286                 steps = view->height > view->lineno
2287                       ? -view->lineno : -view->height;
2288                 break;
2290         case REQ_MOVE_PAGE_DOWN:
2291                 steps = view->lineno + view->height >= view->lines
2292                       ? view->lines - view->lineno - 1 : view->height;
2293                 break;
2295         case REQ_MOVE_UP:
2296                 steps = -1;
2297                 break;
2299         case REQ_MOVE_DOWN:
2300                 steps = 1;
2301                 break;
2303         default:
2304                 die("request %d not handled in switch", request);
2305         }
2307         if (steps <= 0 && view->lineno == 0) {
2308                 report("Cannot move beyond the first line");
2309                 return;
2311         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2312                 report("Cannot move beyond the last line");
2313                 return;
2314         }
2316         /* Move the current line */
2317         view->lineno += steps;
2318         assert(0 <= view->lineno && view->lineno < view->lines);
2320         /* Check whether the view needs to be scrolled */
2321         if (view->lineno < view->offset ||
2322             view->lineno >= view->offset + view->height) {
2323                 scroll_steps = steps;
2324                 if (steps < 0 && -steps > view->offset) {
2325                         scroll_steps = -view->offset;
2327                 } else if (steps > 0) {
2328                         if (view->lineno == view->lines - 1 &&
2329                             view->lines > view->height) {
2330                                 scroll_steps = view->lines - view->offset - 1;
2331                                 if (scroll_steps >= view->height)
2332                                         scroll_steps -= view->height - 1;
2333                         }
2334                 }
2335         }
2337         if (!view_is_displayed(view)) {
2338                 view->offset += scroll_steps;
2339                 assert(0 <= view->offset && view->offset < view->lines);
2340                 view->ops->select(view, &view->line[view->lineno]);
2341                 return;
2342         }
2344         /* Repaint the old "current" line if we be scrolling */
2345         if (ABS(steps) < view->height)
2346                 draw_view_line(view, view->lineno - steps - view->offset);
2348         if (scroll_steps) {
2349                 do_scroll_view(view, scroll_steps);
2350                 return;
2351         }
2353         /* Draw the current line */
2354         draw_view_line(view, view->lineno - view->offset);
2356         redrawwin(view->win);
2357         wrefresh(view->win);
2358         report("");
2362 /*
2363  * Searching
2364  */
2366 static void search_view(struct view *view, enum request request);
2368 static bool
2369 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2371         assert(view_is_displayed(view));
2373         if (!view->ops->grep(view, line))
2374                 return FALSE;
2376         if (lineno - view->offset >= view->height) {
2377                 view->offset = lineno;
2378                 view->lineno = lineno;
2379                 redraw_view(view);
2381         } else {
2382                 unsigned long old_lineno = view->lineno - view->offset;
2384                 view->lineno = lineno;
2385                 draw_view_line(view, old_lineno);
2387                 draw_view_line(view, view->lineno - view->offset);
2388                 redrawwin(view->win);
2389                 wrefresh(view->win);
2390         }
2392         report("Line %ld matches '%s'", lineno + 1, view->grep);
2393         return TRUE;
2396 static void
2397 find_next(struct view *view, enum request request)
2399         unsigned long lineno = view->lineno;
2400         int direction;
2402         if (!*view->grep) {
2403                 if (!*opt_search)
2404                         report("No previous search");
2405                 else
2406                         search_view(view, request);
2407                 return;
2408         }
2410         switch (request) {
2411         case REQ_SEARCH:
2412         case REQ_FIND_NEXT:
2413                 direction = 1;
2414                 break;
2416         case REQ_SEARCH_BACK:
2417         case REQ_FIND_PREV:
2418                 direction = -1;
2419                 break;
2421         default:
2422                 return;
2423         }
2425         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2426                 lineno += direction;
2428         /* Note, lineno is unsigned long so will wrap around in which case it
2429          * will become bigger than view->lines. */
2430         for (; lineno < view->lines; lineno += direction) {
2431                 struct line *line = &view->line[lineno];
2433                 if (find_next_line(view, lineno, line))
2434                         return;
2435         }
2437         report("No match found for '%s'", view->grep);
2440 static void
2441 search_view(struct view *view, enum request request)
2443         int regex_err;
2445         if (view->regex) {
2446                 regfree(view->regex);
2447                 *view->grep = 0;
2448         } else {
2449                 view->regex = calloc(1, sizeof(*view->regex));
2450                 if (!view->regex)
2451                         return;
2452         }
2454         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2455         if (regex_err != 0) {
2456                 char buf[SIZEOF_STR] = "unknown error";
2458                 regerror(regex_err, view->regex, buf, sizeof(buf));
2459                 report("Search failed: %s", buf);
2460                 return;
2461         }
2463         string_copy(view->grep, opt_search);
2465         find_next(view, request);
2468 /*
2469  * Incremental updating
2470  */
2472 static void
2473 reset_view(struct view *view)
2475         int i;
2477         for (i = 0; i < view->lines; i++)
2478                 free(view->line[i].data);
2479         free(view->line);
2481         view->line = NULL;
2482         view->offset = 0;
2483         view->lines  = 0;
2484         view->lineno = 0;
2485         view->line_size = 0;
2486         view->line_alloc = 0;
2487         view->vid[0] = 0;
2490 static void
2491 free_argv(const char *argv[])
2493         int argc;
2495         for (argc = 0; argv[argc]; argc++)
2496                 free((void *) argv[argc]);
2499 static bool
2500 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2502         char buf[SIZEOF_STR];
2503         int argc;
2504         bool noreplace = flags == FORMAT_NONE;
2506         free_argv(dst_argv);
2508         for (argc = 0; src_argv[argc]; argc++) {
2509                 const char *arg = src_argv[argc];
2510                 size_t bufpos = 0;
2512                 while (arg) {
2513                         char *next = strstr(arg, "%(");
2514                         int len = next - arg;
2515                         const char *value;
2517                         if (!next || noreplace) {
2518                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2519                                         noreplace = TRUE;
2520                                 len = strlen(arg);
2521                                 value = "";
2523                         } else if (!prefixcmp(next, "%(directory)")) {
2524                                 value = opt_path;
2526                         } else if (!prefixcmp(next, "%(file)")) {
2527                                 value = opt_file;
2529                         } else if (!prefixcmp(next, "%(ref)")) {
2530                                 value = *opt_ref ? opt_ref : "HEAD";
2532                         } else if (!prefixcmp(next, "%(head)")) {
2533                                 value = ref_head;
2535                         } else if (!prefixcmp(next, "%(commit)")) {
2536                                 value = ref_commit;
2538                         } else if (!prefixcmp(next, "%(blob)")) {
2539                                 value = ref_blob;
2541                         } else {
2542                                 report("Unknown replacement: `%s`", next);
2543                                 return FALSE;
2544                         }
2546                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2547                                 return FALSE;
2549                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2550                 }
2552                 dst_argv[argc] = strdup(buf);
2553                 if (!dst_argv[argc])
2554                         break;
2555         }
2557         dst_argv[argc] = NULL;
2559         return src_argv[argc] == NULL;
2562 static void
2563 end_update(struct view *view, bool force)
2565         if (!view->pipe)
2566                 return;
2567         while (!view->ops->read(view, NULL))
2568                 if (!force)
2569                         return;
2570         set_nonblocking_input(FALSE);
2571         if (force)
2572                 kill_io(view->pipe);
2573         done_io(view->pipe);
2574         view->pipe = NULL;
2577 static void
2578 setup_update(struct view *view, const char *vid)
2580         set_nonblocking_input(TRUE);
2581         reset_view(view);
2582         string_copy_rev(view->vid, vid);
2583         view->pipe = &view->io;
2584         view->start_time = time(NULL);
2587 static bool
2588 prepare_update(struct view *view, const char *argv[], const char *dir,
2589                enum format_flags flags)
2591         if (view->pipe)
2592                 end_update(view, TRUE);
2593         return init_io_rd(&view->io, argv, dir, flags);
2596 static bool
2597 prepare_update_file(struct view *view, const char *name)
2599         if (view->pipe)
2600                 end_update(view, TRUE);
2601         return io_open(&view->io, name);
2604 static bool
2605 begin_update(struct view *view, bool refresh)
2607         if (refresh) {
2608                 if (!start_io(&view->io))
2609                         return FALSE;
2611         } else {
2612                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2613                         opt_path[0] = 0;
2615                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2616                         return FALSE;
2618                 /* Put the current ref_* value to the view title ref
2619                  * member. This is needed by the blob view. Most other
2620                  * views sets it automatically after loading because the
2621                  * first line is a commit line. */
2622                 string_copy_rev(view->ref, view->id);
2623         }
2625         setup_update(view, view->id);
2627         return TRUE;
2630 #define ITEM_CHUNK_SIZE 256
2631 static void *
2632 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2634         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2635         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2637         if (mem == NULL || num_chunks != num_chunks_new) {
2638                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2639                 mem = realloc(mem, *size * item_size);
2640         }
2642         return mem;
2645 static struct line *
2646 realloc_lines(struct view *view, size_t line_size)
2648         size_t alloc = view->line_alloc;
2649         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2650                                          sizeof(*view->line));
2652         if (!tmp)
2653                 return NULL;
2655         view->line = tmp;
2656         view->line_alloc = alloc;
2657         view->line_size = line_size;
2658         return view->line;
2661 static bool
2662 update_view(struct view *view)
2664         char out_buffer[BUFSIZ * 2];
2665         char *line;
2666         /* The number of lines to read. If too low it will cause too much
2667          * redrawing (and possible flickering), if too high responsiveness
2668          * will suffer. */
2669         unsigned long lines = view->height;
2670         int redraw_from = -1;
2672         if (!view->pipe)
2673                 return TRUE;
2675         /* Only redraw if lines are visible. */
2676         if (view->offset + view->height >= view->lines)
2677                 redraw_from = view->lines - view->offset;
2679         /* FIXME: This is probably not perfect for backgrounded views. */
2680         if (!realloc_lines(view, view->lines + lines))
2681                 goto alloc_error;
2683         while ((line = io_gets(view->pipe))) {
2684                 size_t linelen = strlen(line);
2686                 if (opt_iconv != ICONV_NONE) {
2687                         ICONV_CONST char *inbuf = line;
2688                         size_t inlen = linelen;
2690                         char *outbuf = out_buffer;
2691                         size_t outlen = sizeof(out_buffer);
2693                         size_t ret;
2695                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2696                         if (ret != (size_t) -1) {
2697                                 line = out_buffer;
2698                                 linelen = strlen(out_buffer);
2699                         }
2700                 }
2702                 if (!view->ops->read(view, line))
2703                         goto alloc_error;
2705                 if (lines-- == 1)
2706                         break;
2707         }
2709         {
2710                 int digits;
2712                 lines = view->lines;
2713                 for (digits = 0; lines; digits++)
2714                         lines /= 10;
2716                 /* Keep the displayed view in sync with line number scaling. */
2717                 if (digits != view->digits) {
2718                         view->digits = digits;
2719                         redraw_from = 0;
2720                 }
2721         }
2723         if (io_error(view->pipe)) {
2724                 report("Failed to read: %s", io_strerror(view->pipe));
2725                 end_update(view, TRUE);
2727         } else if (io_eof(view->pipe)) {
2728                 report("");
2729                 end_update(view, FALSE);
2730         }
2732         if (!view_is_displayed(view))
2733                 return TRUE;
2735         if (view == VIEW(REQ_VIEW_TREE)) {
2736                 /* Clear the view and redraw everything since the tree sorting
2737                  * might have rearranged things. */
2738                 redraw_view(view);
2740         } else if (redraw_from >= 0) {
2741                 /* If this is an incremental update, redraw the previous line
2742                  * since for commits some members could have changed when
2743                  * loading the main view. */
2744                 if (redraw_from > 0)
2745                         redraw_from--;
2747                 /* Since revision graph visualization requires knowledge
2748                  * about the parent commit, it causes a further one-off
2749                  * needed to be redrawn for incremental updates. */
2750                 if (redraw_from > 0 && opt_rev_graph)
2751                         redraw_from--;
2753                 /* Incrementally draw avoids flickering. */
2754                 redraw_view_from(view, redraw_from);
2755         }
2757         if (view == VIEW(REQ_VIEW_BLAME))
2758                 redraw_view_dirty(view);
2760         /* Update the title _after_ the redraw so that if the redraw picks up a
2761          * commit reference in view->ref it'll be available here. */
2762         update_view_title(view);
2763         return TRUE;
2765 alloc_error:
2766         report("Allocation failure");
2767         end_update(view, TRUE);
2768         return FALSE;
2771 static struct line *
2772 add_line_data(struct view *view, void *data, enum line_type type)
2774         struct line *line = &view->line[view->lines++];
2776         memset(line, 0, sizeof(*line));
2777         line->type = type;
2778         line->data = data;
2780         return line;
2783 static struct line *
2784 add_line_text(struct view *view, const char *text, enum line_type type)
2786         char *data = text ? strdup(text) : NULL;
2788         return data ? add_line_data(view, data, type) : NULL;
2792 /*
2793  * View opening
2794  */
2796 enum open_flags {
2797         OPEN_DEFAULT = 0,       /* Use default view switching. */
2798         OPEN_SPLIT = 1,         /* Split current view. */
2799         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2800         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2801         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2802         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2803         OPEN_PREPARED = 32,     /* Open already prepared command. */
2804 };
2806 static void
2807 open_view(struct view *prev, enum request request, enum open_flags flags)
2809         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2810         bool split = !!(flags & OPEN_SPLIT);
2811         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2812         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2813         struct view *view = VIEW(request);
2814         int nviews = displayed_views();
2815         struct view *base_view = display[0];
2817         if (view == prev && nviews == 1 && !reload) {
2818                 report("Already in %s view", view->name);
2819                 return;
2820         }
2822         if (view->git_dir && !opt_git_dir[0]) {
2823                 report("The %s view is disabled in pager view", view->name);
2824                 return;
2825         }
2827         if (split) {
2828                 display[1] = view;
2829                 if (!backgrounded)
2830                         current_view = 1;
2831         } else if (!nomaximize) {
2832                 /* Maximize the current view. */
2833                 memset(display, 0, sizeof(display));
2834                 current_view = 0;
2835                 display[current_view] = view;
2836         }
2838         /* Resize the view when switching between split- and full-screen,
2839          * or when switching between two different full-screen views. */
2840         if (nviews != displayed_views() ||
2841             (nviews == 1 && base_view != display[0]))
2842                 resize_display();
2844         if (view->pipe)
2845                 end_update(view, TRUE);
2847         if (view->ops->open) {
2848                 if (!view->ops->open(view)) {
2849                         report("Failed to load %s view", view->name);
2850                         return;
2851                 }
2853         } else if ((reload || strcmp(view->vid, view->id)) &&
2854                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2855                 report("Failed to load %s view", view->name);
2856                 return;
2857         }
2859         if (split && prev->lineno - prev->offset >= prev->height) {
2860                 /* Take the title line into account. */
2861                 int lines = prev->lineno - prev->offset - prev->height + 1;
2863                 /* Scroll the view that was split if the current line is
2864                  * outside the new limited view. */
2865                 do_scroll_view(prev, lines);
2866         }
2868         if (prev && view != prev) {
2869                 if (split && !backgrounded) {
2870                         /* "Blur" the previous view. */
2871                         update_view_title(prev);
2872                 }
2874                 view->parent = prev;
2875         }
2877         if (view->pipe && view->lines == 0) {
2878                 /* Clear the old view and let the incremental updating refill
2879                  * the screen. */
2880                 werase(view->win);
2881                 report("");
2882         } else if (view_is_displayed(view)) {
2883                 redraw_view(view);
2884                 report("");
2885         }
2887         /* If the view is backgrounded the above calls to report()
2888          * won't redraw the view title. */
2889         if (backgrounded)
2890                 update_view_title(view);
2893 static void
2894 open_external_viewer(const char *argv[], const char *dir)
2896         def_prog_mode();           /* save current tty modes */
2897         endwin();                  /* restore original tty modes */
2898         run_io_fg(argv, dir);
2899         fprintf(stderr, "Press Enter to continue");
2900         getc(opt_tty);
2901         reset_prog_mode();
2902         redraw_display();
2905 static void
2906 open_mergetool(const char *file)
2908         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2910         open_external_viewer(mergetool_argv, NULL);
2913 static void
2914 open_editor(bool from_root, const char *file)
2916         const char *editor_argv[] = { "vi", file, NULL };
2917         const char *editor;
2919         editor = getenv("GIT_EDITOR");
2920         if (!editor && *opt_editor)
2921                 editor = opt_editor;
2922         if (!editor)
2923                 editor = getenv("VISUAL");
2924         if (!editor)
2925                 editor = getenv("EDITOR");
2926         if (!editor)
2927                 editor = "vi";
2929         editor_argv[0] = editor;
2930         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2933 static void
2934 open_run_request(enum request request)
2936         struct run_request *req = get_run_request(request);
2937         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2939         if (!req) {
2940                 report("Unknown run request");
2941                 return;
2942         }
2944         if (format_argv(argv, req->argv, FORMAT_ALL))
2945                 open_external_viewer(argv, NULL);
2946         free_argv(argv);
2949 /*
2950  * User request switch noodle
2951  */
2953 static int
2954 view_driver(struct view *view, enum request request)
2956         int i;
2958         if (request == REQ_NONE) {
2959                 doupdate();
2960                 return TRUE;
2961         }
2963         if (request > REQ_NONE) {
2964                 open_run_request(request);
2965                 /* FIXME: When all views can refresh always do this. */
2966                 if (view == VIEW(REQ_VIEW_STATUS) ||
2967                     view == VIEW(REQ_VIEW_MAIN) ||
2968                     view == VIEW(REQ_VIEW_LOG) ||
2969                     view == VIEW(REQ_VIEW_STAGE))
2970                         request = REQ_REFRESH;
2971                 else
2972                         return TRUE;
2973         }
2975         if (view && view->lines) {
2976                 request = view->ops->request(view, request, &view->line[view->lineno]);
2977                 if (request == REQ_NONE)
2978                         return TRUE;
2979         }
2981         switch (request) {
2982         case REQ_MOVE_UP:
2983         case REQ_MOVE_DOWN:
2984         case REQ_MOVE_PAGE_UP:
2985         case REQ_MOVE_PAGE_DOWN:
2986         case REQ_MOVE_FIRST_LINE:
2987         case REQ_MOVE_LAST_LINE:
2988                 move_view(view, request);
2989                 break;
2991         case REQ_SCROLL_LINE_DOWN:
2992         case REQ_SCROLL_LINE_UP:
2993         case REQ_SCROLL_PAGE_DOWN:
2994         case REQ_SCROLL_PAGE_UP:
2995                 scroll_view(view, request);
2996                 break;
2998         case REQ_VIEW_BLAME:
2999                 if (!opt_file[0]) {
3000                         report("No file chosen, press %s to open tree view",
3001                                get_key(REQ_VIEW_TREE));
3002                         break;
3003                 }
3004                 open_view(view, request, OPEN_DEFAULT);
3005                 break;
3007         case REQ_VIEW_BLOB:
3008                 if (!ref_blob[0]) {
3009                         report("No file chosen, press %s to open tree view",
3010                                get_key(REQ_VIEW_TREE));
3011                         break;
3012                 }
3013                 open_view(view, request, OPEN_DEFAULT);
3014                 break;
3016         case REQ_VIEW_PAGER:
3017                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3018                         report("No pager content, press %s to run command from prompt",
3019                                get_key(REQ_PROMPT));
3020                         break;
3021                 }
3022                 open_view(view, request, OPEN_DEFAULT);
3023                 break;
3025         case REQ_VIEW_STAGE:
3026                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3027                         report("No stage content, press %s to open the status view and choose file",
3028                                get_key(REQ_VIEW_STATUS));
3029                         break;
3030                 }
3031                 open_view(view, request, OPEN_DEFAULT);
3032                 break;
3034         case REQ_VIEW_STATUS:
3035                 if (opt_is_inside_work_tree == FALSE) {
3036                         report("The status view requires a working tree");
3037                         break;
3038                 }
3039                 open_view(view, request, OPEN_DEFAULT);
3040                 break;
3042         case REQ_VIEW_MAIN:
3043         case REQ_VIEW_DIFF:
3044         case REQ_VIEW_LOG:
3045         case REQ_VIEW_TREE:
3046         case REQ_VIEW_HELP:
3047                 open_view(view, request, OPEN_DEFAULT);
3048                 break;
3050         case REQ_NEXT:
3051         case REQ_PREVIOUS:
3052                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3054                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3055                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3056                    (view == VIEW(REQ_VIEW_DIFF) &&
3057                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3058                    (view == VIEW(REQ_VIEW_STAGE) &&
3059                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3060                    (view == VIEW(REQ_VIEW_BLOB) &&
3061                      view->parent == VIEW(REQ_VIEW_TREE))) {
3062                         int line;
3064                         view = view->parent;
3065                         line = view->lineno;
3066                         move_view(view, request);
3067                         if (view_is_displayed(view))
3068                                 update_view_title(view);
3069                         if (line != view->lineno)
3070                                 view->ops->request(view, REQ_ENTER,
3071                                                    &view->line[view->lineno]);
3073                 } else {
3074                         move_view(view, request);
3075                 }
3076                 break;
3078         case REQ_VIEW_NEXT:
3079         {
3080                 int nviews = displayed_views();
3081                 int next_view = (current_view + 1) % nviews;
3083                 if (next_view == current_view) {
3084                         report("Only one view is displayed");
3085                         break;
3086                 }
3088                 current_view = next_view;
3089                 /* Blur out the title of the previous view. */
3090                 update_view_title(view);
3091                 report("");
3092                 break;
3093         }
3094         case REQ_REFRESH:
3095                 report("Refreshing is not yet supported for the %s view", view->name);
3096                 break;
3098         case REQ_MAXIMIZE:
3099                 if (displayed_views() == 2)
3100                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3101                 break;
3103         case REQ_TOGGLE_LINENO:
3104                 opt_line_number = !opt_line_number;
3105                 redraw_display();
3106                 break;
3108         case REQ_TOGGLE_DATE:
3109                 opt_date = !opt_date;
3110                 redraw_display();
3111                 break;
3113         case REQ_TOGGLE_AUTHOR:
3114                 opt_author = !opt_author;
3115                 redraw_display();
3116                 break;
3118         case REQ_TOGGLE_REV_GRAPH:
3119                 opt_rev_graph = !opt_rev_graph;
3120                 redraw_display();
3121                 break;
3123         case REQ_TOGGLE_REFS:
3124                 opt_show_refs = !opt_show_refs;
3125                 redraw_display();
3126                 break;
3128         case REQ_SEARCH:
3129         case REQ_SEARCH_BACK:
3130                 search_view(view, request);
3131                 break;
3133         case REQ_FIND_NEXT:
3134         case REQ_FIND_PREV:
3135                 find_next(view, request);
3136                 break;
3138         case REQ_STOP_LOADING:
3139                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3140                         view = &views[i];
3141                         if (view->pipe)
3142                                 report("Stopped loading the %s view", view->name),
3143                         end_update(view, TRUE);
3144                 }
3145                 break;
3147         case REQ_SHOW_VERSION:
3148                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3149                 return TRUE;
3151         case REQ_SCREEN_RESIZE:
3152                 resize_display();
3153                 /* Fall-through */
3154         case REQ_SCREEN_REDRAW:
3155                 redraw_display();
3156                 break;
3158         case REQ_EDIT:
3159                 report("Nothing to edit");
3160                 break;
3162         case REQ_ENTER:
3163                 report("Nothing to enter");
3164                 break;
3166         case REQ_VIEW_CLOSE:
3167                 /* XXX: Mark closed views by letting view->parent point to the
3168                  * view itself. Parents to closed view should never be
3169                  * followed. */
3170                 if (view->parent &&
3171                     view->parent->parent != view->parent) {
3172                         memset(display, 0, sizeof(display));
3173                         current_view = 0;
3174                         display[current_view] = view->parent;
3175                         view->parent = view;
3176                         resize_display();
3177                         redraw_display();
3178                         report("");
3179                         break;
3180                 }
3181                 /* Fall-through */
3182         case REQ_QUIT:
3183                 return FALSE;
3185         default:
3186                 report("Unknown key, press 'h' for help");
3187                 return TRUE;
3188         }
3190         return TRUE;
3194 /*
3195  * Pager backend
3196  */
3198 static bool
3199 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3201         char *text = line->data;
3203         if (opt_line_number && draw_lineno(view, lineno))
3204                 return TRUE;
3206         draw_text(view, line->type, text, TRUE);
3207         return TRUE;
3210 static bool
3211 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3213         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3214         char refbuf[SIZEOF_STR];
3215         char *ref = NULL;
3217         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3218                 ref = chomp_string(refbuf);
3220         if (!ref || !*ref)
3221                 return TRUE;
3223         /* This is the only fatal call, since it can "corrupt" the buffer. */
3224         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3225                 return FALSE;
3227         return TRUE;
3230 static void
3231 add_pager_refs(struct view *view, struct line *line)
3233         char buf[SIZEOF_STR];
3234         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3235         struct ref **refs;
3236         size_t bufpos = 0, refpos = 0;
3237         const char *sep = "Refs: ";
3238         bool is_tag = FALSE;
3240         assert(line->type == LINE_COMMIT);
3242         refs = get_refs(commit_id);
3243         if (!refs) {
3244                 if (view == VIEW(REQ_VIEW_DIFF))
3245                         goto try_add_describe_ref;
3246                 return;
3247         }
3249         do {
3250                 struct ref *ref = refs[refpos];
3251                 const char *fmt = ref->tag    ? "%s[%s]" :
3252                                   ref->remote ? "%s<%s>" : "%s%s";
3254                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3255                         return;
3256                 sep = ", ";
3257                 if (ref->tag)
3258                         is_tag = TRUE;
3259         } while (refs[refpos++]->next);
3261         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3262 try_add_describe_ref:
3263                 /* Add <tag>-g<commit_id> "fake" reference. */
3264                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3265                         return;
3266         }
3268         if (bufpos == 0)
3269                 return;
3271         if (!realloc_lines(view, view->line_size + 1))
3272                 return;
3274         add_line_text(view, buf, LINE_PP_REFS);
3277 static bool
3278 pager_read(struct view *view, char *data)
3280         struct line *line;
3282         if (!data)
3283                 return TRUE;
3285         line = add_line_text(view, data, get_line_type(data));
3286         if (!line)
3287                 return FALSE;
3289         if (line->type == LINE_COMMIT &&
3290             (view == VIEW(REQ_VIEW_DIFF) ||
3291              view == VIEW(REQ_VIEW_LOG)))
3292                 add_pager_refs(view, line);
3294         return TRUE;
3297 static enum request
3298 pager_request(struct view *view, enum request request, struct line *line)
3300         int split = 0;
3302         if (request != REQ_ENTER)
3303                 return request;
3305         if (line->type == LINE_COMMIT &&
3306            (view == VIEW(REQ_VIEW_LOG) ||
3307             view == VIEW(REQ_VIEW_PAGER))) {
3308                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3309                 split = 1;
3310         }
3312         /* Always scroll the view even if it was split. That way
3313          * you can use Enter to scroll through the log view and
3314          * split open each commit diff. */
3315         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3317         /* FIXME: A minor workaround. Scrolling the view will call report("")
3318          * but if we are scrolling a non-current view this won't properly
3319          * update the view title. */
3320         if (split)
3321                 update_view_title(view);
3323         return REQ_NONE;
3326 static bool
3327 pager_grep(struct view *view, struct line *line)
3329         regmatch_t pmatch;
3330         char *text = line->data;
3332         if (!*text)
3333                 return FALSE;
3335         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3336                 return FALSE;
3338         return TRUE;
3341 static void
3342 pager_select(struct view *view, struct line *line)
3344         if (line->type == LINE_COMMIT) {
3345                 char *text = (char *)line->data + STRING_SIZE("commit ");
3347                 if (view != VIEW(REQ_VIEW_PAGER))
3348                         string_copy_rev(view->ref, text);
3349                 string_copy_rev(ref_commit, text);
3350         }
3353 static struct view_ops pager_ops = {
3354         "line",
3355         NULL,
3356         NULL,
3357         pager_read,
3358         pager_draw,
3359         pager_request,
3360         pager_grep,
3361         pager_select,
3362 };
3364 static const char *log_argv[SIZEOF_ARG] = {
3365         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3366 };
3368 static enum request
3369 log_request(struct view *view, enum request request, struct line *line)
3371         switch (request) {
3372         case REQ_REFRESH:
3373                 load_refs();
3374                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3375                 return REQ_NONE;
3376         default:
3377                 return pager_request(view, request, line);
3378         }
3381 static struct view_ops log_ops = {
3382         "line",
3383         log_argv,
3384         NULL,
3385         pager_read,
3386         pager_draw,
3387         log_request,
3388         pager_grep,
3389         pager_select,
3390 };
3392 static const char *diff_argv[SIZEOF_ARG] = {
3393         "git", "show", "--pretty=fuller", "--no-color", "--root",
3394                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3395 };
3397 static struct view_ops diff_ops = {
3398         "line",
3399         diff_argv,
3400         NULL,
3401         pager_read,
3402         pager_draw,
3403         pager_request,
3404         pager_grep,
3405         pager_select,
3406 };
3408 /*
3409  * Help backend
3410  */
3412 static bool
3413 help_open(struct view *view)
3415         char buf[BUFSIZ];
3416         int lines = ARRAY_SIZE(req_info) + 2;
3417         int i;
3419         if (view->lines > 0)
3420                 return TRUE;
3422         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3423                 if (!req_info[i].request)
3424                         lines++;
3426         lines += run_requests + 1;
3428         view->line = calloc(lines, sizeof(*view->line));
3429         if (!view->line)
3430                 return FALSE;
3432         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3434         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3435                 const char *key;
3437                 if (req_info[i].request == REQ_NONE)
3438                         continue;
3440                 if (!req_info[i].request) {
3441                         add_line_text(view, "", LINE_DEFAULT);
3442                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3443                         continue;
3444                 }
3446                 key = get_key(req_info[i].request);
3447                 if (!*key)
3448                         key = "(no key defined)";
3450                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3451                         continue;
3453                 add_line_text(view, buf, LINE_DEFAULT);
3454         }
3456         if (run_requests) {
3457                 add_line_text(view, "", LINE_DEFAULT);
3458                 add_line_text(view, "External commands:", LINE_DEFAULT);
3459         }
3461         for (i = 0; i < run_requests; i++) {
3462                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3463                 const char *key;
3464                 char cmd[SIZEOF_STR];
3465                 size_t bufpos;
3466                 int argc;
3468                 if (!req)
3469                         continue;
3471                 key = get_key_name(req->key);
3472                 if (!*key)
3473                         key = "(no key defined)";
3475                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3476                         if (!string_format_from(cmd, &bufpos, "%s%s",
3477                                                 argc ? " " : "", req->argv[argc]))
3478                                 return REQ_NONE;
3480                 if (!string_format(buf, "    %-10s %-14s `%s`",
3481                                    keymap_table[req->keymap].name, key, cmd))
3482                         continue;
3484                 add_line_text(view, buf, LINE_DEFAULT);
3485         }
3487         return TRUE;
3490 static struct view_ops help_ops = {
3491         "line",
3492         NULL,
3493         help_open,
3494         NULL,
3495         pager_draw,
3496         pager_request,
3497         pager_grep,
3498         pager_select,
3499 };
3502 /*
3503  * Tree backend
3504  */
3506 struct tree_stack_entry {
3507         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3508         unsigned long lineno;           /* Line number to restore */
3509         char *name;                     /* Position of name in opt_path */
3510 };
3512 /* The top of the path stack. */
3513 static struct tree_stack_entry *tree_stack = NULL;
3514 unsigned long tree_lineno = 0;
3516 static void
3517 pop_tree_stack_entry(void)
3519         struct tree_stack_entry *entry = tree_stack;
3521         tree_lineno = entry->lineno;
3522         entry->name[0] = 0;
3523         tree_stack = entry->prev;
3524         free(entry);
3527 static void
3528 push_tree_stack_entry(const char *name, unsigned long lineno)
3530         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3531         size_t pathlen = strlen(opt_path);
3533         if (!entry)
3534                 return;
3536         entry->prev = tree_stack;
3537         entry->name = opt_path + pathlen;
3538         tree_stack = entry;
3540         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3541                 pop_tree_stack_entry();
3542                 return;
3543         }
3545         /* Move the current line to the first tree entry. */
3546         tree_lineno = 1;
3547         entry->lineno = lineno;
3550 /* Parse output from git-ls-tree(1):
3551  *
3552  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3553  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3554  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3555  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3556  */
3558 #define SIZEOF_TREE_ATTR \
3559         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3561 #define TREE_UP_FORMAT "040000 tree %s\t.."
3563 static int
3564 tree_compare_entry(enum line_type type1, const char *name1,
3565                    enum line_type type2, const char *name2)
3567         if (type1 != type2) {
3568                 if (type1 == LINE_TREE_DIR)
3569                         return -1;
3570                 return 1;
3571         }
3573         return strcmp(name1, name2);
3576 static const char *
3577 tree_path(struct line *line)
3579         const char *path = line->data;
3581         return path + SIZEOF_TREE_ATTR;
3584 static bool
3585 tree_read(struct view *view, char *text)
3587         size_t textlen = text ? strlen(text) : 0;
3588         char buf[SIZEOF_STR];
3589         unsigned long pos;
3590         enum line_type type;
3591         bool first_read = view->lines == 0;
3593         if (!text)
3594                 return TRUE;
3595         if (textlen <= SIZEOF_TREE_ATTR)
3596                 return FALSE;
3598         type = text[STRING_SIZE("100644 ")] == 't'
3599              ? LINE_TREE_DIR : LINE_TREE_FILE;
3601         if (first_read) {
3602                 /* Add path info line */
3603                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3604                     !realloc_lines(view, view->line_size + 1) ||
3605                     !add_line_text(view, buf, LINE_DEFAULT))
3606                         return FALSE;
3608                 /* Insert "link" to parent directory. */
3609                 if (*opt_path) {
3610                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3611                             !realloc_lines(view, view->line_size + 1) ||
3612                             !add_line_text(view, buf, LINE_TREE_DIR))
3613                                 return FALSE;
3614                 }
3615         }
3617         /* Strip the path part ... */
3618         if (*opt_path) {
3619                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3620                 size_t striplen = strlen(opt_path);
3621                 char *path = text + SIZEOF_TREE_ATTR;
3623                 if (pathlen > striplen)
3624                         memmove(path, path + striplen,
3625                                 pathlen - striplen + 1);
3626         }
3628         /* Skip "Directory ..." and ".." line. */
3629         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3630                 struct line *line = &view->line[pos];
3631                 const char *path1 = tree_path(line);
3632                 char *path2 = text + SIZEOF_TREE_ATTR;
3633                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3635                 if (cmp <= 0)
3636                         continue;
3638                 text = strdup(text);
3639                 if (!text)
3640                         return FALSE;
3642                 if (view->lines > pos)
3643                         memmove(&view->line[pos + 1], &view->line[pos],
3644                                 (view->lines - pos) * sizeof(*line));
3646                 line = &view->line[pos];
3647                 line->data = text;
3648                 line->type = type;
3649                 view->lines++;
3650                 return TRUE;
3651         }
3653         if (!add_line_text(view, text, type))
3654                 return FALSE;
3656         if (tree_lineno > view->lineno) {
3657                 view->lineno = tree_lineno;
3658                 tree_lineno = 0;
3659         }
3661         return TRUE;
3664 static enum request
3665 tree_request(struct view *view, enum request request, struct line *line)
3667         enum open_flags flags;
3669         switch (request) {
3670         case REQ_VIEW_BLAME:
3671                 if (line->type != LINE_TREE_FILE) {
3672                         report("Blame only supported for files");
3673                         return REQ_NONE;
3674                 }
3676                 string_copy(opt_ref, view->vid);
3677                 return request;
3679         case REQ_EDIT:
3680                 if (line->type != LINE_TREE_FILE) {
3681                         report("Edit only supported for files");
3682                 } else if (!is_head_commit(view->vid)) {
3683                         report("Edit only supported for files in the current work tree");
3684                 } else {
3685                         open_editor(TRUE, opt_file);
3686                 }
3687                 return REQ_NONE;
3689         case REQ_TREE_PARENT:
3690                 if (!*opt_path) {
3691                         /* quit view if at top of tree */
3692                         return REQ_VIEW_CLOSE;
3693                 }
3694                 /* fake 'cd  ..' */
3695                 line = &view->line[1];
3696                 break;
3698         case REQ_ENTER:
3699                 break;
3701         default:
3702                 return request;
3703         }
3705         /* Cleanup the stack if the tree view is at a different tree. */
3706         while (!*opt_path && tree_stack)
3707                 pop_tree_stack_entry();
3709         switch (line->type) {
3710         case LINE_TREE_DIR:
3711                 /* Depending on whether it is a subdir or parent (updir?) link
3712                  * mangle the path buffer. */
3713                 if (line == &view->line[1] && *opt_path) {
3714                         pop_tree_stack_entry();
3716                 } else {
3717                         const char *basename = tree_path(line);
3719                         push_tree_stack_entry(basename, view->lineno);
3720                 }
3722                 /* Trees and subtrees share the same ID, so they are not not
3723                  * unique like blobs. */
3724                 flags = OPEN_RELOAD;
3725                 request = REQ_VIEW_TREE;
3726                 break;
3728         case LINE_TREE_FILE:
3729                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3730                 request = REQ_VIEW_BLOB;
3731                 break;
3733         default:
3734                 return TRUE;
3735         }
3737         open_view(view, request, flags);
3738         if (request == REQ_VIEW_TREE) {
3739                 view->lineno = tree_lineno;
3740         }
3742         return REQ_NONE;
3745 static void
3746 tree_select(struct view *view, struct line *line)
3748         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3750         if (line->type == LINE_TREE_FILE) {
3751                 string_copy_rev(ref_blob, text);
3752                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3754         } else if (line->type != LINE_TREE_DIR) {
3755                 return;
3756         }
3758         string_copy_rev(view->ref, text);
3761 static const char *tree_argv[SIZEOF_ARG] = {
3762         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3763 };
3765 static struct view_ops tree_ops = {
3766         "file",
3767         tree_argv,
3768         NULL,
3769         tree_read,
3770         pager_draw,
3771         tree_request,
3772         pager_grep,
3773         tree_select,
3774 };
3776 static bool
3777 blob_read(struct view *view, char *line)
3779         if (!line)
3780                 return TRUE;
3781         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3784 static const char *blob_argv[SIZEOF_ARG] = {
3785         "git", "cat-file", "blob", "%(blob)", NULL
3786 };
3788 static struct view_ops blob_ops = {
3789         "line",
3790         blob_argv,
3791         NULL,
3792         blob_read,
3793         pager_draw,
3794         pager_request,
3795         pager_grep,
3796         pager_select,
3797 };
3799 /*
3800  * Blame backend
3801  *
3802  * Loading the blame view is a two phase job:
3803  *
3804  *  1. File content is read either using opt_file from the
3805  *     filesystem or using git-cat-file.
3806  *  2. Then blame information is incrementally added by
3807  *     reading output from git-blame.
3808  */
3810 static const char *blame_head_argv[] = {
3811         "git", "blame", "--incremental", "--", "%(file)", NULL
3812 };
3814 static const char *blame_ref_argv[] = {
3815         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3816 };
3818 static const char *blame_cat_file_argv[] = {
3819         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3820 };
3822 struct blame_commit {
3823         char id[SIZEOF_REV];            /* SHA1 ID. */
3824         char title[128];                /* First line of the commit message. */
3825         char author[75];                /* Author of the commit. */
3826         struct tm time;                 /* Date from the author ident. */
3827         char filename[128];             /* Name of file. */
3828 };
3830 struct blame {
3831         struct blame_commit *commit;
3832         char text[1];
3833 };
3835 static bool
3836 blame_open(struct view *view)
3838         if (*opt_ref || !io_open(&view->io, opt_file)) {
3839                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3840                         return FALSE;
3841         }
3843         setup_update(view, opt_file);
3844         string_format(view->ref, "%s ...", opt_file);
3846         return TRUE;
3849 static struct blame_commit *
3850 get_blame_commit(struct view *view, const char *id)
3852         size_t i;
3854         for (i = 0; i < view->lines; i++) {
3855                 struct blame *blame = view->line[i].data;
3857                 if (!blame->commit)
3858                         continue;
3860                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3861                         return blame->commit;
3862         }
3864         {
3865                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3867                 if (commit)
3868                         string_ncopy(commit->id, id, SIZEOF_REV);
3869                 return commit;
3870         }
3873 static bool
3874 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3876         const char *pos = *posref;
3878         *posref = NULL;
3879         pos = strchr(pos + 1, ' ');
3880         if (!pos || !isdigit(pos[1]))
3881                 return FALSE;
3882         *number = atoi(pos + 1);
3883         if (*number < min || *number > max)
3884                 return FALSE;
3886         *posref = pos;
3887         return TRUE;
3890 static struct blame_commit *
3891 parse_blame_commit(struct view *view, const char *text, int *blamed)
3893         struct blame_commit *commit;
3894         struct blame *blame;
3895         const char *pos = text + SIZEOF_REV - 1;
3896         size_t lineno;
3897         size_t group;
3899         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3900                 return NULL;
3902         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3903             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3904                 return NULL;
3906         commit = get_blame_commit(view, text);
3907         if (!commit)
3908                 return NULL;
3910         *blamed += group;
3911         while (group--) {
3912                 struct line *line = &view->line[lineno + group - 1];
3914                 blame = line->data;
3915                 blame->commit = commit;
3916                 line->dirty = 1;
3917         }
3919         return commit;
3922 static bool
3923 blame_read_file(struct view *view, const char *line, bool *read_file)
3925         if (!line) {
3926                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3927                 struct io io = {};
3929                 if (view->lines == 0 && !view->parent)
3930                         die("No blame exist for %s", view->vid);
3932                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3933                         report("Failed to load blame data");
3934                         return TRUE;
3935                 }
3937                 done_io(view->pipe);
3938                 view->io = io;
3939                 *read_file = FALSE;
3940                 return FALSE;
3942         } else {
3943                 size_t linelen = strlen(line);
3944                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3946                 blame->commit = NULL;
3947                 strncpy(blame->text, line, linelen);
3948                 blame->text[linelen] = 0;
3949                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3950         }
3953 static bool
3954 match_blame_header(const char *name, char **line)
3956         size_t namelen = strlen(name);
3957         bool matched = !strncmp(name, *line, namelen);
3959         if (matched)
3960                 *line += namelen;
3962         return matched;
3965 static bool
3966 blame_read(struct view *view, char *line)
3968         static struct blame_commit *commit = NULL;
3969         static int blamed = 0;
3970         static time_t author_time;
3971         static bool read_file = TRUE;
3973         if (read_file)
3974                 return blame_read_file(view, line, &read_file);
3976         if (!line) {
3977                 /* Reset all! */
3978                 commit = NULL;
3979                 blamed = 0;
3980                 read_file = TRUE;
3981                 string_format(view->ref, "%s", view->vid);
3982                 if (view_is_displayed(view)) {
3983                         update_view_title(view);
3984                         redraw_view_from(view, 0);
3985                 }
3986                 return TRUE;
3987         }
3989         if (!commit) {
3990                 commit = parse_blame_commit(view, line, &blamed);
3991                 string_format(view->ref, "%s %2d%%", view->vid,
3992                               blamed * 100 / view->lines);
3994         } else if (match_blame_header("author ", &line)) {
3995                 string_ncopy(commit->author, line, strlen(line));
3997         } else if (match_blame_header("author-time ", &line)) {
3998                 author_time = (time_t) atol(line);
4000         } else if (match_blame_header("author-tz ", &line)) {
4001                 long tz;
4003                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4004                 tz += ('0' - line[2]) * 60 * 60;
4005                 tz += ('0' - line[3]) * 60;
4006                 tz += ('0' - line[4]) * 60;
4008                 if (line[0] == '-')
4009                         tz = -tz;
4011                 author_time -= tz;
4012                 gmtime_r(&author_time, &commit->time);
4014         } else if (match_blame_header("summary ", &line)) {
4015                 string_ncopy(commit->title, line, strlen(line));
4017         } else if (match_blame_header("filename ", &line)) {
4018                 string_ncopy(commit->filename, line, strlen(line));
4019                 commit = NULL;
4020         }
4022         return TRUE;
4025 static bool
4026 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4028         struct blame *blame = line->data;
4029         struct tm *time = NULL;
4030         const char *id = NULL, *author = NULL;
4032         if (blame->commit && *blame->commit->filename) {
4033                 id = blame->commit->id;
4034                 author = blame->commit->author;
4035                 time = &blame->commit->time;
4036         }
4038         if (opt_date && draw_date(view, time))
4039                 return TRUE;
4041         if (opt_author &&
4042             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4043                 return TRUE;
4045         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4046                 return TRUE;
4048         if (draw_lineno(view, lineno))
4049                 return TRUE;
4051         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4052         return TRUE;
4055 static enum request
4056 blame_request(struct view *view, enum request request, struct line *line)
4058         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4059         struct blame *blame = line->data;
4061         switch (request) {
4062         case REQ_VIEW_BLAME:
4063                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4064                         report("Commit ID unknown");
4065                         break;
4066                 }
4067                 string_copy(opt_ref, blame->commit->id);
4068                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4069                 return request;
4071         case REQ_ENTER:
4072                 if (!blame->commit) {
4073                         report("No commit loaded yet");
4074                         break;
4075                 }
4077                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4078                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4079                         break;
4081                 if (!strcmp(blame->commit->id, NULL_ID)) {
4082                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4083                         const char *diff_index_argv[] = {
4084                                 "git", "diff-index", "--root", "--cached",
4085                                         "--patch-with-stat", "-C", "-M",
4086                                         "HEAD", "--", view->vid, NULL
4087                         };
4089                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4090                                 report("Failed to allocate diff command");
4091                                 break;
4092                         }
4093                         flags |= OPEN_PREPARED;
4094                 }
4096                 open_view(view, REQ_VIEW_DIFF, flags);
4097                 break;
4099         default:
4100                 return request;
4101         }
4103         return REQ_NONE;
4106 static bool
4107 blame_grep(struct view *view, struct line *line)
4109         struct blame *blame = line->data;
4110         struct blame_commit *commit = blame->commit;
4111         regmatch_t pmatch;
4113 #define MATCH(text, on)                                                 \
4114         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4116         if (commit) {
4117                 char buf[DATE_COLS + 1];
4119                 if (MATCH(commit->title, 1) ||
4120                     MATCH(commit->author, opt_author) ||
4121                     MATCH(commit->id, opt_date))
4122                         return TRUE;
4124                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4125                     MATCH(buf, 1))
4126                         return TRUE;
4127         }
4129         return MATCH(blame->text, 1);
4131 #undef MATCH
4134 static void
4135 blame_select(struct view *view, struct line *line)
4137         struct blame *blame = line->data;
4138         struct blame_commit *commit = blame->commit;
4140         if (!commit)
4141                 return;
4143         if (!strcmp(commit->id, NULL_ID))
4144                 string_ncopy(ref_commit, "HEAD", 4);
4145         else
4146                 string_copy_rev(ref_commit, commit->id);
4149 static struct view_ops blame_ops = {
4150         "line",
4151         NULL,
4152         blame_open,
4153         blame_read,
4154         blame_draw,
4155         blame_request,
4156         blame_grep,
4157         blame_select,
4158 };
4160 /*
4161  * Status backend
4162  */
4164 struct status {
4165         char status;
4166         struct {
4167                 mode_t mode;
4168                 char rev[SIZEOF_REV];
4169                 char name[SIZEOF_STR];
4170         } old;
4171         struct {
4172                 mode_t mode;
4173                 char rev[SIZEOF_REV];
4174                 char name[SIZEOF_STR];
4175         } new;
4176 };
4178 static char status_onbranch[SIZEOF_STR];
4179 static struct status stage_status;
4180 static enum line_type stage_line_type;
4181 static size_t stage_chunks;
4182 static int *stage_chunk;
4184 /* This should work even for the "On branch" line. */
4185 static inline bool
4186 status_has_none(struct view *view, struct line *line)
4188         return line < view->line + view->lines && !line[1].data;
4191 /* Get fields from the diff line:
4192  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4193  */
4194 static inline bool
4195 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4197         const char *old_mode = buf +  1;
4198         const char *new_mode = buf +  8;
4199         const char *old_rev  = buf + 15;
4200         const char *new_rev  = buf + 56;
4201         const char *status   = buf + 97;
4203         if (bufsize < 99 ||
4204             old_mode[-1] != ':' ||
4205             new_mode[-1] != ' ' ||
4206             old_rev[-1]  != ' ' ||
4207             new_rev[-1]  != ' ' ||
4208             status[-1]   != ' ')
4209                 return FALSE;
4211         file->status = *status;
4213         string_copy_rev(file->old.rev, old_rev);
4214         string_copy_rev(file->new.rev, new_rev);
4216         file->old.mode = strtoul(old_mode, NULL, 8);
4217         file->new.mode = strtoul(new_mode, NULL, 8);
4219         file->old.name[0] = file->new.name[0] = 0;
4221         return TRUE;
4224 static bool
4225 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4227         struct status *file = NULL;
4228         struct status *unmerged = NULL;
4229         char buf[SIZEOF_STR * 4];
4230         size_t bufsize = 0;
4231         struct io io = {};
4233         if (!run_io(&io, argv, NULL, IO_RD))
4234                 return FALSE;
4236         add_line_data(view, NULL, type);
4238         while (!io_eof(&io)) {
4239                 char *sep;
4240                 ssize_t readsize;
4242                 readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
4243                 if (io_error(&io))
4244                         break;
4245                 bufsize += readsize;
4247                 /* Process while we have NUL chars. */
4248                 while ((sep = memchr(buf, 0, bufsize))) {
4249                         size_t sepsize = sep - buf + 1;
4251                         if (!file) {
4252                                 if (!realloc_lines(view, view->line_size + 1))
4253                                         goto error_out;
4255                                 file = calloc(1, sizeof(*file));
4256                                 if (!file)
4257                                         goto error_out;
4259                                 add_line_data(view, file, type);
4260                         }
4262                         /* Parse diff info part. */
4263                         if (status) {
4264                                 file->status = status;
4265                                 if (status == 'A')
4266                                         string_copy(file->old.rev, NULL_ID);
4268                         } else if (!file->status) {
4269                                 if (!status_get_diff(file, buf, sepsize))
4270                                         goto error_out;
4272                                 bufsize -= sepsize;
4273                                 memmove(buf, sep + 1, bufsize);
4275                                 sep = memchr(buf, 0, bufsize);
4276                                 if (!sep)
4277                                         break;
4278                                 sepsize = sep - buf + 1;
4280                                 /* Collapse all 'M'odified entries that
4281                                  * follow a associated 'U'nmerged entry.
4282                                  */
4283                                 if (file->status == 'U') {
4284                                         unmerged = file;
4286                                 } else if (unmerged) {
4287                                         int collapse = !strcmp(buf, unmerged->new.name);
4289                                         unmerged = NULL;
4290                                         if (collapse) {
4291                                                 free(file);
4292                                                 view->lines--;
4293                                                 continue;
4294                                         }
4295                                 }
4296                         }
4298                         /* Grab the old name for rename/copy. */
4299                         if (!*file->old.name &&
4300                             (file->status == 'R' || file->status == 'C')) {
4301                                 sepsize = sep - buf + 1;
4302                                 string_ncopy(file->old.name, buf, sepsize);
4303                                 bufsize -= sepsize;
4304                                 memmove(buf, sep + 1, bufsize);
4306                                 sep = memchr(buf, 0, bufsize);
4307                                 if (!sep)
4308                                         break;
4309                                 sepsize = sep - buf + 1;
4310                         }
4312                         /* git-ls-files just delivers a NUL separated
4313                          * list of file names similar to the second half
4314                          * of the git-diff-* output. */
4315                         string_ncopy(file->new.name, buf, sepsize);
4316                         if (!*file->old.name)
4317                                 string_copy(file->old.name, file->new.name);
4318                         bufsize -= sepsize;
4319                         memmove(buf, sep + 1, bufsize);
4320                         file = NULL;
4321                 }
4322         }
4324         if (io_error(&io)) {
4325 error_out:
4326                 done_io(&io);
4327                 return FALSE;
4328         }
4330         if (!view->line[view->lines - 1].data)
4331                 add_line_data(view, NULL, LINE_STAT_NONE);
4333         done_io(&io);
4334         return TRUE;
4337 /* Don't show unmerged entries in the staged section. */
4338 static const char *status_diff_index_argv[] = {
4339         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4340                              "--cached", "-M", "HEAD", NULL
4341 };
4343 static const char *status_diff_files_argv[] = {
4344         "git", "diff-files", "-z", NULL
4345 };
4347 static const char *status_list_other_argv[] = {
4348         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4349 };
4351 static const char *status_list_no_head_argv[] = {
4352         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4353 };
4355 static const char *update_index_argv[] = {
4356         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4357 };
4359 /* First parse staged info using git-diff-index(1), then parse unstaged
4360  * info using git-diff-files(1), and finally untracked files using
4361  * git-ls-files(1). */
4362 static bool
4363 status_open(struct view *view)
4365         unsigned long prev_lineno = view->lineno;
4367         reset_view(view);
4369         if (!realloc_lines(view, view->line_size + 7))
4370                 return FALSE;
4372         add_line_data(view, NULL, LINE_STAT_HEAD);
4373         if (is_initial_commit())
4374                 string_copy(status_onbranch, "Initial commit");
4375         else if (!*opt_head)
4376                 string_copy(status_onbranch, "Not currently on any branch");
4377         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4378                 return FALSE;
4380         run_io_bg(update_index_argv);
4382         if (is_initial_commit()) {
4383                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4384                         return FALSE;
4385         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4386                 return FALSE;
4387         }
4389         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4390             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4391                 return FALSE;
4393         /* If all went well restore the previous line number to stay in
4394          * the context or select a line with something that can be
4395          * updated. */
4396         if (prev_lineno >= view->lines)
4397                 prev_lineno = view->lines - 1;
4398         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4399                 prev_lineno++;
4400         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4401                 prev_lineno--;
4403         /* If the above fails, always skip the "On branch" line. */
4404         if (prev_lineno < view->lines)
4405                 view->lineno = prev_lineno;
4406         else
4407                 view->lineno = 1;
4409         if (view->lineno < view->offset)
4410                 view->offset = view->lineno;
4411         else if (view->offset + view->height <= view->lineno)
4412                 view->offset = view->lineno - view->height + 1;
4414         return TRUE;
4417 static bool
4418 status_draw(struct view *view, struct line *line, unsigned int lineno)
4420         struct status *status = line->data;
4421         enum line_type type;
4422         const char *text;
4424         if (!status) {
4425                 switch (line->type) {
4426                 case LINE_STAT_STAGED:
4427                         type = LINE_STAT_SECTION;
4428                         text = "Changes to be committed:";
4429                         break;
4431                 case LINE_STAT_UNSTAGED:
4432                         type = LINE_STAT_SECTION;
4433                         text = "Changed but not updated:";
4434                         break;
4436                 case LINE_STAT_UNTRACKED:
4437                         type = LINE_STAT_SECTION;
4438                         text = "Untracked files:";
4439                         break;
4441                 case LINE_STAT_NONE:
4442                         type = LINE_DEFAULT;
4443                         text = "    (no files)";
4444                         break;
4446                 case LINE_STAT_HEAD:
4447                         type = LINE_STAT_HEAD;
4448                         text = status_onbranch;
4449                         break;
4451                 default:
4452                         return FALSE;
4453                 }
4454         } else {
4455                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4457                 buf[0] = status->status;
4458                 if (draw_text(view, line->type, buf, TRUE))
4459                         return TRUE;
4460                 type = LINE_DEFAULT;
4461                 text = status->new.name;
4462         }
4464         draw_text(view, type, text, TRUE);
4465         return TRUE;
4468 static enum request
4469 status_enter(struct view *view, struct line *line)
4471         struct status *status = line->data;
4472         const char *oldpath = status ? status->old.name : NULL;
4473         /* Diffs for unmerged entries are empty when passing the new
4474          * path, so leave it empty. */
4475         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4476         const char *info;
4477         enum open_flags split;
4478         struct view *stage = VIEW(REQ_VIEW_STAGE);
4480         if (line->type == LINE_STAT_NONE ||
4481             (!status && line[1].type == LINE_STAT_NONE)) {
4482                 report("No file to diff");
4483                 return REQ_NONE;
4484         }
4486         switch (line->type) {
4487         case LINE_STAT_STAGED:
4488                 if (is_initial_commit()) {
4489                         const char *no_head_diff_argv[] = {
4490                                 "git", "diff", "--no-color", "--patch-with-stat",
4491                                         "--", "/dev/null", newpath, NULL
4492                         };
4494                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4495                                 return REQ_QUIT;
4496                 } else {
4497                         const char *index_show_argv[] = {
4498                                 "git", "diff-index", "--root", "--patch-with-stat",
4499                                         "-C", "-M", "--cached", "HEAD", "--",
4500                                         oldpath, newpath, NULL
4501                         };
4503                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4504                                 return REQ_QUIT;
4505                 }
4507                 if (status)
4508                         info = "Staged changes to %s";
4509                 else
4510                         info = "Staged changes";
4511                 break;
4513         case LINE_STAT_UNSTAGED:
4514         {
4515                 const char *files_show_argv[] = {
4516                         "git", "diff-files", "--root", "--patch-with-stat",
4517                                 "-C", "-M", "--", oldpath, newpath, NULL
4518                 };
4520                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4521                         return REQ_QUIT;
4522                 if (status)
4523                         info = "Unstaged changes to %s";
4524                 else
4525                         info = "Unstaged changes";
4526                 break;
4527         }
4528         case LINE_STAT_UNTRACKED:
4529                 if (!newpath) {
4530                         report("No file to show");
4531                         return REQ_NONE;
4532                 }
4534                 if (!suffixcmp(status->new.name, -1, "/")) {
4535                         report("Cannot display a directory");
4536                         return REQ_NONE;
4537                 }
4539                 if (!prepare_update_file(stage, newpath))
4540                         return REQ_QUIT;
4541                 info = "Untracked file %s";
4542                 break;
4544         case LINE_STAT_HEAD:
4545                 return REQ_NONE;
4547         default:
4548                 die("line type %d not handled in switch", line->type);
4549         }
4551         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4552         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4553         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4554                 if (status) {
4555                         stage_status = *status;
4556                 } else {
4557                         memset(&stage_status, 0, sizeof(stage_status));
4558                 }
4560                 stage_line_type = line->type;
4561                 stage_chunks = 0;
4562                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4563         }
4565         return REQ_NONE;
4568 static bool
4569 status_exists(struct status *status, enum line_type type)
4571         struct view *view = VIEW(REQ_VIEW_STATUS);
4572         struct line *line;
4574         for (line = view->line; line < view->line + view->lines; line++) {
4575                 struct status *pos = line->data;
4577                 if (line->type == type && pos &&
4578                     !strcmp(status->new.name, pos->new.name))
4579                         return TRUE;
4580         }
4582         return FALSE;
4586 static bool
4587 status_update_prepare(struct io *io, enum line_type type)
4589         const char *staged_argv[] = {
4590                 "git", "update-index", "-z", "--index-info", NULL
4591         };
4592         const char *others_argv[] = {
4593                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4594         };
4596         switch (type) {
4597         case LINE_STAT_STAGED:
4598                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4600         case LINE_STAT_UNSTAGED:
4601                 return run_io(io, others_argv, opt_cdup, IO_WR);
4603         case LINE_STAT_UNTRACKED:
4604                 return run_io(io, others_argv, NULL, IO_WR);
4606         default:
4607                 die("line type %d not handled in switch", type);
4608                 return FALSE;
4609         }
4612 static bool
4613 status_update_write(struct io *io, struct status *status, enum line_type type)
4615         char buf[SIZEOF_STR];
4616         size_t bufsize = 0;
4618         switch (type) {
4619         case LINE_STAT_STAGED:
4620                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4621                                         status->old.mode,
4622                                         status->old.rev,
4623                                         status->old.name, 0))
4624                         return FALSE;
4625                 break;
4627         case LINE_STAT_UNSTAGED:
4628         case LINE_STAT_UNTRACKED:
4629                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4630                         return FALSE;
4631                 break;
4633         default:
4634                 die("line type %d not handled in switch", type);
4635         }
4637         return io_write(io, buf, bufsize);
4640 static bool
4641 status_update_file(struct status *status, enum line_type type)
4643         struct io io = {};
4644         bool result;
4646         if (!status_update_prepare(&io, type))
4647                 return FALSE;
4649         result = status_update_write(&io, status, type);
4650         done_io(&io);
4651         return result;
4654 static bool
4655 status_update_files(struct view *view, struct line *line)
4657         struct io io = {};
4658         bool result = TRUE;
4659         struct line *pos = view->line + view->lines;
4660         int files = 0;
4661         int file, done;
4663         if (!status_update_prepare(&io, line->type))
4664                 return FALSE;
4666         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4667                 files++;
4669         for (file = 0, done = 0; result && file < files; line++, file++) {
4670                 int almost_done = file * 100 / files;
4672                 if (almost_done > done) {
4673                         done = almost_done;
4674                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4675                                       file, files, done);
4676                         update_view_title(view);
4677                 }
4678                 result = status_update_write(&io, line->data, line->type);
4679         }
4681         done_io(&io);
4682         return result;
4685 static bool
4686 status_update(struct view *view)
4688         struct line *line = &view->line[view->lineno];
4690         assert(view->lines);
4692         if (!line->data) {
4693                 /* This should work even for the "On branch" line. */
4694                 if (line < view->line + view->lines && !line[1].data) {
4695                         report("Nothing to update");
4696                         return FALSE;
4697                 }
4699                 if (!status_update_files(view, line + 1)) {
4700                         report("Failed to update file status");
4701                         return FALSE;
4702                 }
4704         } else if (!status_update_file(line->data, line->type)) {
4705                 report("Failed to update file status");
4706                 return FALSE;
4707         }
4709         return TRUE;
4712 static bool
4713 status_revert(struct status *status, enum line_type type, bool has_none)
4715         if (!status || type != LINE_STAT_UNSTAGED) {
4716                 if (type == LINE_STAT_STAGED) {
4717                         report("Cannot revert changes to staged files");
4718                 } else if (type == LINE_STAT_UNTRACKED) {
4719                         report("Cannot revert changes to untracked files");
4720                 } else if (has_none) {
4721                         report("Nothing to revert");
4722                 } else {
4723                         report("Cannot revert changes to multiple files");
4724                 }
4725                 return FALSE;
4727         } else {
4728                 const char *checkout_argv[] = {
4729                         "git", "checkout", "--", status->old.name, NULL
4730                 };
4732                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4733                         return FALSE;
4734                 return run_io_fg(checkout_argv, opt_cdup);
4735         }
4738 static enum request
4739 status_request(struct view *view, enum request request, struct line *line)
4741         struct status *status = line->data;
4743         switch (request) {
4744         case REQ_STATUS_UPDATE:
4745                 if (!status_update(view))
4746                         return REQ_NONE;
4747                 break;
4749         case REQ_STATUS_REVERT:
4750                 if (!status_revert(status, line->type, status_has_none(view, line)))
4751                         return REQ_NONE;
4752                 break;
4754         case REQ_STATUS_MERGE:
4755                 if (!status || status->status != 'U') {
4756                         report("Merging only possible for files with unmerged status ('U').");
4757                         return REQ_NONE;
4758                 }
4759                 open_mergetool(status->new.name);
4760                 break;
4762         case REQ_EDIT:
4763                 if (!status)
4764                         return request;
4765                 if (status->status == 'D') {
4766                         report("File has been deleted.");
4767                         return REQ_NONE;
4768                 }
4770                 open_editor(status->status != '?', status->new.name);
4771                 break;
4773         case REQ_VIEW_BLAME:
4774                 if (status) {
4775                         string_copy(opt_file, status->new.name);
4776                         opt_ref[0] = 0;
4777                 }
4778                 return request;
4780         case REQ_ENTER:
4781                 /* After returning the status view has been split to
4782                  * show the stage view. No further reloading is
4783                  * necessary. */
4784                 status_enter(view, line);
4785                 return REQ_NONE;
4787         case REQ_REFRESH:
4788                 /* Simply reload the view. */
4789                 break;
4791         default:
4792                 return request;
4793         }
4795         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4797         return REQ_NONE;
4800 static void
4801 status_select(struct view *view, struct line *line)
4803         struct status *status = line->data;
4804         char file[SIZEOF_STR] = "all files";
4805         const char *text;
4806         const char *key;
4808         if (status && !string_format(file, "'%s'", status->new.name))
4809                 return;
4811         if (!status && line[1].type == LINE_STAT_NONE)
4812                 line++;
4814         switch (line->type) {
4815         case LINE_STAT_STAGED:
4816                 text = "Press %s to unstage %s for commit";
4817                 break;
4819         case LINE_STAT_UNSTAGED:
4820                 text = "Press %s to stage %s for commit";
4821                 break;
4823         case LINE_STAT_UNTRACKED:
4824                 text = "Press %s to stage %s for addition";
4825                 break;
4827         case LINE_STAT_HEAD:
4828         case LINE_STAT_NONE:
4829                 text = "Nothing to update";
4830                 break;
4832         default:
4833                 die("line type %d not handled in switch", line->type);
4834         }
4836         if (status && status->status == 'U') {
4837                 text = "Press %s to resolve conflict in %s";
4838                 key = get_key(REQ_STATUS_MERGE);
4840         } else {
4841                 key = get_key(REQ_STATUS_UPDATE);
4842         }
4844         string_format(view->ref, text, key, file);
4847 static bool
4848 status_grep(struct view *view, struct line *line)
4850         struct status *status = line->data;
4851         enum { S_STATUS, S_NAME, S_END } state;
4852         char buf[2] = "?";
4853         regmatch_t pmatch;
4855         if (!status)
4856                 return FALSE;
4858         for (state = S_STATUS; state < S_END; state++) {
4859                 const char *text;
4861                 switch (state) {
4862                 case S_NAME:    text = status->new.name;        break;
4863                 case S_STATUS:
4864                         buf[0] = status->status;
4865                         text = buf;
4866                         break;
4868                 default:
4869                         return FALSE;
4870                 }
4872                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4873                         return TRUE;
4874         }
4876         return FALSE;
4879 static struct view_ops status_ops = {
4880         "file",
4881         NULL,
4882         status_open,
4883         NULL,
4884         status_draw,
4885         status_request,
4886         status_grep,
4887         status_select,
4888 };
4891 static bool
4892 stage_diff_write(struct io *io, struct line *line, struct line *end)
4894         while (line < end) {
4895                 if (!io_write(io, line->data, strlen(line->data)) ||
4896                     !io_write(io, "\n", 1))
4897                         return FALSE;
4898                 line++;
4899                 if (line->type == LINE_DIFF_CHUNK ||
4900                     line->type == LINE_DIFF_HEADER)
4901                         break;
4902         }
4904         return TRUE;
4907 static struct line *
4908 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4910         for (; view->line < line; line--)
4911                 if (line->type == type)
4912                         return line;
4914         return NULL;
4917 static bool
4918 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4920         const char *apply_argv[SIZEOF_ARG] = {
4921                 "git", "apply", "--whitespace=nowarn", NULL
4922         };
4923         struct line *diff_hdr;
4924         struct io io = {};
4925         int argc = 3;
4927         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4928         if (!diff_hdr)
4929                 return FALSE;
4931         if (!revert)
4932                 apply_argv[argc++] = "--cached";
4933         if (revert || stage_line_type == LINE_STAT_STAGED)
4934                 apply_argv[argc++] = "-R";
4935         apply_argv[argc++] = "-";
4936         apply_argv[argc++] = NULL;
4937         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4938                 return FALSE;
4940         if (!stage_diff_write(&io, diff_hdr, chunk) ||
4941             !stage_diff_write(&io, chunk, view->line + view->lines))
4942                 chunk = NULL;
4944         done_io(&io);
4945         run_io_bg(update_index_argv);
4947         return chunk ? TRUE : FALSE;
4950 static bool
4951 stage_update(struct view *view, struct line *line)
4953         struct line *chunk = NULL;
4955         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4956                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4958         if (chunk) {
4959                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4960                         report("Failed to apply chunk");
4961                         return FALSE;
4962                 }
4964         } else if (!stage_status.status) {
4965                 view = VIEW(REQ_VIEW_STATUS);
4967                 for (line = view->line; line < view->line + view->lines; line++)
4968                         if (line->type == stage_line_type)
4969                                 break;
4971                 if (!status_update_files(view, line + 1)) {
4972                         report("Failed to update files");
4973                         return FALSE;
4974                 }
4976         } else if (!status_update_file(&stage_status, stage_line_type)) {
4977                 report("Failed to update file");
4978                 return FALSE;
4979         }
4981         return TRUE;
4984 static bool
4985 stage_revert(struct view *view, struct line *line)
4987         struct line *chunk = NULL;
4989         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4990                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4992         if (chunk) {
4993                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4994                         return FALSE;
4996                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4997                         report("Failed to revert chunk");
4998                         return FALSE;
4999                 }
5000                 return TRUE;
5002         } else {
5003                 return status_revert(stage_status.status ? &stage_status : NULL,
5004                                      stage_line_type, FALSE);
5005         }
5009 static void
5010 stage_next(struct view *view, struct line *line)
5012         int i;
5014         if (!stage_chunks) {
5015                 static size_t alloc = 0;
5016                 int *tmp;
5018                 for (line = view->line; line < view->line + view->lines; line++) {
5019                         if (line->type != LINE_DIFF_CHUNK)
5020                                 continue;
5022                         tmp = realloc_items(stage_chunk, &alloc,
5023                                             stage_chunks, sizeof(*tmp));
5024                         if (!tmp) {
5025                                 report("Allocation failure");
5026                                 return;
5027                         }
5029                         stage_chunk = tmp;
5030                         stage_chunk[stage_chunks++] = line - view->line;
5031                 }
5032         }
5034         for (i = 0; i < stage_chunks; i++) {
5035                 if (stage_chunk[i] > view->lineno) {
5036                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5037                         report("Chunk %d of %d", i + 1, stage_chunks);
5038                         return;
5039                 }
5040         }
5042         report("No next chunk found");
5045 static enum request
5046 stage_request(struct view *view, enum request request, struct line *line)
5048         switch (request) {
5049         case REQ_STATUS_UPDATE:
5050                 if (!stage_update(view, line))
5051                         return REQ_NONE;
5052                 break;
5054         case REQ_STATUS_REVERT:
5055                 if (!stage_revert(view, line))
5056                         return REQ_NONE;
5057                 break;
5059         case REQ_STAGE_NEXT:
5060                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5061                         report("File is untracked; press %s to add",
5062                                get_key(REQ_STATUS_UPDATE));
5063                         return REQ_NONE;
5064                 }
5065                 stage_next(view, line);
5066                 return REQ_NONE;
5068         case REQ_EDIT:
5069                 if (!stage_status.new.name[0])
5070                         return request;
5071                 if (stage_status.status == 'D') {
5072                         report("File has been deleted.");
5073                         return REQ_NONE;
5074                 }
5076                 open_editor(stage_status.status != '?', stage_status.new.name);
5077                 break;
5079         case REQ_REFRESH:
5080                 /* Reload everything ... */
5081                 break;
5083         case REQ_VIEW_BLAME:
5084                 if (stage_status.new.name[0]) {
5085                         string_copy(opt_file, stage_status.new.name);
5086                         opt_ref[0] = 0;
5087                 }
5088                 return request;
5090         case REQ_ENTER:
5091                 return pager_request(view, request, line);
5093         default:
5094                 return request;
5095         }
5097         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5099         /* Check whether the staged entry still exists, and close the
5100          * stage view if it doesn't. */
5101         if (!status_exists(&stage_status, stage_line_type))
5102                 return REQ_VIEW_CLOSE;
5104         if (stage_line_type == LINE_STAT_UNTRACKED) {
5105                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5106                         report("Cannot display a directory");
5107                         return REQ_NONE;
5108                 }
5110                 if (!prepare_update_file(view, stage_status.new.name)) {
5111                         report("Failed to open file: %s", strerror(errno));
5112                         return REQ_NONE;
5113                 }
5114         }
5115         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5117         return REQ_NONE;
5120 static struct view_ops stage_ops = {
5121         "line",
5122         NULL,
5123         NULL,
5124         pager_read,
5125         pager_draw,
5126         stage_request,
5127         pager_grep,
5128         pager_select,
5129 };
5132 /*
5133  * Revision graph
5134  */
5136 struct commit {
5137         char id[SIZEOF_REV];            /* SHA1 ID. */
5138         char title[128];                /* First line of the commit message. */
5139         char author[75];                /* Author of the commit. */
5140         struct tm time;                 /* Date from the author ident. */
5141         struct ref **refs;              /* Repository references. */
5142         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5143         size_t graph_size;              /* The width of the graph array. */
5144         bool has_parents;               /* Rewritten --parents seen. */
5145 };
5147 /* Size of rev graph with no  "padding" columns */
5148 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5150 struct rev_graph {
5151         struct rev_graph *prev, *next, *parents;
5152         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5153         size_t size;
5154         struct commit *commit;
5155         size_t pos;
5156         unsigned int boundary:1;
5157 };
5159 /* Parents of the commit being visualized. */
5160 static struct rev_graph graph_parents[4];
5162 /* The current stack of revisions on the graph. */
5163 static struct rev_graph graph_stacks[4] = {
5164         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5165         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5166         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5167         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5168 };
5170 static inline bool
5171 graph_parent_is_merge(struct rev_graph *graph)
5173         return graph->parents->size > 1;
5176 static inline void
5177 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5179         struct commit *commit = graph->commit;
5181         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5182                 commit->graph[commit->graph_size++] = symbol;
5185 static void
5186 clear_rev_graph(struct rev_graph *graph)
5188         graph->boundary = 0;
5189         graph->size = graph->pos = 0;
5190         graph->commit = NULL;
5191         memset(graph->parents, 0, sizeof(*graph->parents));
5194 static void
5195 done_rev_graph(struct rev_graph *graph)
5197         if (graph_parent_is_merge(graph) &&
5198             graph->pos < graph->size - 1 &&
5199             graph->next->size == graph->size + graph->parents->size - 1) {
5200                 size_t i = graph->pos + graph->parents->size - 1;
5202                 graph->commit->graph_size = i * 2;
5203                 while (i < graph->next->size - 1) {
5204                         append_to_rev_graph(graph, ' ');
5205                         append_to_rev_graph(graph, '\\');
5206                         i++;
5207                 }
5208         }
5210         clear_rev_graph(graph);
5213 static void
5214 push_rev_graph(struct rev_graph *graph, const char *parent)
5216         int i;
5218         /* "Collapse" duplicate parents lines.
5219          *
5220          * FIXME: This needs to also update update the drawn graph but
5221          * for now it just serves as a method for pruning graph lines. */
5222         for (i = 0; i < graph->size; i++)
5223                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5224                         return;
5226         if (graph->size < SIZEOF_REVITEMS) {
5227                 string_copy_rev(graph->rev[graph->size++], parent);
5228         }
5231 static chtype
5232 get_rev_graph_symbol(struct rev_graph *graph)
5234         chtype symbol;
5236         if (graph->boundary)
5237                 symbol = REVGRAPH_BOUND;
5238         else if (graph->parents->size == 0)
5239                 symbol = REVGRAPH_INIT;
5240         else if (graph_parent_is_merge(graph))
5241                 symbol = REVGRAPH_MERGE;
5242         else if (graph->pos >= graph->size)
5243                 symbol = REVGRAPH_BRANCH;
5244         else
5245                 symbol = REVGRAPH_COMMIT;
5247         return symbol;
5250 static void
5251 draw_rev_graph(struct rev_graph *graph)
5253         struct rev_filler {
5254                 chtype separator, line;
5255         };
5256         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5257         static struct rev_filler fillers[] = {
5258                 { ' ',  '|' },
5259                 { '`',  '.' },
5260                 { '\'', ' ' },
5261                 { '/',  ' ' },
5262         };
5263         chtype symbol = get_rev_graph_symbol(graph);
5264         struct rev_filler *filler;
5265         size_t i;
5267         if (opt_line_graphics)
5268                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5270         filler = &fillers[DEFAULT];
5272         for (i = 0; i < graph->pos; i++) {
5273                 append_to_rev_graph(graph, filler->line);
5274                 if (graph_parent_is_merge(graph->prev) &&
5275                     graph->prev->pos == i)
5276                         filler = &fillers[RSHARP];
5278                 append_to_rev_graph(graph, filler->separator);
5279         }
5281         /* Place the symbol for this revision. */
5282         append_to_rev_graph(graph, symbol);
5284         if (graph->prev->size > graph->size)
5285                 filler = &fillers[RDIAG];
5286         else
5287                 filler = &fillers[DEFAULT];
5289         i++;
5291         for (; i < graph->size; i++) {
5292                 append_to_rev_graph(graph, filler->separator);
5293                 append_to_rev_graph(graph, filler->line);
5294                 if (graph_parent_is_merge(graph->prev) &&
5295                     i < graph->prev->pos + graph->parents->size)
5296                         filler = &fillers[RSHARP];
5297                 if (graph->prev->size > graph->size)
5298                         filler = &fillers[LDIAG];
5299         }
5301         if (graph->prev->size > graph->size) {
5302                 append_to_rev_graph(graph, filler->separator);
5303                 if (filler->line != ' ')
5304                         append_to_rev_graph(graph, filler->line);
5305         }
5308 /* Prepare the next rev graph */
5309 static void
5310 prepare_rev_graph(struct rev_graph *graph)
5312         size_t i;
5314         /* First, traverse all lines of revisions up to the active one. */
5315         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5316                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5317                         break;
5319                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5320         }
5322         /* Interleave the new revision parent(s). */
5323         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5324                 push_rev_graph(graph->next, graph->parents->rev[i]);
5326         /* Lastly, put any remaining revisions. */
5327         for (i = graph->pos + 1; i < graph->size; i++)
5328                 push_rev_graph(graph->next, graph->rev[i]);
5331 static void
5332 update_rev_graph(struct rev_graph *graph)
5334         /* If this is the finalizing update ... */
5335         if (graph->commit)
5336                 prepare_rev_graph(graph);
5338         /* Graph visualization needs a one rev look-ahead,
5339          * so the first update doesn't visualize anything. */
5340         if (!graph->prev->commit)
5341                 return;
5343         draw_rev_graph(graph->prev);
5344         done_rev_graph(graph->prev->prev);
5348 /*
5349  * Main view backend
5350  */
5352 static const char *main_argv[SIZEOF_ARG] = {
5353         "git", "log", "--no-color", "--pretty=raw", "--parents",
5354                       "--topo-order", "%(head)", NULL
5355 };
5357 static bool
5358 main_draw(struct view *view, struct line *line, unsigned int lineno)
5360         struct commit *commit = line->data;
5362         if (!*commit->author)
5363                 return FALSE;
5365         if (opt_date && draw_date(view, &commit->time))
5366                 return TRUE;
5368         if (opt_author &&
5369             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5370                 return TRUE;
5372         if (opt_rev_graph && commit->graph_size &&
5373             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5374                 return TRUE;
5376         if (opt_show_refs && commit->refs) {
5377                 size_t i = 0;
5379                 do {
5380                         enum line_type type;
5382                         if (commit->refs[i]->head)
5383                                 type = LINE_MAIN_HEAD;
5384                         else if (commit->refs[i]->ltag)
5385                                 type = LINE_MAIN_LOCAL_TAG;
5386                         else if (commit->refs[i]->tag)
5387                                 type = LINE_MAIN_TAG;
5388                         else if (commit->refs[i]->tracked)
5389                                 type = LINE_MAIN_TRACKED;
5390                         else if (commit->refs[i]->remote)
5391                                 type = LINE_MAIN_REMOTE;
5392                         else
5393                                 type = LINE_MAIN_REF;
5395                         if (draw_text(view, type, "[", TRUE) ||
5396                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5397                             draw_text(view, type, "]", TRUE))
5398                                 return TRUE;
5400                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5401                                 return TRUE;
5402                 } while (commit->refs[i++]->next);
5403         }
5405         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5406         return TRUE;
5409 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5410 static bool
5411 main_read(struct view *view, char *line)
5413         static struct rev_graph *graph = graph_stacks;
5414         enum line_type type;
5415         struct commit *commit;
5417         if (!line) {
5418                 int i;
5420                 if (!view->lines && !view->parent)
5421                         die("No revisions match the given arguments.");
5422                 if (view->lines > 0) {
5423                         commit = view->line[view->lines - 1].data;
5424                         if (!*commit->author) {
5425                                 view->lines--;
5426                                 free(commit);
5427                                 graph->commit = NULL;
5428                         }
5429                 }
5430                 update_rev_graph(graph);
5432                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5433                         clear_rev_graph(&graph_stacks[i]);
5434                 return TRUE;
5435         }
5437         type = get_line_type(line);
5438         if (type == LINE_COMMIT) {
5439                 commit = calloc(1, sizeof(struct commit));
5440                 if (!commit)
5441                         return FALSE;
5443                 line += STRING_SIZE("commit ");
5444                 if (*line == '-') {
5445                         graph->boundary = 1;
5446                         line++;
5447                 }
5449                 string_copy_rev(commit->id, line);
5450                 commit->refs = get_refs(commit->id);
5451                 graph->commit = commit;
5452                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5454                 while ((line = strchr(line, ' '))) {
5455                         line++;
5456                         push_rev_graph(graph->parents, line);
5457                         commit->has_parents = TRUE;
5458                 }
5459                 return TRUE;
5460         }
5462         if (!view->lines)
5463                 return TRUE;
5464         commit = view->line[view->lines - 1].data;
5466         switch (type) {
5467         case LINE_PARENT:
5468                 if (commit->has_parents)
5469                         break;
5470                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5471                 break;
5473         case LINE_AUTHOR:
5474         {
5475                 /* Parse author lines where the name may be empty:
5476                  *      author  <email@address.tld> 1138474660 +0100
5477                  */
5478                 char *ident = line + STRING_SIZE("author ");
5479                 char *nameend = strchr(ident, '<');
5480                 char *emailend = strchr(ident, '>');
5482                 if (!nameend || !emailend)
5483                         break;
5485                 update_rev_graph(graph);
5486                 graph = graph->next;
5488                 *nameend = *emailend = 0;
5489                 ident = chomp_string(ident);
5490                 if (!*ident) {
5491                         ident = chomp_string(nameend + 1);
5492                         if (!*ident)
5493                                 ident = "Unknown";
5494                 }
5496                 string_ncopy(commit->author, ident, strlen(ident));
5498                 /* Parse epoch and timezone */
5499                 if (emailend[1] == ' ') {
5500                         char *secs = emailend + 2;
5501                         char *zone = strchr(secs, ' ');
5502                         time_t time = (time_t) atol(secs);
5504                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5505                                 long tz;
5507                                 zone++;
5508                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5509                                 tz += ('0' - zone[2]) * 60 * 60;
5510                                 tz += ('0' - zone[3]) * 60;
5511                                 tz += ('0' - zone[4]) * 60;
5513                                 if (zone[0] == '-')
5514                                         tz = -tz;
5516                                 time -= tz;
5517                         }
5519                         gmtime_r(&time, &commit->time);
5520                 }
5521                 break;
5522         }
5523         default:
5524                 /* Fill in the commit title if it has not already been set. */
5525                 if (commit->title[0])
5526                         break;
5528                 /* Require titles to start with a non-space character at the
5529                  * offset used by git log. */
5530                 if (strncmp(line, "    ", 4))
5531                         break;
5532                 line += 4;
5533                 /* Well, if the title starts with a whitespace character,
5534                  * try to be forgiving.  Otherwise we end up with no title. */
5535                 while (isspace(*line))
5536                         line++;
5537                 if (*line == '\0')
5538                         break;
5539                 /* FIXME: More graceful handling of titles; append "..." to
5540                  * shortened titles, etc. */
5542                 string_ncopy(commit->title, line, strlen(line));
5543         }
5545         return TRUE;
5548 static enum request
5549 main_request(struct view *view, enum request request, struct line *line)
5551         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5553         switch (request) {
5554         case REQ_ENTER:
5555                 open_view(view, REQ_VIEW_DIFF, flags);
5556                 break;
5557         case REQ_REFRESH:
5558                 load_refs();
5559                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5560                 break;
5561         default:
5562                 return request;
5563         }
5565         return REQ_NONE;
5568 static bool
5569 grep_refs(struct ref **refs, regex_t *regex)
5571         regmatch_t pmatch;
5572         size_t i = 0;
5574         if (!refs)
5575                 return FALSE;
5576         do {
5577                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5578                         return TRUE;
5579         } while (refs[i++]->next);
5581         return FALSE;
5584 static bool
5585 main_grep(struct view *view, struct line *line)
5587         struct commit *commit = line->data;
5588         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5589         char buf[DATE_COLS + 1];
5590         regmatch_t pmatch;
5592         for (state = S_TITLE; state < S_END; state++) {
5593                 char *text;
5595                 switch (state) {
5596                 case S_TITLE:   text = commit->title;   break;
5597                 case S_AUTHOR:
5598                         if (!opt_author)
5599                                 continue;
5600                         text = commit->author;
5601                         break;
5602                 case S_DATE:
5603                         if (!opt_date)
5604                                 continue;
5605                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5606                                 continue;
5607                         text = buf;
5608                         break;
5609                 case S_REFS:
5610                         if (!opt_show_refs)
5611                                 continue;
5612                         if (grep_refs(commit->refs, view->regex) == TRUE)
5613                                 return TRUE;
5614                         continue;
5615                 default:
5616                         return FALSE;
5617                 }
5619                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5620                         return TRUE;
5621         }
5623         return FALSE;
5626 static void
5627 main_select(struct view *view, struct line *line)
5629         struct commit *commit = line->data;
5631         string_copy_rev(view->ref, commit->id);
5632         string_copy_rev(ref_commit, view->ref);
5635 static struct view_ops main_ops = {
5636         "commit",
5637         main_argv,
5638         NULL,
5639         main_read,
5640         main_draw,
5641         main_request,
5642         main_grep,
5643         main_select,
5644 };
5647 /*
5648  * Unicode / UTF-8 handling
5649  *
5650  * NOTE: Much of the following code for dealing with unicode is derived from
5651  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5652  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5653  */
5655 /* I've (over)annotated a lot of code snippets because I am not entirely
5656  * confident that the approach taken by this small UTF-8 interface is correct.
5657  * --jonas */
5659 static inline int
5660 unicode_width(unsigned long c)
5662         if (c >= 0x1100 &&
5663            (c <= 0x115f                         /* Hangul Jamo */
5664             || c == 0x2329
5665             || c == 0x232a
5666             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5667                                                 /* CJK ... Yi */
5668             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5669             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5670             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5671             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5672             || (c >= 0xffe0  && c <= 0xffe6)
5673             || (c >= 0x20000 && c <= 0x2fffd)
5674             || (c >= 0x30000 && c <= 0x3fffd)))
5675                 return 2;
5677         if (c == '\t')
5678                 return opt_tab_size;
5680         return 1;
5683 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5684  * Illegal bytes are set one. */
5685 static const unsigned char utf8_bytes[256] = {
5686         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,
5687         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,
5688         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,
5689         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,
5690         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,
5691         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,
5692         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,
5693         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,
5694 };
5696 /* Decode UTF-8 multi-byte representation into a unicode character. */
5697 static inline unsigned long
5698 utf8_to_unicode(const char *string, size_t length)
5700         unsigned long unicode;
5702         switch (length) {
5703         case 1:
5704                 unicode  =   string[0];
5705                 break;
5706         case 2:
5707                 unicode  =  (string[0] & 0x1f) << 6;
5708                 unicode +=  (string[1] & 0x3f);
5709                 break;
5710         case 3:
5711                 unicode  =  (string[0] & 0x0f) << 12;
5712                 unicode += ((string[1] & 0x3f) << 6);
5713                 unicode +=  (string[2] & 0x3f);
5714                 break;
5715         case 4:
5716                 unicode  =  (string[0] & 0x0f) << 18;
5717                 unicode += ((string[1] & 0x3f) << 12);
5718                 unicode += ((string[2] & 0x3f) << 6);
5719                 unicode +=  (string[3] & 0x3f);
5720                 break;
5721         case 5:
5722                 unicode  =  (string[0] & 0x0f) << 24;
5723                 unicode += ((string[1] & 0x3f) << 18);
5724                 unicode += ((string[2] & 0x3f) << 12);
5725                 unicode += ((string[3] & 0x3f) << 6);
5726                 unicode +=  (string[4] & 0x3f);
5727                 break;
5728         case 6:
5729                 unicode  =  (string[0] & 0x01) << 30;
5730                 unicode += ((string[1] & 0x3f) << 24);
5731                 unicode += ((string[2] & 0x3f) << 18);
5732                 unicode += ((string[3] & 0x3f) << 12);
5733                 unicode += ((string[4] & 0x3f) << 6);
5734                 unicode +=  (string[5] & 0x3f);
5735                 break;
5736         default:
5737                 die("Invalid unicode length");
5738         }
5740         /* Invalid characters could return the special 0xfffd value but NUL
5741          * should be just as good. */
5742         return unicode > 0xffff ? 0 : unicode;
5745 /* Calculates how much of string can be shown within the given maximum width
5746  * and sets trimmed parameter to non-zero value if all of string could not be
5747  * shown. If the reserve flag is TRUE, it will reserve at least one
5748  * trailing character, which can be useful when drawing a delimiter.
5749  *
5750  * Returns the number of bytes to output from string to satisfy max_width. */
5751 static size_t
5752 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5754         const char *start = string;
5755         const char *end = strchr(string, '\0');
5756         unsigned char last_bytes = 0;
5757         size_t last_ucwidth = 0;
5759         *width = 0;
5760         *trimmed = 0;
5762         while (string < end) {
5763                 int c = *(unsigned char *) string;
5764                 unsigned char bytes = utf8_bytes[c];
5765                 size_t ucwidth;
5766                 unsigned long unicode;
5768                 if (string + bytes > end)
5769                         break;
5771                 /* Change representation to figure out whether
5772                  * it is a single- or double-width character. */
5774                 unicode = utf8_to_unicode(string, bytes);
5775                 /* FIXME: Graceful handling of invalid unicode character. */
5776                 if (!unicode)
5777                         break;
5779                 ucwidth = unicode_width(unicode);
5780                 *width  += ucwidth;
5781                 if (*width > max_width) {
5782                         *trimmed = 1;
5783                         *width -= ucwidth;
5784                         if (reserve && *width == max_width) {
5785                                 string -= last_bytes;
5786                                 *width -= last_ucwidth;
5787                         }
5788                         break;
5789                 }
5791                 string  += bytes;
5792                 last_bytes = bytes;
5793                 last_ucwidth = ucwidth;
5794         }
5796         return string - start;
5800 /*
5801  * Status management
5802  */
5804 /* Whether or not the curses interface has been initialized. */
5805 static bool cursed = FALSE;
5807 /* The status window is used for polling keystrokes. */
5808 static WINDOW *status_win;
5810 static bool status_empty = TRUE;
5812 /* Update status and title window. */
5813 static void
5814 report(const char *msg, ...)
5816         struct view *view = display[current_view];
5818         if (input_mode)
5819                 return;
5821         if (!view) {
5822                 char buf[SIZEOF_STR];
5823                 va_list args;
5825                 va_start(args, msg);
5826                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5827                         buf[sizeof(buf) - 1] = 0;
5828                         buf[sizeof(buf) - 2] = '.';
5829                         buf[sizeof(buf) - 3] = '.';
5830                         buf[sizeof(buf) - 4] = '.';
5831                 }
5832                 va_end(args);
5833                 die("%s", buf);
5834         }
5836         if (!status_empty || *msg) {
5837                 va_list args;
5839                 va_start(args, msg);
5841                 wmove(status_win, 0, 0);
5842                 if (*msg) {
5843                         vwprintw(status_win, msg, args);
5844                         status_empty = FALSE;
5845                 } else {
5846                         status_empty = TRUE;
5847                 }
5848                 wclrtoeol(status_win);
5849                 wrefresh(status_win);
5851                 va_end(args);
5852         }
5854         update_view_title(view);
5855         update_display_cursor(view);
5858 /* Controls when nodelay should be in effect when polling user input. */
5859 static void
5860 set_nonblocking_input(bool loading)
5862         static unsigned int loading_views;
5864         if ((loading == FALSE && loading_views-- == 1) ||
5865             (loading == TRUE  && loading_views++ == 0))
5866                 nodelay(status_win, loading);
5869 static void
5870 init_display(void)
5872         int x, y;
5874         /* Initialize the curses library */
5875         if (isatty(STDIN_FILENO)) {
5876                 cursed = !!initscr();
5877                 opt_tty = stdin;
5878         } else {
5879                 /* Leave stdin and stdout alone when acting as a pager. */
5880                 opt_tty = fopen("/dev/tty", "r+");
5881                 if (!opt_tty)
5882                         die("Failed to open /dev/tty");
5883                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5884         }
5886         if (!cursed)
5887                 die("Failed to initialize curses");
5889         nonl();         /* Tell curses not to do NL->CR/NL on output */
5890         cbreak();       /* Take input chars one at a time, no wait for \n */
5891         noecho();       /* Don't echo input */
5892         leaveok(stdscr, TRUE);
5894         if (has_colors())
5895                 init_colors();
5897         getmaxyx(stdscr, y, x);
5898         status_win = newwin(1, 0, y - 1, 0);
5899         if (!status_win)
5900                 die("Failed to create status window");
5902         /* Enable keyboard mapping */
5903         keypad(status_win, TRUE);
5904         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5906         TABSIZE = opt_tab_size;
5907         if (opt_line_graphics) {
5908                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5909         }
5912 static bool
5913 prompt_yesno(const char *prompt)
5915         enum { WAIT, STOP, CANCEL  } status = WAIT;
5916         bool answer = FALSE;
5918         while (status == WAIT) {
5919                 struct view *view;
5920                 int i, key;
5922                 input_mode = TRUE;
5924                 foreach_view (view, i)
5925                         update_view(view);
5927                 input_mode = FALSE;
5929                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5930                 wclrtoeol(status_win);
5932                 /* Refresh, accept single keystroke of input */
5933                 key = wgetch(status_win);
5934                 switch (key) {
5935                 case ERR:
5936                         break;
5938                 case 'y':
5939                 case 'Y':
5940                         answer = TRUE;
5941                         status = STOP;
5942                         break;
5944                 case KEY_ESC:
5945                 case KEY_RETURN:
5946                 case KEY_ENTER:
5947                 case KEY_BACKSPACE:
5948                 case 'n':
5949                 case 'N':
5950                 case '\n':
5951                 default:
5952                         answer = FALSE;
5953                         status = CANCEL;
5954                 }
5955         }
5957         /* Clear the status window */
5958         status_empty = FALSE;
5959         report("");
5961         return answer;
5964 static char *
5965 read_prompt(const char *prompt)
5967         enum { READING, STOP, CANCEL } status = READING;
5968         static char buf[SIZEOF_STR];
5969         int pos = 0;
5971         while (status == READING) {
5972                 struct view *view;
5973                 int i, key;
5975                 input_mode = TRUE;
5977                 foreach_view (view, i)
5978                         update_view(view);
5980                 input_mode = FALSE;
5982                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5983                 wclrtoeol(status_win);
5985                 /* Refresh, accept single keystroke of input */
5986                 key = wgetch(status_win);
5987                 switch (key) {
5988                 case KEY_RETURN:
5989                 case KEY_ENTER:
5990                 case '\n':
5991                         status = pos ? STOP : CANCEL;
5992                         break;
5994                 case KEY_BACKSPACE:
5995                         if (pos > 0)
5996                                 pos--;
5997                         else
5998                                 status = CANCEL;
5999                         break;
6001                 case KEY_ESC:
6002                         status = CANCEL;
6003                         break;
6005                 case ERR:
6006                         break;
6008                 default:
6009                         if (pos >= sizeof(buf)) {
6010                                 report("Input string too long");
6011                                 return NULL;
6012                         }
6014                         if (isprint(key))
6015                                 buf[pos++] = (char) key;
6016                 }
6017         }
6019         /* Clear the status window */
6020         status_empty = FALSE;
6021         report("");
6023         if (status == CANCEL)
6024                 return NULL;
6026         buf[pos++] = 0;
6028         return buf;
6031 /*
6032  * Repository properties
6033  */
6035 static int
6036 git_properties(const char **argv, const char *separators,
6037                int (*read_property)(char *, size_t, char *, size_t))
6039         struct io io = {};
6041         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6042                 return read_properties(&io, separators, read_property);
6043         return ERR;
6046 static struct ref *refs = NULL;
6047 static size_t refs_alloc = 0;
6048 static size_t refs_size = 0;
6050 /* Id <-> ref store */
6051 static struct ref ***id_refs = NULL;
6052 static size_t id_refs_alloc = 0;
6053 static size_t id_refs_size = 0;
6055 static int
6056 compare_refs(const void *ref1_, const void *ref2_)
6058         const struct ref *ref1 = *(const struct ref **)ref1_;
6059         const struct ref *ref2 = *(const struct ref **)ref2_;
6061         if (ref1->tag != ref2->tag)
6062                 return ref2->tag - ref1->tag;
6063         if (ref1->ltag != ref2->ltag)
6064                 return ref2->ltag - ref2->ltag;
6065         if (ref1->head != ref2->head)
6066                 return ref2->head - ref1->head;
6067         if (ref1->tracked != ref2->tracked)
6068                 return ref2->tracked - ref1->tracked;
6069         if (ref1->remote != ref2->remote)
6070                 return ref2->remote - ref1->remote;
6071         return strcmp(ref1->name, ref2->name);
6074 static struct ref **
6075 get_refs(const char *id)
6077         struct ref ***tmp_id_refs;
6078         struct ref **ref_list = NULL;
6079         size_t ref_list_alloc = 0;
6080         size_t ref_list_size = 0;
6081         size_t i;
6083         for (i = 0; i < id_refs_size; i++)
6084                 if (!strcmp(id, id_refs[i][0]->id))
6085                         return id_refs[i];
6087         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6088                                     sizeof(*id_refs));
6089         if (!tmp_id_refs)
6090                 return NULL;
6092         id_refs = tmp_id_refs;
6094         for (i = 0; i < refs_size; i++) {
6095                 struct ref **tmp;
6097                 if (strcmp(id, refs[i].id))
6098                         continue;
6100                 tmp = realloc_items(ref_list, &ref_list_alloc,
6101                                     ref_list_size + 1, sizeof(*ref_list));
6102                 if (!tmp) {
6103                         if (ref_list)
6104                                 free(ref_list);
6105                         return NULL;
6106                 }
6108                 ref_list = tmp;
6109                 ref_list[ref_list_size] = &refs[i];
6110                 /* XXX: The properties of the commit chains ensures that we can
6111                  * safely modify the shared ref. The repo references will
6112                  * always be similar for the same id. */
6113                 ref_list[ref_list_size]->next = 1;
6115                 ref_list_size++;
6116         }
6118         if (ref_list) {
6119                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6120                 ref_list[ref_list_size - 1]->next = 0;
6121                 id_refs[id_refs_size++] = ref_list;
6122         }
6124         return ref_list;
6127 static int
6128 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6130         struct ref *ref;
6131         bool tag = FALSE;
6132         bool ltag = FALSE;
6133         bool remote = FALSE;
6134         bool tracked = FALSE;
6135         bool check_replace = FALSE;
6136         bool head = FALSE;
6138         if (!prefixcmp(name, "refs/tags/")) {
6139                 if (!suffixcmp(name, namelen, "^{}")) {
6140                         namelen -= 3;
6141                         name[namelen] = 0;
6142                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6143                                 check_replace = TRUE;
6144                 } else {
6145                         ltag = TRUE;
6146                 }
6148                 tag = TRUE;
6149                 namelen -= STRING_SIZE("refs/tags/");
6150                 name    += STRING_SIZE("refs/tags/");
6152         } else if (!prefixcmp(name, "refs/remotes/")) {
6153                 remote = TRUE;
6154                 namelen -= STRING_SIZE("refs/remotes/");
6155                 name    += STRING_SIZE("refs/remotes/");
6156                 tracked  = !strcmp(opt_remote, name);
6158         } else if (!prefixcmp(name, "refs/heads/")) {
6159                 namelen -= STRING_SIZE("refs/heads/");
6160                 name    += STRING_SIZE("refs/heads/");
6161                 head     = !strncmp(opt_head, name, namelen);
6163         } else if (!strcmp(name, "HEAD")) {
6164                 string_ncopy(opt_head_rev, id, idlen);
6165                 return OK;
6166         }
6168         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6169                 /* it's an annotated tag, replace the previous sha1 with the
6170                  * resolved commit id; relies on the fact git-ls-remote lists
6171                  * the commit id of an annotated tag right before the commit id
6172                  * it points to. */
6173                 refs[refs_size - 1].ltag = ltag;
6174                 string_copy_rev(refs[refs_size - 1].id, id);
6176                 return OK;
6177         }
6178         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6179         if (!refs)
6180                 return ERR;
6182         ref = &refs[refs_size++];
6183         ref->name = malloc(namelen + 1);
6184         if (!ref->name)
6185                 return ERR;
6187         strncpy(ref->name, name, namelen);
6188         ref->name[namelen] = 0;
6189         ref->head = head;
6190         ref->tag = tag;
6191         ref->ltag = ltag;
6192         ref->remote = remote;
6193         ref->tracked = tracked;
6194         string_copy_rev(ref->id, id);
6196         return OK;
6199 static int
6200 load_refs(void)
6202         static const char *ls_remote_argv[SIZEOF_ARG] = {
6203                 "git", "ls-remote", ".", NULL
6204         };
6205         static bool init = FALSE;
6207         if (!init) {
6208                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6209                 init = TRUE;
6210         }
6212         if (!*opt_git_dir)
6213                 return OK;
6215         while (refs_size > 0)
6216                 free(refs[--refs_size].name);
6217         while (id_refs_size > 0)
6218                 free(id_refs[--id_refs_size]);
6220         return git_properties(ls_remote_argv, "\t", read_ref);
6223 static int
6224 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6226         if (!strcmp(name, "i18n.commitencoding"))
6227                 string_ncopy(opt_encoding, value, valuelen);
6229         if (!strcmp(name, "core.editor"))
6230                 string_ncopy(opt_editor, value, valuelen);
6232         /* branch.<head>.remote */
6233         if (*opt_head &&
6234             !strncmp(name, "branch.", 7) &&
6235             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6236             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6237                 string_ncopy(opt_remote, value, valuelen);
6239         if (*opt_head && *opt_remote &&
6240             !strncmp(name, "branch.", 7) &&
6241             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6242             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6243                 size_t from = strlen(opt_remote);
6245                 if (!prefixcmp(value, "refs/heads/")) {
6246                         value += STRING_SIZE("refs/heads/");
6247                         valuelen -= STRING_SIZE("refs/heads/");
6248                 }
6250                 if (!string_format_from(opt_remote, &from, "/%s", value))
6251                         opt_remote[0] = 0;
6252         }
6254         return OK;
6257 static int
6258 load_git_config(void)
6260         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6262         return git_properties(config_list_argv, "=", read_repo_config_option);
6265 static int
6266 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6268         if (!opt_git_dir[0]) {
6269                 string_ncopy(opt_git_dir, name, namelen);
6271         } else if (opt_is_inside_work_tree == -1) {
6272                 /* This can be 3 different values depending on the
6273                  * version of git being used. If git-rev-parse does not
6274                  * understand --is-inside-work-tree it will simply echo
6275                  * the option else either "true" or "false" is printed.
6276                  * Default to true for the unknown case. */
6277                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6278         } else {
6279                 string_ncopy(opt_cdup, name, namelen);
6280         }
6282         return OK;
6285 static int
6286 load_repo_info(void)
6288         const char *head_argv[] = {
6289                 "git", "symbolic-ref", "HEAD", NULL
6290         };
6291         const char *rev_parse_argv[] = {
6292                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6293                         "--show-cdup", NULL
6294         };
6296         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6297                 chomp_string(opt_head);
6298                 if (!prefixcmp(opt_head, "refs/heads/")) {
6299                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6301                         memmove(opt_head, offset, strlen(offset) + 1);
6302                 }
6303         }
6305         return git_properties(rev_parse_argv, "=", read_repo_info);
6308 static int
6309 read_properties(struct io *io, const char *separators,
6310                 int (*read_property)(char *, size_t, char *, size_t))
6312         char *name;
6313         int state = OK;
6315         if (!start_io(io))
6316                 return ERR;
6318         while (state == OK && (name = io_gets(io))) {
6319                 char *value;
6320                 size_t namelen;
6321                 size_t valuelen;
6323                 name = chomp_string(name);
6324                 namelen = strcspn(name, separators);
6326                 if (name[namelen]) {
6327                         name[namelen] = 0;
6328                         value = chomp_string(name + namelen + 1);
6329                         valuelen = strlen(value);
6331                 } else {
6332                         value = "";
6333                         valuelen = 0;
6334                 }
6336                 state = read_property(name, namelen, value, valuelen);
6337         }
6339         if (state != ERR && io_error(io))
6340                 state = ERR;
6341         done_io(io);
6343         return state;
6347 /*
6348  * Main
6349  */
6351 static void __NORETURN
6352 quit(int sig)
6354         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6355         if (cursed)
6356                 endwin();
6357         exit(0);
6360 static void __NORETURN
6361 die(const char *err, ...)
6363         va_list args;
6365         endwin();
6367         va_start(args, err);
6368         fputs("tig: ", stderr);
6369         vfprintf(stderr, err, args);
6370         fputs("\n", stderr);
6371         va_end(args);
6373         exit(1);
6376 static void
6377 warn(const char *msg, ...)
6379         va_list args;
6381         va_start(args, msg);
6382         fputs("tig warning: ", stderr);
6383         vfprintf(stderr, msg, args);
6384         fputs("\n", stderr);
6385         va_end(args);
6388 int
6389 main(int argc, const char *argv[])
6391         const char **run_argv = NULL;
6392         struct view *view;
6393         enum request request;
6394         size_t i;
6396         signal(SIGINT, quit);
6398         if (setlocale(LC_ALL, "")) {
6399                 char *codeset = nl_langinfo(CODESET);
6401                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6402         }
6404         if (load_repo_info() == ERR)
6405                 die("Failed to load repo info.");
6407         if (load_options() == ERR)
6408                 die("Failed to load user config.");
6410         if (load_git_config() == ERR)
6411                 die("Failed to load repo config.");
6413         request = parse_options(argc, argv, &run_argv);
6414         if (request == REQ_NONE)
6415                 return 0;
6417         /* Require a git repository unless when running in pager mode. */
6418         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6419                 die("Not a git repository");
6421         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6422                 opt_utf8 = FALSE;
6424         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6425                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6426                 if (opt_iconv == ICONV_NONE)
6427                         die("Failed to initialize character set conversion");
6428         }
6430         if (load_refs() == ERR)
6431                 die("Failed to load refs.");
6433         foreach_view (view, i)
6434                 argv_from_env(view->ops->argv, view->cmd_env);
6436         init_display();
6438         if (request == REQ_VIEW_PAGER || run_argv) {
6439                 if (request == REQ_VIEW_PAGER)
6440                         io_open(&VIEW(request)->io, "");
6441                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6442                         die("Failed to format arguments");
6443                 open_view(NULL, request, OPEN_PREPARED);
6444                 request = REQ_NONE;
6445         }
6447         while (view_driver(display[current_view], request)) {
6448                 int key;
6449                 int i;
6451                 foreach_view (view, i)
6452                         update_view(view);
6453                 view = display[current_view];
6455                 /* Refresh, accept single keystroke of input */
6456                 key = wgetch(status_win);
6458                 /* wgetch() with nodelay() enabled returns ERR when there's no
6459                  * input. */
6460                 if (key == ERR) {
6461                         request = REQ_NONE;
6462                         continue;
6463                 }
6465                 request = get_keybinding(view->keymap, key);
6467                 /* Some low-level request handling. This keeps access to
6468                  * status_win restricted. */
6469                 switch (request) {
6470                 case REQ_PROMPT:
6471                 {
6472                         char *cmd = read_prompt(":");
6474                         if (cmd) {
6475                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6476                                 const char *argv[SIZEOF_ARG] = { "git" };
6477                                 int argc = 1;
6479                                 /* When running random commands, initially show the
6480                                  * command in the title. However, it maybe later be
6481                                  * overwritten if a commit line is selected. */
6482                                 string_ncopy(next->ref, cmd, strlen(cmd));
6484                                 if (!argv_from_string(argv, &argc, cmd)) {
6485                                         report("Too many arguments");
6486                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6487                                         report("Failed to format command");
6488                                 } else {
6489                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6490                                 }
6491                         }
6493                         request = REQ_NONE;
6494                         break;
6495                 }
6496                 case REQ_SEARCH:
6497                 case REQ_SEARCH_BACK:
6498                 {
6499                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6500                         char *search = read_prompt(prompt);
6502                         if (search)
6503                                 string_ncopy(opt_search, search, strlen(search));
6504                         else
6505                                 request = REQ_NONE;
6506                         break;
6507                 }
6508                 case REQ_SCREEN_RESIZE:
6509                 {
6510                         int height, width;
6512                         getmaxyx(stdscr, height, width);
6514                         /* Resize the status view and let the view driver take
6515                          * care of resizing the displayed views. */
6516                         wresize(status_win, 1, width);
6517                         mvwin(status_win, height - 1, 0);
6518                         wrefresh(status_win);
6519                         break;
6520                 }
6521                 default:
6522                         break;
6523                 }
6524         }
6526         quit(0);
6528         return 0;