Code

IO API: use in the blame view
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123         "git ls-remote . 2>/dev/null"
125 #define TIG_MAIN_BASE \
126         "git log --no-color --pretty=raw --parents --topo-order"
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB         '\t'
130 #define KEY_RETURN      '\r'
131 #define KEY_ESC         27
134 struct ref {
135         char *name;             /* Ref name; tag or head names are shortened. */
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         unsigned int head:1;    /* Is it the current HEAD? */
138         unsigned int tag:1;     /* Is it a tag? */
139         unsigned int ltag:1;    /* If so, is the tag local? */
140         unsigned int remote:1;  /* Is it a remote ref? */
141         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
142         unsigned int next:1;    /* For ref lists: are there more refs? */
143 };
145 static struct ref **get_refs(const char *id);
147 enum format_flags {
148         FORMAT_ALL,             /* Perform replacement in all arguments. */
149         FORMAT_DASH,            /* Perform replacement up until "--". */
150         FORMAT_NONE             /* No replacement should be performed. */
151 };
153 static bool format_command(char dst[], const char *src[], enum format_flags flags);
154 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
156 struct int_map {
157         const char *name;
158         int namelen;
159         int value;
160 };
162 static int
163 set_from_int_map(struct int_map *map, size_t map_size,
164                  int *value, const char *name, int namelen)
167         int i;
169         for (i = 0; i < map_size; i++)
170                 if (namelen == map[i].namelen &&
171                     !strncasecmp(name, map[i].name, namelen)) {
172                         *value = map[i].value;
173                         return OK;
174                 }
176         return ERR;
180 /*
181  * String helpers
182  */
184 static inline void
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187         if (srclen > dstlen - 1)
188                 srclen = dstlen - 1;
190         strncpy(dst, src, srclen);
191         dst[srclen] = 0;
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200         string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
208 static char *
209 chomp_string(char *name)
211         int namelen;
213         while (isspace(*name))
214                 name++;
216         namelen = strlen(name) - 1;
217         while (namelen > 0 && isspace(name[namelen]))
218                 name[namelen--] = 0;
220         return name;
223 static bool
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
226         va_list args;
227         size_t pos = bufpos ? *bufpos : 0;
229         va_start(args, fmt);
230         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
231         va_end(args);
233         if (bufpos)
234                 *bufpos = pos;
236         return pos >= bufsize ? FALSE : TRUE;
239 #define string_format(buf, fmt, args...) \
240         string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243         string_nformat(buf, sizeof(buf), from, fmt, args)
245 static int
246 string_enum_compare(const char *str1, const char *str2, int len)
248         size_t i;
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252         /* Diff-Header == DIFF_HEADER */
253         for (i = 0; i < len; i++) {
254                 if (toupper(str1[i]) == toupper(str2[i]))
255                         continue;
257                 if (string_enum_sep(str1[i]) &&
258                     string_enum_sep(str2[i]))
259                         continue;
261                 return str1[i] - str2[i];
262         }
264         return 0;
267 #define prefixcmp(str1, str2) \
268         strncmp(str1, str2, STRING_SIZE(str2))
270 static inline int
271 suffixcmp(const char *str, int slen, const char *suffix)
273         size_t len = slen >= 0 ? slen : strlen(str);
274         size_t suffixlen = strlen(suffix);
276         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
279 /* Shell quoting
280  *
281  * NOTE: The following is a slightly modified copy of the git project's shell
282  * quoting routines found in the quote.c file.
283  *
284  * Help to copy the thing properly quoted for the shell safety.  any single
285  * quote is replaced with '\'', any exclamation point is replaced with '\!',
286  * and the whole thing is enclosed in a
287  *
288  * E.g.
289  *  original     sq_quote     result
290  *  name     ==> name      ==> 'name'
291  *  a b      ==> a b       ==> 'a b'
292  *  a'b      ==> a'\''b    ==> 'a'\''b'
293  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
294  */
296 static size_t
297 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
299         char c;
301 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
303         BUFPUT('\'');
304         while ((c = *src++)) {
305                 if (c == '\'' || c == '!') {
306                         BUFPUT('\'');
307                         BUFPUT('\\');
308                         BUFPUT(c);
309                         BUFPUT('\'');
310                 } else {
311                         BUFPUT(c);
312                 }
313         }
314         BUFPUT('\'');
316         if (bufsize < SIZEOF_STR)
317                 buf[bufsize] = 0;
319         return bufsize;
322 static bool
323 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
325         int valuelen;
327         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
328                 bool advance = cmd[valuelen] != 0;
330                 cmd[valuelen] = 0;
331                 argv[(*argc)++] = chomp_string(cmd);
332                 cmd += valuelen + advance;
333         }
335         if (*argc < SIZEOF_ARG)
336                 argv[*argc] = NULL;
337         return *argc < SIZEOF_ARG;
340 static void
341 argv_from_env(const char **argv, const char *name)
343         char *env = argv ? getenv(name) : NULL;
344         int argc = 0;
346         if (env && *env)
347                 env = strdup(env);
348         if (env && !argv_from_string(argv, &argc, env))
349                 die("Too many arguments in the `%s` environment variable", name);
353 /*
354  * Executing external commands.
355  */
357 enum io_type {
358         IO_FD,                  /* File descriptor based IO. */
359         IO_FG,                  /* Execute command with same std{in,out,err}. */
360         IO_RD,                  /* Read only fork+exec IO. */
361         IO_WR,                  /* Write only fork+exec IO. */
362 };
364 struct io {
365         enum io_type type;      /* The requested type of pipe. */
366         const char *dir;        /* Directory from which to execute. */
367         FILE *pipe;             /* Pipe for reading or writing. */
368         int error;              /* Error status. */
369         char sh[SIZEOF_STR];    /* Shell command buffer. */
370         char *buf;              /* Read/write buffer. */
371         size_t bufalloc;        /* Allocated buffer size. */
372 };
374 static void
375 reset_io(struct io *io)
377         io->pipe = NULL;
378         io->buf = NULL;
379         io->bufalloc = 0;
380         io->error = 0;
383 static void
384 init_io(struct io *io, const char *dir, enum io_type type)
386         reset_io(io);
387         io->type = type;
388         io->dir = dir;
391 static bool
392 init_io_rd(struct io *io, const char *argv[], const char *dir,
393                 enum format_flags flags)
395         init_io(io, dir, IO_RD);
396         return format_command(io->sh, argv, flags);
399 static bool
400 init_io_fd(struct io *io, FILE *pipe)
402         init_io(io, NULL, IO_FD);
403         io->pipe = pipe;
404         return io->pipe != NULL;
407 static bool
408 done_io(struct io *io)
410         free(io->buf);
411         if (io->type == IO_FD)
412                 fclose(io->pipe);
413         else if (io->type == IO_RD || io->type == IO_WR)
414                 pclose(io->pipe);
415         reset_io(io);
416         return TRUE;
419 static bool
420 start_io(struct io *io)
422         char buf[SIZEOF_STR * 2];
423         size_t bufpos = 0;
425         if (io->dir && *io->dir &&
426             !string_format_from(buf, &bufpos, "cd %s;", io->dir))
427                 return FALSE;
429         if (!string_format_from(buf, &bufpos, "%s", io->sh))
430                 return FALSE;
432         if (io->type == IO_FG)
433                 return system(buf) == 0;
435         io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
436         return io->pipe != NULL;
439 static bool
440 run_io(struct io *io, enum io_type type, const char *cmd)
442         init_io(io, NULL, type);
443         string_ncopy(io->sh, cmd, strlen(cmd));
444         return start_io(io);
447 static int
448 run_io_do(struct io *io)
450         return start_io(io) && done_io(io);
453 static bool
454 run_io_fg(const char **argv, const char *dir)
456         struct io io = {};
458         init_io(&io, dir, IO_FG);
459         if (!format_command(io.sh, argv, FORMAT_NONE))
460                 return FALSE;
461         return run_io_do(&io);
464 static bool
465 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
467         return init_io_rd(io, argv, NULL, flags) && start_io(io);
470 static bool
471 io_eof(struct io *io)
473         return feof(io->pipe);
476 static int
477 io_error(struct io *io)
479         return io->error;
482 static bool
483 io_strerror(struct io *io)
485         return strerror(io->error);
488 static char *
489 io_gets(struct io *io)
491         if (!io->buf) {
492                 io->buf = malloc(BUFSIZ);
493                 if (!io->buf)
494                         return NULL;
495                 io->bufalloc = BUFSIZ;
496         }
498         if (!fgets(io->buf, io->bufalloc, io->pipe)) {
499                 if (ferror(io->pipe))
500                         io->error = errno;
501                 return NULL;
502         }
504         return io->buf;
508 /*
509  * User requests
510  */
512 #define REQ_INFO \
513         /* XXX: Keep the view request first and in sync with views[]. */ \
514         REQ_GROUP("View switching") \
515         REQ_(VIEW_MAIN,         "Show main view"), \
516         REQ_(VIEW_DIFF,         "Show diff view"), \
517         REQ_(VIEW_LOG,          "Show log view"), \
518         REQ_(VIEW_TREE,         "Show tree view"), \
519         REQ_(VIEW_BLOB,         "Show blob view"), \
520         REQ_(VIEW_BLAME,        "Show blame view"), \
521         REQ_(VIEW_HELP,         "Show help page"), \
522         REQ_(VIEW_PAGER,        "Show pager view"), \
523         REQ_(VIEW_STATUS,       "Show status view"), \
524         REQ_(VIEW_STAGE,        "Show stage view"), \
525         \
526         REQ_GROUP("View manipulation") \
527         REQ_(ENTER,             "Enter current line and scroll"), \
528         REQ_(NEXT,              "Move to next"), \
529         REQ_(PREVIOUS,          "Move to previous"), \
530         REQ_(VIEW_NEXT,         "Move focus to next view"), \
531         REQ_(REFRESH,           "Reload and refresh"), \
532         REQ_(MAXIMIZE,          "Maximize the current view"), \
533         REQ_(VIEW_CLOSE,        "Close the current view"), \
534         REQ_(QUIT,              "Close all views and quit"), \
535         \
536         REQ_GROUP("View specific requests") \
537         REQ_(STATUS_UPDATE,     "Update file status"), \
538         REQ_(STATUS_REVERT,     "Revert file changes"), \
539         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
540         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
541         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
542         \
543         REQ_GROUP("Cursor navigation") \
544         REQ_(MOVE_UP,           "Move cursor one line up"), \
545         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
546         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
547         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
548         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
549         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
550         \
551         REQ_GROUP("Scrolling") \
552         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
553         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
554         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
555         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
556         \
557         REQ_GROUP("Searching") \
558         REQ_(SEARCH,            "Search the view"), \
559         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
560         REQ_(FIND_NEXT,         "Find next search match"), \
561         REQ_(FIND_PREV,         "Find previous search match"), \
562         \
563         REQ_GROUP("Option manipulation") \
564         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
565         REQ_(TOGGLE_DATE,       "Toggle date display"), \
566         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
567         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
568         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
569         \
570         REQ_GROUP("Misc") \
571         REQ_(PROMPT,            "Bring up the prompt"), \
572         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
573         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
574         REQ_(SHOW_VERSION,      "Show version information"), \
575         REQ_(STOP_LOADING,      "Stop all loading views"), \
576         REQ_(EDIT,              "Open in editor"), \
577         REQ_(NONE,              "Do nothing")
580 /* User action requests. */
581 enum request {
582 #define REQ_GROUP(help)
583 #define REQ_(req, help) REQ_##req
585         /* Offset all requests to avoid conflicts with ncurses getch values. */
586         REQ_OFFSET = KEY_MAX + 1,
587         REQ_INFO
589 #undef  REQ_GROUP
590 #undef  REQ_
591 };
593 struct request_info {
594         enum request request;
595         const char *name;
596         int namelen;
597         const char *help;
598 };
600 static struct request_info req_info[] = {
601 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
602 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
603         REQ_INFO
604 #undef  REQ_GROUP
605 #undef  REQ_
606 };
608 static enum request
609 get_request(const char *name)
611         int namelen = strlen(name);
612         int i;
614         for (i = 0; i < ARRAY_SIZE(req_info); i++)
615                 if (req_info[i].namelen == namelen &&
616                     !string_enum_compare(req_info[i].name, name, namelen))
617                         return req_info[i].request;
619         return REQ_NONE;
623 /*
624  * Options
625  */
627 static const char usage[] =
628 "tig " TIG_VERSION " (" __DATE__ ")\n"
629 "\n"
630 "Usage: tig        [options] [revs] [--] [paths]\n"
631 "   or: tig show   [options] [revs] [--] [paths]\n"
632 "   or: tig blame  [rev] path\n"
633 "   or: tig status\n"
634 "   or: tig <      [git command output]\n"
635 "\n"
636 "Options:\n"
637 "  -v, --version   Show version and exit\n"
638 "  -h, --help      Show help message and exit";
640 /* Option and state variables. */
641 static bool opt_date                    = TRUE;
642 static bool opt_author                  = TRUE;
643 static bool opt_line_number             = FALSE;
644 static bool opt_line_graphics           = TRUE;
645 static bool opt_rev_graph               = FALSE;
646 static bool opt_show_refs               = TRUE;
647 static int opt_num_interval             = NUMBER_INTERVAL;
648 static int opt_tab_size                 = TAB_SIZE;
649 static int opt_author_cols              = AUTHOR_COLS-1;
650 static char opt_cmd[SIZEOF_STR]         = "";
651 static char opt_path[SIZEOF_STR]        = "";
652 static char opt_file[SIZEOF_STR]        = "";
653 static char opt_ref[SIZEOF_REF]         = "";
654 static char opt_head[SIZEOF_REF]        = "";
655 static char opt_head_rev[SIZEOF_REV]    = "";
656 static char opt_remote[SIZEOF_REF]      = "";
657 static FILE *opt_pipe                   = NULL;
658 static char opt_encoding[20]            = "UTF-8";
659 static bool opt_utf8                    = TRUE;
660 static char opt_codeset[20]             = "UTF-8";
661 static iconv_t opt_iconv                = ICONV_NONE;
662 static char opt_search[SIZEOF_STR]      = "";
663 static char opt_cdup[SIZEOF_STR]        = "";
664 static char opt_git_dir[SIZEOF_STR]     = "";
665 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
666 static char opt_editor[SIZEOF_STR]      = "";
667 static FILE *opt_tty                    = NULL;
669 #define is_initial_commit()     (!*opt_head_rev)
670 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
672 static enum request
673 parse_options(int argc, const char *argv[])
675         enum request request = REQ_VIEW_MAIN;
676         size_t buf_size;
677         const char *subcommand;
678         bool seen_dashdash = FALSE;
679         int i;
681         if (!isatty(STDIN_FILENO)) {
682                 opt_pipe = stdin;
683                 return REQ_VIEW_PAGER;
684         }
686         if (argc <= 1)
687                 return REQ_VIEW_MAIN;
689         subcommand = argv[1];
690         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
691                 if (!strcmp(subcommand, "-S"))
692                         warn("`-S' has been deprecated; use `tig status' instead");
693                 if (argc > 2)
694                         warn("ignoring arguments after `%s'", subcommand);
695                 return REQ_VIEW_STATUS;
697         } else if (!strcmp(subcommand, "blame")) {
698                 if (argc <= 2 || argc > 4)
699                         die("invalid number of options to blame\n\n%s", usage);
701                 i = 2;
702                 if (argc == 4) {
703                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
704                         i++;
705                 }
707                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
708                 return REQ_VIEW_BLAME;
710         } else if (!strcmp(subcommand, "show")) {
711                 request = REQ_VIEW_DIFF;
713         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
714                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
715                 warn("`tig %s' has been deprecated", subcommand);
717         } else {
718                 subcommand = NULL;
719         }
721         if (!subcommand)
722                 /* XXX: This is vulnerable to the user overriding
723                  * options required for the main view parser. */
724                 string_copy(opt_cmd, TIG_MAIN_BASE);
725         else
726                 string_format(opt_cmd, "git %s", subcommand);
728         buf_size = strlen(opt_cmd);
730         for (i = 1 + !!subcommand; i < argc; i++) {
731                 const char *opt = argv[i];
733                 if (seen_dashdash || !strcmp(opt, "--")) {
734                         seen_dashdash = TRUE;
736                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
737                         printf("tig version %s\n", TIG_VERSION);
738                         return REQ_NONE;
740                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
741                         printf("%s\n", usage);
742                         return REQ_NONE;
743                 }
745                 opt_cmd[buf_size++] = ' ';
746                 buf_size = sq_quote(opt_cmd, buf_size, opt);
747                 if (buf_size >= sizeof(opt_cmd))
748                         die("command too long");
749         }
751         opt_cmd[buf_size] = 0;
753         return request;
757 /*
758  * Line-oriented content detection.
759  */
761 #define LINE_INFO \
762 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
763 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
764 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
765 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
766 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
767 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
768 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
769 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
770 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
771 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
772 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
773 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
774 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
775 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
776 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
777 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
778 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
779 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
780 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
781 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
782 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
783 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
784 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
785 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
786 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
787 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
788 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
789 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
790 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
791 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
792 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
793 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
794 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
795 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
796 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
797 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
798 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
799 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
800 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
801 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
802 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
803 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
804 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
805 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
806 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
807 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
808 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
809 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
810 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
811 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
812 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
813 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
814 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
815 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
817 enum line_type {
818 #define LINE(type, line, fg, bg, attr) \
819         LINE_##type
820         LINE_INFO,
821         LINE_NONE
822 #undef  LINE
823 };
825 struct line_info {
826         const char *name;       /* Option name. */
827         int namelen;            /* Size of option name. */
828         const char *line;       /* The start of line to match. */
829         int linelen;            /* Size of string to match. */
830         int fg, bg, attr;       /* Color and text attributes for the lines. */
831 };
833 static struct line_info line_info[] = {
834 #define LINE(type, line, fg, bg, attr) \
835         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
836         LINE_INFO
837 #undef  LINE
838 };
840 static enum line_type
841 get_line_type(const char *line)
843         int linelen = strlen(line);
844         enum line_type type;
846         for (type = 0; type < ARRAY_SIZE(line_info); type++)
847                 /* Case insensitive search matches Signed-off-by lines better. */
848                 if (linelen >= line_info[type].linelen &&
849                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
850                         return type;
852         return LINE_DEFAULT;
855 static inline int
856 get_line_attr(enum line_type type)
858         assert(type < ARRAY_SIZE(line_info));
859         return COLOR_PAIR(type) | line_info[type].attr;
862 static struct line_info *
863 get_line_info(const char *name)
865         size_t namelen = strlen(name);
866         enum line_type type;
868         for (type = 0; type < ARRAY_SIZE(line_info); type++)
869                 if (namelen == line_info[type].namelen &&
870                     !string_enum_compare(line_info[type].name, name, namelen))
871                         return &line_info[type];
873         return NULL;
876 static void
877 init_colors(void)
879         int default_bg = line_info[LINE_DEFAULT].bg;
880         int default_fg = line_info[LINE_DEFAULT].fg;
881         enum line_type type;
883         start_color();
885         if (assume_default_colors(default_fg, default_bg) == ERR) {
886                 default_bg = COLOR_BLACK;
887                 default_fg = COLOR_WHITE;
888         }
890         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
891                 struct line_info *info = &line_info[type];
892                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
893                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
895                 init_pair(type, fg, bg);
896         }
899 struct line {
900         enum line_type type;
902         /* State flags */
903         unsigned int selected:1;
904         unsigned int dirty:1;
906         void *data;             /* User data */
907 };
910 /*
911  * Keys
912  */
914 struct keybinding {
915         int alias;
916         enum request request;
917 };
919 static struct keybinding default_keybindings[] = {
920         /* View switching */
921         { 'm',          REQ_VIEW_MAIN },
922         { 'd',          REQ_VIEW_DIFF },
923         { 'l',          REQ_VIEW_LOG },
924         { 't',          REQ_VIEW_TREE },
925         { 'f',          REQ_VIEW_BLOB },
926         { 'B',          REQ_VIEW_BLAME },
927         { 'p',          REQ_VIEW_PAGER },
928         { 'h',          REQ_VIEW_HELP },
929         { 'S',          REQ_VIEW_STATUS },
930         { 'c',          REQ_VIEW_STAGE },
932         /* View manipulation */
933         { 'q',          REQ_VIEW_CLOSE },
934         { KEY_TAB,      REQ_VIEW_NEXT },
935         { KEY_RETURN,   REQ_ENTER },
936         { KEY_UP,       REQ_PREVIOUS },
937         { KEY_DOWN,     REQ_NEXT },
938         { 'R',          REQ_REFRESH },
939         { KEY_F(5),     REQ_REFRESH },
940         { 'O',          REQ_MAXIMIZE },
942         /* Cursor navigation */
943         { 'k',          REQ_MOVE_UP },
944         { 'j',          REQ_MOVE_DOWN },
945         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
946         { KEY_END,      REQ_MOVE_LAST_LINE },
947         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
948         { ' ',          REQ_MOVE_PAGE_DOWN },
949         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
950         { 'b',          REQ_MOVE_PAGE_UP },
951         { '-',          REQ_MOVE_PAGE_UP },
953         /* Scrolling */
954         { KEY_IC,       REQ_SCROLL_LINE_UP },
955         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
956         { 'w',          REQ_SCROLL_PAGE_UP },
957         { 's',          REQ_SCROLL_PAGE_DOWN },
959         /* Searching */
960         { '/',          REQ_SEARCH },
961         { '?',          REQ_SEARCH_BACK },
962         { 'n',          REQ_FIND_NEXT },
963         { 'N',          REQ_FIND_PREV },
965         /* Misc */
966         { 'Q',          REQ_QUIT },
967         { 'z',          REQ_STOP_LOADING },
968         { 'v',          REQ_SHOW_VERSION },
969         { 'r',          REQ_SCREEN_REDRAW },
970         { '.',          REQ_TOGGLE_LINENO },
971         { 'D',          REQ_TOGGLE_DATE },
972         { 'A',          REQ_TOGGLE_AUTHOR },
973         { 'g',          REQ_TOGGLE_REV_GRAPH },
974         { 'F',          REQ_TOGGLE_REFS },
975         { ':',          REQ_PROMPT },
976         { 'u',          REQ_STATUS_UPDATE },
977         { '!',          REQ_STATUS_REVERT },
978         { 'M',          REQ_STATUS_MERGE },
979         { '@',          REQ_STAGE_NEXT },
980         { ',',          REQ_TREE_PARENT },
981         { 'e',          REQ_EDIT },
983         /* Using the ncurses SIGWINCH handler. */
984         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
985 };
987 #define KEYMAP_INFO \
988         KEYMAP_(GENERIC), \
989         KEYMAP_(MAIN), \
990         KEYMAP_(DIFF), \
991         KEYMAP_(LOG), \
992         KEYMAP_(TREE), \
993         KEYMAP_(BLOB), \
994         KEYMAP_(BLAME), \
995         KEYMAP_(PAGER), \
996         KEYMAP_(HELP), \
997         KEYMAP_(STATUS), \
998         KEYMAP_(STAGE)
1000 enum keymap {
1001 #define KEYMAP_(name) KEYMAP_##name
1002         KEYMAP_INFO
1003 #undef  KEYMAP_
1004 };
1006 static struct int_map keymap_table[] = {
1007 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1008         KEYMAP_INFO
1009 #undef  KEYMAP_
1010 };
1012 #define set_keymap(map, name) \
1013         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1015 struct keybinding_table {
1016         struct keybinding *data;
1017         size_t size;
1018 };
1020 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1022 static void
1023 add_keybinding(enum keymap keymap, enum request request, int key)
1025         struct keybinding_table *table = &keybindings[keymap];
1027         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1028         if (!table->data)
1029                 die("Failed to allocate keybinding");
1030         table->data[table->size].alias = key;
1031         table->data[table->size++].request = request;
1034 /* Looks for a key binding first in the given map, then in the generic map, and
1035  * lastly in the default keybindings. */
1036 static enum request
1037 get_keybinding(enum keymap keymap, int key)
1039         size_t i;
1041         for (i = 0; i < keybindings[keymap].size; i++)
1042                 if (keybindings[keymap].data[i].alias == key)
1043                         return keybindings[keymap].data[i].request;
1045         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1046                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1047                         return keybindings[KEYMAP_GENERIC].data[i].request;
1049         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1050                 if (default_keybindings[i].alias == key)
1051                         return default_keybindings[i].request;
1053         return (enum request) key;
1057 struct key {
1058         const char *name;
1059         int value;
1060 };
1062 static struct key key_table[] = {
1063         { "Enter",      KEY_RETURN },
1064         { "Space",      ' ' },
1065         { "Backspace",  KEY_BACKSPACE },
1066         { "Tab",        KEY_TAB },
1067         { "Escape",     KEY_ESC },
1068         { "Left",       KEY_LEFT },
1069         { "Right",      KEY_RIGHT },
1070         { "Up",         KEY_UP },
1071         { "Down",       KEY_DOWN },
1072         { "Insert",     KEY_IC },
1073         { "Delete",     KEY_DC },
1074         { "Hash",       '#' },
1075         { "Home",       KEY_HOME },
1076         { "End",        KEY_END },
1077         { "PageUp",     KEY_PPAGE },
1078         { "PageDown",   KEY_NPAGE },
1079         { "F1",         KEY_F(1) },
1080         { "F2",         KEY_F(2) },
1081         { "F3",         KEY_F(3) },
1082         { "F4",         KEY_F(4) },
1083         { "F5",         KEY_F(5) },
1084         { "F6",         KEY_F(6) },
1085         { "F7",         KEY_F(7) },
1086         { "F8",         KEY_F(8) },
1087         { "F9",         KEY_F(9) },
1088         { "F10",        KEY_F(10) },
1089         { "F11",        KEY_F(11) },
1090         { "F12",        KEY_F(12) },
1091 };
1093 static int
1094 get_key_value(const char *name)
1096         int i;
1098         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1099                 if (!strcasecmp(key_table[i].name, name))
1100                         return key_table[i].value;
1102         if (strlen(name) == 1 && isprint(*name))
1103                 return (int) *name;
1105         return ERR;
1108 static const char *
1109 get_key_name(int key_value)
1111         static char key_char[] = "'X'";
1112         const char *seq = NULL;
1113         int key;
1115         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1116                 if (key_table[key].value == key_value)
1117                         seq = key_table[key].name;
1119         if (seq == NULL &&
1120             key_value < 127 &&
1121             isprint(key_value)) {
1122                 key_char[1] = (char) key_value;
1123                 seq = key_char;
1124         }
1126         return seq ? seq : "(no key)";
1129 static const char *
1130 get_key(enum request request)
1132         static char buf[BUFSIZ];
1133         size_t pos = 0;
1134         char *sep = "";
1135         int i;
1137         buf[pos] = 0;
1139         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1140                 struct keybinding *keybinding = &default_keybindings[i];
1142                 if (keybinding->request != request)
1143                         continue;
1145                 if (!string_format_from(buf, &pos, "%s%s", sep,
1146                                         get_key_name(keybinding->alias)))
1147                         return "Too many keybindings!";
1148                 sep = ", ";
1149         }
1151         return buf;
1154 struct run_request {
1155         enum keymap keymap;
1156         int key;
1157         const char *argv[SIZEOF_ARG];
1158 };
1160 static struct run_request *run_request;
1161 static size_t run_requests;
1163 static enum request
1164 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1166         struct run_request *req;
1168         if (argc >= ARRAY_SIZE(req->argv) - 1)
1169                 return REQ_NONE;
1171         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1172         if (!req)
1173                 return REQ_NONE;
1175         run_request = req;
1176         req = &run_request[run_requests];
1177         req->keymap = keymap;
1178         req->key = key;
1179         req->argv[0] = NULL;
1181         if (!format_argv(req->argv, argv, FORMAT_NONE))
1182                 return REQ_NONE;
1184         return REQ_NONE + ++run_requests;
1187 static struct run_request *
1188 get_run_request(enum request request)
1190         if (request <= REQ_NONE)
1191                 return NULL;
1192         return &run_request[request - REQ_NONE - 1];
1195 static void
1196 add_builtin_run_requests(void)
1198         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1199         const char *gc[] = { "git", "gc", NULL };
1200         struct {
1201                 enum keymap keymap;
1202                 int key;
1203                 int argc;
1204                 const char **argv;
1205         } reqs[] = {
1206                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1207                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1208         };
1209         int i;
1211         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1212                 enum request req;
1214                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1215                 if (req != REQ_NONE)
1216                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1217         }
1220 /*
1221  * User config file handling.
1222  */
1224 static struct int_map color_map[] = {
1225 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1226         COLOR_MAP(DEFAULT),
1227         COLOR_MAP(BLACK),
1228         COLOR_MAP(BLUE),
1229         COLOR_MAP(CYAN),
1230         COLOR_MAP(GREEN),
1231         COLOR_MAP(MAGENTA),
1232         COLOR_MAP(RED),
1233         COLOR_MAP(WHITE),
1234         COLOR_MAP(YELLOW),
1235 };
1237 #define set_color(color, name) \
1238         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1240 static struct int_map attr_map[] = {
1241 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1242         ATTR_MAP(NORMAL),
1243         ATTR_MAP(BLINK),
1244         ATTR_MAP(BOLD),
1245         ATTR_MAP(DIM),
1246         ATTR_MAP(REVERSE),
1247         ATTR_MAP(STANDOUT),
1248         ATTR_MAP(UNDERLINE),
1249 };
1251 #define set_attribute(attr, name) \
1252         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1254 static int   config_lineno;
1255 static bool  config_errors;
1256 static const char *config_msg;
1258 /* Wants: object fgcolor bgcolor [attr] */
1259 static int
1260 option_color_command(int argc, const char *argv[])
1262         struct line_info *info;
1264         if (argc != 3 && argc != 4) {
1265                 config_msg = "Wrong number of arguments given to color command";
1266                 return ERR;
1267         }
1269         info = get_line_info(argv[0]);
1270         if (!info) {
1271                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1272                         info = get_line_info("delimiter");
1274                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1275                         info = get_line_info("date");
1277                 } else {
1278                         config_msg = "Unknown color name";
1279                         return ERR;
1280                 }
1281         }
1283         if (set_color(&info->fg, argv[1]) == ERR ||
1284             set_color(&info->bg, argv[2]) == ERR) {
1285                 config_msg = "Unknown color";
1286                 return ERR;
1287         }
1289         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1290                 config_msg = "Unknown attribute";
1291                 return ERR;
1292         }
1294         return OK;
1297 static bool parse_bool(const char *s)
1299         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1300                 !strcmp(s, "yes")) ? TRUE : FALSE;
1303 static int
1304 parse_int(const char *s, int default_value, int min, int max)
1306         int value = atoi(s);
1308         return (value < min || value > max) ? default_value : value;
1311 /* Wants: name = value */
1312 static int
1313 option_set_command(int argc, const char *argv[])
1315         if (argc != 3) {
1316                 config_msg = "Wrong number of arguments given to set command";
1317                 return ERR;
1318         }
1320         if (strcmp(argv[1], "=")) {
1321                 config_msg = "No value assigned";
1322                 return ERR;
1323         }
1325         if (!strcmp(argv[0], "show-author")) {
1326                 opt_author = parse_bool(argv[2]);
1327                 return OK;
1328         }
1330         if (!strcmp(argv[0], "show-date")) {
1331                 opt_date = parse_bool(argv[2]);
1332                 return OK;
1333         }
1335         if (!strcmp(argv[0], "show-rev-graph")) {
1336                 opt_rev_graph = parse_bool(argv[2]);
1337                 return OK;
1338         }
1340         if (!strcmp(argv[0], "show-refs")) {
1341                 opt_show_refs = parse_bool(argv[2]);
1342                 return OK;
1343         }
1345         if (!strcmp(argv[0], "show-line-numbers")) {
1346                 opt_line_number = parse_bool(argv[2]);
1347                 return OK;
1348         }
1350         if (!strcmp(argv[0], "line-graphics")) {
1351                 opt_line_graphics = parse_bool(argv[2]);
1352                 return OK;
1353         }
1355         if (!strcmp(argv[0], "line-number-interval")) {
1356                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1357                 return OK;
1358         }
1360         if (!strcmp(argv[0], "author-width")) {
1361                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1362                 return OK;
1363         }
1365         if (!strcmp(argv[0], "tab-size")) {
1366                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1367                 return OK;
1368         }
1370         if (!strcmp(argv[0], "commit-encoding")) {
1371                 const char *arg = argv[2];
1372                 int arglen = strlen(arg);
1374                 switch (arg[0]) {
1375                 case '"':
1376                 case '\'':
1377                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1378                                 config_msg = "Unmatched quotation";
1379                                 return ERR;
1380                         }
1381                         arg += 1; arglen -= 2;
1382                 default:
1383                         string_ncopy(opt_encoding, arg, strlen(arg));
1384                         return OK;
1385                 }
1386         }
1388         config_msg = "Unknown variable name";
1389         return ERR;
1392 /* Wants: mode request key */
1393 static int
1394 option_bind_command(int argc, const char *argv[])
1396         enum request request;
1397         int keymap;
1398         int key;
1400         if (argc < 3) {
1401                 config_msg = "Wrong number of arguments given to bind command";
1402                 return ERR;
1403         }
1405         if (set_keymap(&keymap, argv[0]) == ERR) {
1406                 config_msg = "Unknown key map";
1407                 return ERR;
1408         }
1410         key = get_key_value(argv[1]);
1411         if (key == ERR) {
1412                 config_msg = "Unknown key";
1413                 return ERR;
1414         }
1416         request = get_request(argv[2]);
1417         if (request == REQ_NONE) {
1418                 const char *obsolete[] = { "cherry-pick" };
1419                 size_t namelen = strlen(argv[2]);
1420                 int i;
1422                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1423                         if (namelen == strlen(obsolete[i]) &&
1424                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1425                                 config_msg = "Obsolete request name";
1426                                 return ERR;
1427                         }
1428                 }
1429         }
1430         if (request == REQ_NONE && *argv[2]++ == '!')
1431                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1432         if (request == REQ_NONE) {
1433                 config_msg = "Unknown request name";
1434                 return ERR;
1435         }
1437         add_keybinding(keymap, request, key);
1439         return OK;
1442 static int
1443 set_option(const char *opt, char *value)
1445         const char *argv[SIZEOF_ARG];
1446         int argc = 0;
1448         if (!argv_from_string(argv, &argc, value)) {
1449                 config_msg = "Too many option arguments";
1450                 return ERR;
1451         }
1453         if (!strcmp(opt, "color"))
1454                 return option_color_command(argc, argv);
1456         if (!strcmp(opt, "set"))
1457                 return option_set_command(argc, argv);
1459         if (!strcmp(opt, "bind"))
1460                 return option_bind_command(argc, argv);
1462         config_msg = "Unknown option command";
1463         return ERR;
1466 static int
1467 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1469         int status = OK;
1471         config_lineno++;
1472         config_msg = "Internal error";
1474         /* Check for comment markers, since read_properties() will
1475          * only ensure opt and value are split at first " \t". */
1476         optlen = strcspn(opt, "#");
1477         if (optlen == 0)
1478                 return OK;
1480         if (opt[optlen] != 0) {
1481                 config_msg = "No option value";
1482                 status = ERR;
1484         }  else {
1485                 /* Look for comment endings in the value. */
1486                 size_t len = strcspn(value, "#");
1488                 if (len < valuelen) {
1489                         valuelen = len;
1490                         value[valuelen] = 0;
1491                 }
1493                 status = set_option(opt, value);
1494         }
1496         if (status == ERR) {
1497                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1498                         config_lineno, (int) optlen, opt, config_msg);
1499                 config_errors = TRUE;
1500         }
1502         /* Always keep going if errors are encountered. */
1503         return OK;
1506 static void
1507 load_option_file(const char *path)
1509         FILE *file;
1511         /* It's ok that the file doesn't exist. */
1512         file = fopen(path, "r");
1513         if (!file)
1514                 return;
1516         config_lineno = 0;
1517         config_errors = FALSE;
1519         if (read_properties(file, " \t", read_option) == ERR ||
1520             config_errors == TRUE)
1521                 fprintf(stderr, "Errors while loading %s.\n", path);
1524 static int
1525 load_options(void)
1527         const char *home = getenv("HOME");
1528         const char *tigrc_user = getenv("TIGRC_USER");
1529         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1530         char buf[SIZEOF_STR];
1532         add_builtin_run_requests();
1534         if (!tigrc_system) {
1535                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1536                         return ERR;
1537                 tigrc_system = buf;
1538         }
1539         load_option_file(tigrc_system);
1541         if (!tigrc_user) {
1542                 if (!home || !string_format(buf, "%s/.tigrc", home))
1543                         return ERR;
1544                 tigrc_user = buf;
1545         }
1546         load_option_file(tigrc_user);
1548         return OK;
1552 /*
1553  * The viewer
1554  */
1556 struct view;
1557 struct view_ops;
1559 /* The display array of active views and the index of the current view. */
1560 static struct view *display[2];
1561 static unsigned int current_view;
1563 /* Reading from the prompt? */
1564 static bool input_mode = FALSE;
1566 #define foreach_displayed_view(view, i) \
1567         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1569 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1571 /* Current head and commit ID */
1572 static char ref_blob[SIZEOF_REF]        = "";
1573 static char ref_commit[SIZEOF_REF]      = "HEAD";
1574 static char ref_head[SIZEOF_REF]        = "HEAD";
1576 struct view {
1577         const char *name;       /* View name */
1578         const char *cmd_env;    /* Command line set via environment */
1579         const char *id;         /* Points to either of ref_{head,commit,blob} */
1581         struct view_ops *ops;   /* View operations */
1583         enum keymap keymap;     /* What keymap does this view have */
1584         bool git_dir;           /* Whether the view requires a git directory. */
1586         char ref[SIZEOF_REF];   /* Hovered commit reference */
1587         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1589         int height, width;      /* The width and height of the main window */
1590         WINDOW *win;            /* The main window */
1591         WINDOW *title;          /* The title window living below the main window */
1593         /* Navigation */
1594         unsigned long offset;   /* Offset of the window top */
1595         unsigned long lineno;   /* Current line number */
1597         /* Searching */
1598         char grep[SIZEOF_STR];  /* Search string */
1599         regex_t *regex;         /* Pre-compiled regex */
1601         /* If non-NULL, points to the view that opened this view. If this view
1602          * is closed tig will switch back to the parent view. */
1603         struct view *parent;
1605         /* Buffering */
1606         size_t lines;           /* Total number of lines */
1607         struct line *line;      /* Line index */
1608         size_t line_alloc;      /* Total number of allocated lines */
1609         size_t line_size;       /* Total number of used lines */
1610         unsigned int digits;    /* Number of digits in the lines member. */
1612         /* Drawing */
1613         struct line *curline;   /* Line currently being drawn. */
1614         enum line_type curtype; /* Attribute currently used for drawing. */
1615         unsigned long col;      /* Column when drawing. */
1617         /* Loading */
1618         struct io io;
1619         struct io *pipe;
1620         time_t start_time;
1621 };
1623 struct view_ops {
1624         /* What type of content being displayed. Used in the title bar. */
1625         const char *type;
1626         /* Default command arguments. */
1627         const char **argv;
1628         /* Open and reads in all view content. */
1629         bool (*open)(struct view *view);
1630         /* Read one line; updates view->line. */
1631         bool (*read)(struct view *view, char *data);
1632         /* Draw one line; @lineno must be < view->height. */
1633         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1634         /* Depending on view handle a special requests. */
1635         enum request (*request)(struct view *view, enum request request, struct line *line);
1636         /* Search for regex in a line. */
1637         bool (*grep)(struct view *view, struct line *line);
1638         /* Select line */
1639         void (*select)(struct view *view, struct line *line);
1640 };
1642 static struct view_ops blame_ops;
1643 static struct view_ops blob_ops;
1644 static struct view_ops diff_ops;
1645 static struct view_ops help_ops;
1646 static struct view_ops log_ops;
1647 static struct view_ops main_ops;
1648 static struct view_ops pager_ops;
1649 static struct view_ops stage_ops;
1650 static struct view_ops status_ops;
1651 static struct view_ops tree_ops;
1653 #define VIEW_STR(name, env, ref, ops, map, git) \
1654         { name, #env, ref, ops, map, git }
1656 #define VIEW_(id, name, ops, git, ref) \
1657         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1660 static struct view views[] = {
1661         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1662         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1663         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1664         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1665         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1666         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1667         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1668         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1669         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1670         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1671 };
1673 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1674 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1676 #define foreach_view(view, i) \
1677         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1679 #define view_is_displayed(view) \
1680         (view == display[0] || view == display[1])
1683 enum line_graphic {
1684         LINE_GRAPHIC_VLINE
1685 };
1687 static int line_graphics[] = {
1688         /* LINE_GRAPHIC_VLINE: */ '|'
1689 };
1691 static inline void
1692 set_view_attr(struct view *view, enum line_type type)
1694         if (!view->curline->selected && view->curtype != type) {
1695                 wattrset(view->win, get_line_attr(type));
1696                 wchgat(view->win, -1, 0, type, NULL);
1697                 view->curtype = type;
1698         }
1701 static int
1702 draw_chars(struct view *view, enum line_type type, const char *string,
1703            int max_len, bool use_tilde)
1705         int len = 0;
1706         int col = 0;
1707         int trimmed = FALSE;
1709         if (max_len <= 0)
1710                 return 0;
1712         if (opt_utf8) {
1713                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1714         } else {
1715                 col = len = strlen(string);
1716                 if (len > max_len) {
1717                         if (use_tilde) {
1718                                 max_len -= 1;
1719                         }
1720                         col = len = max_len;
1721                         trimmed = TRUE;
1722                 }
1723         }
1725         set_view_attr(view, type);
1726         waddnstr(view->win, string, len);
1727         if (trimmed && use_tilde) {
1728                 set_view_attr(view, LINE_DELIMITER);
1729                 waddch(view->win, '~');
1730                 col++;
1731         }
1733         return col;
1736 static int
1737 draw_space(struct view *view, enum line_type type, int max, int spaces)
1739         static char space[] = "                    ";
1740         int col = 0;
1742         spaces = MIN(max, spaces);
1744         while (spaces > 0) {
1745                 int len = MIN(spaces, sizeof(space) - 1);
1747                 col += draw_chars(view, type, space, spaces, FALSE);
1748                 spaces -= len;
1749         }
1751         return col;
1754 static bool
1755 draw_lineno(struct view *view, unsigned int lineno)
1757         char number[10];
1758         int digits3 = view->digits < 3 ? 3 : view->digits;
1759         int max_number = MIN(digits3, STRING_SIZE(number));
1760         int max = view->width - view->col;
1761         int col;
1763         if (max < max_number)
1764                 max_number = max;
1766         lineno += view->offset + 1;
1767         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1768                 static char fmt[] = "%1ld";
1770                 if (view->digits <= 9)
1771                         fmt[1] = '0' + digits3;
1773                 if (!string_format(number, fmt, lineno))
1774                         number[0] = 0;
1775                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1776         } else {
1777                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1778         }
1780         if (col < max) {
1781                 set_view_attr(view, LINE_DEFAULT);
1782                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1783                 col++;
1784         }
1786         if (col < max)
1787                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1788         view->col += col;
1790         return view->width - view->col <= 0;
1793 static bool
1794 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1796         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1797         return view->width - view->col <= 0;
1800 static bool
1801 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1803         int max = view->width - view->col;
1804         int i;
1806         if (max < size)
1807                 size = max;
1809         set_view_attr(view, type);
1810         /* Using waddch() instead of waddnstr() ensures that
1811          * they'll be rendered correctly for the cursor line. */
1812         for (i = 0; i < size; i++)
1813                 waddch(view->win, graphic[i]);
1815         view->col += size;
1816         if (size < max) {
1817                 waddch(view->win, ' ');
1818                 view->col++;
1819         }
1821         return view->width - view->col <= 0;
1824 static bool
1825 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1827         int max = MIN(view->width - view->col, len);
1828         int col;
1830         if (text)
1831                 col = draw_chars(view, type, text, max - 1, trim);
1832         else
1833                 col = draw_space(view, type, max - 1, max - 1);
1835         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1836         return view->width - view->col <= 0;
1839 static bool
1840 draw_date(struct view *view, struct tm *time)
1842         char buf[DATE_COLS];
1843         char *date;
1844         int timelen = 0;
1846         if (time)
1847                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1848         date = timelen ? buf : NULL;
1850         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1853 static bool
1854 draw_view_line(struct view *view, unsigned int lineno)
1856         struct line *line;
1857         bool selected = (view->offset + lineno == view->lineno);
1858         bool draw_ok;
1860         assert(view_is_displayed(view));
1862         if (view->offset + lineno >= view->lines)
1863                 return FALSE;
1865         line = &view->line[view->offset + lineno];
1867         wmove(view->win, lineno, 0);
1868         view->col = 0;
1869         view->curline = line;
1870         view->curtype = LINE_NONE;
1871         line->selected = FALSE;
1873         if (selected) {
1874                 set_view_attr(view, LINE_CURSOR);
1875                 line->selected = TRUE;
1876                 view->ops->select(view, line);
1877         } else if (line->selected) {
1878                 wclrtoeol(view->win);
1879         }
1881         scrollok(view->win, FALSE);
1882         draw_ok = view->ops->draw(view, line, lineno);
1883         scrollok(view->win, TRUE);
1885         return draw_ok;
1888 static void
1889 redraw_view_dirty(struct view *view)
1891         bool dirty = FALSE;
1892         int lineno;
1894         for (lineno = 0; lineno < view->height; lineno++) {
1895                 struct line *line = &view->line[view->offset + lineno];
1897                 if (!line->dirty)
1898                         continue;
1899                 line->dirty = 0;
1900                 dirty = TRUE;
1901                 if (!draw_view_line(view, lineno))
1902                         break;
1903         }
1905         if (!dirty)
1906                 return;
1907         redrawwin(view->win);
1908         if (input_mode)
1909                 wnoutrefresh(view->win);
1910         else
1911                 wrefresh(view->win);
1914 static void
1915 redraw_view_from(struct view *view, int lineno)
1917         assert(0 <= lineno && lineno < view->height);
1919         for (; lineno < view->height; lineno++) {
1920                 if (!draw_view_line(view, lineno))
1921                         break;
1922         }
1924         redrawwin(view->win);
1925         if (input_mode)
1926                 wnoutrefresh(view->win);
1927         else
1928                 wrefresh(view->win);
1931 static void
1932 redraw_view(struct view *view)
1934         wclear(view->win);
1935         redraw_view_from(view, 0);
1939 static void
1940 update_view_title(struct view *view)
1942         char buf[SIZEOF_STR];
1943         char state[SIZEOF_STR];
1944         size_t bufpos = 0, statelen = 0;
1946         assert(view_is_displayed(view));
1948         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1949                 unsigned int view_lines = view->offset + view->height;
1950                 unsigned int lines = view->lines
1951                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1952                                    : 0;
1954                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1955                                    view->ops->type,
1956                                    view->lineno + 1,
1957                                    view->lines,
1958                                    lines);
1960                 if (view->pipe) {
1961                         time_t secs = time(NULL) - view->start_time;
1963                         /* Three git seconds are a long time ... */
1964                         if (secs > 2)
1965                                 string_format_from(state, &statelen, " %lds", secs);
1966                 }
1967         }
1969         string_format_from(buf, &bufpos, "[%s]", view->name);
1970         if (*view->ref && bufpos < view->width) {
1971                 size_t refsize = strlen(view->ref);
1972                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1974                 if (minsize < view->width)
1975                         refsize = view->width - minsize + 7;
1976                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1977         }
1979         if (statelen && bufpos < view->width) {
1980                 string_format_from(buf, &bufpos, " %s", state);
1981         }
1983         if (view == display[current_view])
1984                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1985         else
1986                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1988         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1989         wclrtoeol(view->title);
1990         wmove(view->title, 0, view->width - 1);
1992         if (input_mode)
1993                 wnoutrefresh(view->title);
1994         else
1995                 wrefresh(view->title);
1998 static void
1999 resize_display(void)
2001         int offset, i;
2002         struct view *base = display[0];
2003         struct view *view = display[1] ? display[1] : display[0];
2005         /* Setup window dimensions */
2007         getmaxyx(stdscr, base->height, base->width);
2009         /* Make room for the status window. */
2010         base->height -= 1;
2012         if (view != base) {
2013                 /* Horizontal split. */
2014                 view->width   = base->width;
2015                 view->height  = SCALE_SPLIT_VIEW(base->height);
2016                 base->height -= view->height;
2018                 /* Make room for the title bar. */
2019                 view->height -= 1;
2020         }
2022         /* Make room for the title bar. */
2023         base->height -= 1;
2025         offset = 0;
2027         foreach_displayed_view (view, i) {
2028                 if (!view->win) {
2029                         view->win = newwin(view->height, 0, offset, 0);
2030                         if (!view->win)
2031                                 die("Failed to create %s view", view->name);
2033                         scrollok(view->win, TRUE);
2035                         view->title = newwin(1, 0, offset + view->height, 0);
2036                         if (!view->title)
2037                                 die("Failed to create title window");
2039                 } else {
2040                         wresize(view->win, view->height, view->width);
2041                         mvwin(view->win,   offset, 0);
2042                         mvwin(view->title, offset + view->height, 0);
2043                 }
2045                 offset += view->height + 1;
2046         }
2049 static void
2050 redraw_display(void)
2052         struct view *view;
2053         int i;
2055         foreach_displayed_view (view, i) {
2056                 redraw_view(view);
2057                 update_view_title(view);
2058         }
2061 static void
2062 update_display_cursor(struct view *view)
2064         /* Move the cursor to the right-most column of the cursor line.
2065          *
2066          * XXX: This could turn out to be a bit expensive, but it ensures that
2067          * the cursor does not jump around. */
2068         if (view->lines) {
2069                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2070                 wrefresh(view->win);
2071         }
2074 /*
2075  * Navigation
2076  */
2078 /* Scrolling backend */
2079 static void
2080 do_scroll_view(struct view *view, int lines)
2082         bool redraw_current_line = FALSE;
2084         /* The rendering expects the new offset. */
2085         view->offset += lines;
2087         assert(0 <= view->offset && view->offset < view->lines);
2088         assert(lines);
2090         /* Move current line into the view. */
2091         if (view->lineno < view->offset) {
2092                 view->lineno = view->offset;
2093                 redraw_current_line = TRUE;
2094         } else if (view->lineno >= view->offset + view->height) {
2095                 view->lineno = view->offset + view->height - 1;
2096                 redraw_current_line = TRUE;
2097         }
2099         assert(view->offset <= view->lineno && view->lineno < view->lines);
2101         /* Redraw the whole screen if scrolling is pointless. */
2102         if (view->height < ABS(lines)) {
2103                 redraw_view(view);
2105         } else {
2106                 int line = lines > 0 ? view->height - lines : 0;
2107                 int end = line + ABS(lines);
2109                 wscrl(view->win, lines);
2111                 for (; line < end; line++) {
2112                         if (!draw_view_line(view, line))
2113                                 break;
2114                 }
2116                 if (redraw_current_line)
2117                         draw_view_line(view, view->lineno - view->offset);
2118         }
2120         redrawwin(view->win);
2121         wrefresh(view->win);
2122         report("");
2125 /* Scroll frontend */
2126 static void
2127 scroll_view(struct view *view, enum request request)
2129         int lines = 1;
2131         assert(view_is_displayed(view));
2133         switch (request) {
2134         case REQ_SCROLL_PAGE_DOWN:
2135                 lines = view->height;
2136         case REQ_SCROLL_LINE_DOWN:
2137                 if (view->offset + lines > view->lines)
2138                         lines = view->lines - view->offset;
2140                 if (lines == 0 || view->offset + view->height >= view->lines) {
2141                         report("Cannot scroll beyond the last line");
2142                         return;
2143                 }
2144                 break;
2146         case REQ_SCROLL_PAGE_UP:
2147                 lines = view->height;
2148         case REQ_SCROLL_LINE_UP:
2149                 if (lines > view->offset)
2150                         lines = view->offset;
2152                 if (lines == 0) {
2153                         report("Cannot scroll beyond the first line");
2154                         return;
2155                 }
2157                 lines = -lines;
2158                 break;
2160         default:
2161                 die("request %d not handled in switch", request);
2162         }
2164         do_scroll_view(view, lines);
2167 /* Cursor moving */
2168 static void
2169 move_view(struct view *view, enum request request)
2171         int scroll_steps = 0;
2172         int steps;
2174         switch (request) {
2175         case REQ_MOVE_FIRST_LINE:
2176                 steps = -view->lineno;
2177                 break;
2179         case REQ_MOVE_LAST_LINE:
2180                 steps = view->lines - view->lineno - 1;
2181                 break;
2183         case REQ_MOVE_PAGE_UP:
2184                 steps = view->height > view->lineno
2185                       ? -view->lineno : -view->height;
2186                 break;
2188         case REQ_MOVE_PAGE_DOWN:
2189                 steps = view->lineno + view->height >= view->lines
2190                       ? view->lines - view->lineno - 1 : view->height;
2191                 break;
2193         case REQ_MOVE_UP:
2194                 steps = -1;
2195                 break;
2197         case REQ_MOVE_DOWN:
2198                 steps = 1;
2199                 break;
2201         default:
2202                 die("request %d not handled in switch", request);
2203         }
2205         if (steps <= 0 && view->lineno == 0) {
2206                 report("Cannot move beyond the first line");
2207                 return;
2209         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2210                 report("Cannot move beyond the last line");
2211                 return;
2212         }
2214         /* Move the current line */
2215         view->lineno += steps;
2216         assert(0 <= view->lineno && view->lineno < view->lines);
2218         /* Check whether the view needs to be scrolled */
2219         if (view->lineno < view->offset ||
2220             view->lineno >= view->offset + view->height) {
2221                 scroll_steps = steps;
2222                 if (steps < 0 && -steps > view->offset) {
2223                         scroll_steps = -view->offset;
2225                 } else if (steps > 0) {
2226                         if (view->lineno == view->lines - 1 &&
2227                             view->lines > view->height) {
2228                                 scroll_steps = view->lines - view->offset - 1;
2229                                 if (scroll_steps >= view->height)
2230                                         scroll_steps -= view->height - 1;
2231                         }
2232                 }
2233         }
2235         if (!view_is_displayed(view)) {
2236                 view->offset += scroll_steps;
2237                 assert(0 <= view->offset && view->offset < view->lines);
2238                 view->ops->select(view, &view->line[view->lineno]);
2239                 return;
2240         }
2242         /* Repaint the old "current" line if we be scrolling */
2243         if (ABS(steps) < view->height)
2244                 draw_view_line(view, view->lineno - steps - view->offset);
2246         if (scroll_steps) {
2247                 do_scroll_view(view, scroll_steps);
2248                 return;
2249         }
2251         /* Draw the current line */
2252         draw_view_line(view, view->lineno - view->offset);
2254         redrawwin(view->win);
2255         wrefresh(view->win);
2256         report("");
2260 /*
2261  * Searching
2262  */
2264 static void search_view(struct view *view, enum request request);
2266 static bool
2267 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2269         assert(view_is_displayed(view));
2271         if (!view->ops->grep(view, line))
2272                 return FALSE;
2274         if (lineno - view->offset >= view->height) {
2275                 view->offset = lineno;
2276                 view->lineno = lineno;
2277                 redraw_view(view);
2279         } else {
2280                 unsigned long old_lineno = view->lineno - view->offset;
2282                 view->lineno = lineno;
2283                 draw_view_line(view, old_lineno);
2285                 draw_view_line(view, view->lineno - view->offset);
2286                 redrawwin(view->win);
2287                 wrefresh(view->win);
2288         }
2290         report("Line %ld matches '%s'", lineno + 1, view->grep);
2291         return TRUE;
2294 static void
2295 find_next(struct view *view, enum request request)
2297         unsigned long lineno = view->lineno;
2298         int direction;
2300         if (!*view->grep) {
2301                 if (!*opt_search)
2302                         report("No previous search");
2303                 else
2304                         search_view(view, request);
2305                 return;
2306         }
2308         switch (request) {
2309         case REQ_SEARCH:
2310         case REQ_FIND_NEXT:
2311                 direction = 1;
2312                 break;
2314         case REQ_SEARCH_BACK:
2315         case REQ_FIND_PREV:
2316                 direction = -1;
2317                 break;
2319         default:
2320                 return;
2321         }
2323         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2324                 lineno += direction;
2326         /* Note, lineno is unsigned long so will wrap around in which case it
2327          * will become bigger than view->lines. */
2328         for (; lineno < view->lines; lineno += direction) {
2329                 struct line *line = &view->line[lineno];
2331                 if (find_next_line(view, lineno, line))
2332                         return;
2333         }
2335         report("No match found for '%s'", view->grep);
2338 static void
2339 search_view(struct view *view, enum request request)
2341         int regex_err;
2343         if (view->regex) {
2344                 regfree(view->regex);
2345                 *view->grep = 0;
2346         } else {
2347                 view->regex = calloc(1, sizeof(*view->regex));
2348                 if (!view->regex)
2349                         return;
2350         }
2352         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2353         if (regex_err != 0) {
2354                 char buf[SIZEOF_STR] = "unknown error";
2356                 regerror(regex_err, view->regex, buf, sizeof(buf));
2357                 report("Search failed: %s", buf);
2358                 return;
2359         }
2361         string_copy(view->grep, opt_search);
2363         find_next(view, request);
2366 /*
2367  * Incremental updating
2368  */
2370 static void
2371 reset_view(struct view *view)
2373         int i;
2375         for (i = 0; i < view->lines; i++)
2376                 free(view->line[i].data);
2377         free(view->line);
2379         view->line = NULL;
2380         view->offset = 0;
2381         view->lines  = 0;
2382         view->lineno = 0;
2383         view->line_size = 0;
2384         view->line_alloc = 0;
2385         view->vid[0] = 0;
2388 static void
2389 free_argv(const char *argv[])
2391         int argc;
2393         for (argc = 0; argv[argc]; argc++)
2394                 free((void *) argv[argc]);
2397 static bool
2398 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2400         char buf[SIZEOF_STR];
2401         int argc;
2402         bool noreplace = flags == FORMAT_NONE;
2404         free_argv(dst_argv);
2406         for (argc = 0; src_argv[argc]; argc++) {
2407                 const char *arg = src_argv[argc];
2408                 size_t bufpos = 0;
2410                 while (arg) {
2411                         char *next = strstr(arg, "%(");
2412                         int len = next - arg;
2413                         const char *value;
2415                         if (!next || noreplace) {
2416                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2417                                         noreplace = TRUE;
2418                                 len = strlen(arg);
2419                                 value = "";
2421                         } else if (!prefixcmp(next, "%(directory)")) {
2422                                 value = opt_path;
2424                         } else if (!prefixcmp(next, "%(file)")) {
2425                                 value = opt_file;
2427                         } else if (!prefixcmp(next, "%(ref)")) {
2428                                 value = *opt_ref ? opt_ref : "HEAD";
2430                         } else if (!prefixcmp(next, "%(head)")) {
2431                                 value = ref_head;
2433                         } else if (!prefixcmp(next, "%(commit)")) {
2434                                 value = ref_commit;
2436                         } else if (!prefixcmp(next, "%(blob)")) {
2437                                 value = ref_blob;
2439                         } else {
2440                                 report("Unknown replacement: `%s`", next);
2441                                 return FALSE;
2442                         }
2444                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2445                                 return FALSE;
2447                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2448                 }
2450                 dst_argv[argc] = strdup(buf);
2451                 if (!dst_argv[argc])
2452                         break;
2453         }
2455         dst_argv[argc] = NULL;
2457         return src_argv[argc] == NULL;
2460 static bool
2461 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2463         const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2464         int bufsize = 0;
2465         int argc;
2467         if (!format_argv(dst_argv, src_argv, flags)) {
2468                 free_argv(dst_argv);
2469                 return FALSE;
2470         }
2472         for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2473                 if (bufsize > 0)
2474                         dst[bufsize++] = ' ';
2475                 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2476         }
2478         if (bufsize < SIZEOF_STR)
2479                 dst[bufsize] = 0;
2480         free_argv(dst_argv);
2482         return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2485 static void
2486 end_update(struct view *view, bool force)
2488         if (!view->pipe)
2489                 return;
2490         while (!view->ops->read(view, NULL))
2491                 if (!force)
2492                         return;
2493         set_nonblocking_input(FALSE);
2494         done_io(view->pipe);
2495         view->pipe = NULL;
2498 static void
2499 setup_update(struct view *view, const char *vid)
2501         set_nonblocking_input(TRUE);
2502         reset_view(view);
2503         string_copy_rev(view->vid, vid);
2504         view->pipe = &view->io;
2505         view->start_time = time(NULL);
2508 static bool
2509 prepare_update(struct view *view, const char *argv[], const char *dir,
2510                enum format_flags flags)
2512         if (view->pipe)
2513                 end_update(view, TRUE);
2514         return init_io_rd(&view->io, argv, dir, flags);
2517 static bool
2518 begin_update(struct view *view, bool refresh)
2520         if (init_io_fd(&view->io, opt_pipe)) {
2521                 opt_pipe = NULL;
2523         } else if (opt_cmd[0]) {
2524                 if (!run_io(&view->io, IO_RD, opt_cmd))
2525                         return FALSE;
2526                 view->ref[0] = 0;
2527                 opt_cmd[0] = 0;
2529         } else if (refresh) {
2530                 if (!start_io(&view->io))
2531                         return FALSE;
2533         } else {
2534                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2535                         opt_path[0] = 0;
2537                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2538                         return FALSE;
2540                 /* Put the current ref_* value to the view title ref
2541                  * member. This is needed by the blob view. Most other
2542                  * views sets it automatically after loading because the
2543                  * first line is a commit line. */
2544                 string_copy_rev(view->ref, view->id);
2545         }
2547         setup_update(view, view->id);
2549         return TRUE;
2552 #define ITEM_CHUNK_SIZE 256
2553 static void *
2554 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2556         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2557         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2559         if (mem == NULL || num_chunks != num_chunks_new) {
2560                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2561                 mem = realloc(mem, *size * item_size);
2562         }
2564         return mem;
2567 static struct line *
2568 realloc_lines(struct view *view, size_t line_size)
2570         size_t alloc = view->line_alloc;
2571         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2572                                          sizeof(*view->line));
2574         if (!tmp)
2575                 return NULL;
2577         view->line = tmp;
2578         view->line_alloc = alloc;
2579         view->line_size = line_size;
2580         return view->line;
2583 static bool
2584 update_view(struct view *view)
2586         char out_buffer[BUFSIZ * 2];
2587         char *line;
2588         /* The number of lines to read. If too low it will cause too much
2589          * redrawing (and possible flickering), if too high responsiveness
2590          * will suffer. */
2591         unsigned long lines = view->height;
2592         int redraw_from = -1;
2594         if (!view->pipe)
2595                 return TRUE;
2597         /* Only redraw if lines are visible. */
2598         if (view->offset + view->height >= view->lines)
2599                 redraw_from = view->lines - view->offset;
2601         /* FIXME: This is probably not perfect for backgrounded views. */
2602         if (!realloc_lines(view, view->lines + lines))
2603                 goto alloc_error;
2605         while ((line = io_gets(view->pipe))) {
2606                 size_t linelen = strlen(line);
2608                 if (linelen)
2609                         line[linelen - 1] = 0;
2611                 if (opt_iconv != ICONV_NONE) {
2612                         ICONV_CONST char *inbuf = line;
2613                         size_t inlen = linelen;
2615                         char *outbuf = out_buffer;
2616                         size_t outlen = sizeof(out_buffer);
2618                         size_t ret;
2620                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2621                         if (ret != (size_t) -1) {
2622                                 line = out_buffer;
2623                                 linelen = strlen(out_buffer);
2624                         }
2625                 }
2627                 if (!view->ops->read(view, line))
2628                         goto alloc_error;
2630                 if (lines-- == 1)
2631                         break;
2632         }
2634         {
2635                 int digits;
2637                 lines = view->lines;
2638                 for (digits = 0; lines; digits++)
2639                         lines /= 10;
2641                 /* Keep the displayed view in sync with line number scaling. */
2642                 if (digits != view->digits) {
2643                         view->digits = digits;
2644                         redraw_from = 0;
2645                 }
2646         }
2648         if (io_error(view->pipe)) {
2649                 report("Failed to read: %s", io_strerror(view->pipe));
2650                 end_update(view, TRUE);
2652         } else if (io_eof(view->pipe)) {
2653                 report("");
2654                 end_update(view, FALSE);
2655         }
2657         if (!view_is_displayed(view))
2658                 return TRUE;
2660         if (view == VIEW(REQ_VIEW_TREE)) {
2661                 /* Clear the view and redraw everything since the tree sorting
2662                  * might have rearranged things. */
2663                 redraw_view(view);
2665         } else if (redraw_from >= 0) {
2666                 /* If this is an incremental update, redraw the previous line
2667                  * since for commits some members could have changed when
2668                  * loading the main view. */
2669                 if (redraw_from > 0)
2670                         redraw_from--;
2672                 /* Since revision graph visualization requires knowledge
2673                  * about the parent commit, it causes a further one-off
2674                  * needed to be redrawn for incremental updates. */
2675                 if (redraw_from > 0 && opt_rev_graph)
2676                         redraw_from--;
2678                 /* Incrementally draw avoids flickering. */
2679                 redraw_view_from(view, redraw_from);
2680         }
2682         if (view == VIEW(REQ_VIEW_BLAME))
2683                 redraw_view_dirty(view);
2685         /* Update the title _after_ the redraw so that if the redraw picks up a
2686          * commit reference in view->ref it'll be available here. */
2687         update_view_title(view);
2688         return TRUE;
2690 alloc_error:
2691         report("Allocation failure");
2692         end_update(view, TRUE);
2693         return FALSE;
2696 static struct line *
2697 add_line_data(struct view *view, void *data, enum line_type type)
2699         struct line *line = &view->line[view->lines++];
2701         memset(line, 0, sizeof(*line));
2702         line->type = type;
2703         line->data = data;
2705         return line;
2708 static struct line *
2709 add_line_text(struct view *view, const char *text, enum line_type type)
2711         char *data = text ? strdup(text) : NULL;
2713         return data ? add_line_data(view, data, type) : NULL;
2717 /*
2718  * View opening
2719  */
2721 enum open_flags {
2722         OPEN_DEFAULT = 0,       /* Use default view switching. */
2723         OPEN_SPLIT = 1,         /* Split current view. */
2724         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2725         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2726         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2727         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2728         OPEN_PREPARED = 32,     /* Open already prepared command. */
2729 };
2731 static void
2732 open_view(struct view *prev, enum request request, enum open_flags flags)
2734         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2735         bool split = !!(flags & OPEN_SPLIT);
2736         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2737         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2738         struct view *view = VIEW(request);
2739         int nviews = displayed_views();
2740         struct view *base_view = display[0];
2742         if (view == prev && nviews == 1 && !reload) {
2743                 report("Already in %s view", view->name);
2744                 return;
2745         }
2747         if (view->git_dir && !opt_git_dir[0]) {
2748                 report("The %s view is disabled in pager view", view->name);
2749                 return;
2750         }
2752         if (split) {
2753                 display[1] = view;
2754                 if (!backgrounded)
2755                         current_view = 1;
2756         } else if (!nomaximize) {
2757                 /* Maximize the current view. */
2758                 memset(display, 0, sizeof(display));
2759                 current_view = 0;
2760                 display[current_view] = view;
2761         }
2763         /* Resize the view when switching between split- and full-screen,
2764          * or when switching between two different full-screen views. */
2765         if (nviews != displayed_views() ||
2766             (nviews == 1 && base_view != display[0]))
2767                 resize_display();
2769         if (view->pipe)
2770                 end_update(view, TRUE);
2772         if (view->ops->open) {
2773                 if (!view->ops->open(view)) {
2774                         report("Failed to load %s view", view->name);
2775                         return;
2776                 }
2778         } else if ((reload || strcmp(view->vid, view->id)) &&
2779                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2780                 report("Failed to load %s view", view->name);
2781                 return;
2782         }
2784         if (split && prev->lineno - prev->offset >= prev->height) {
2785                 /* Take the title line into account. */
2786                 int lines = prev->lineno - prev->offset - prev->height + 1;
2788                 /* Scroll the view that was split if the current line is
2789                  * outside the new limited view. */
2790                 do_scroll_view(prev, lines);
2791         }
2793         if (prev && view != prev) {
2794                 if (split && !backgrounded) {
2795                         /* "Blur" the previous view. */
2796                         update_view_title(prev);
2797                 }
2799                 view->parent = prev;
2800         }
2802         if (view->pipe && view->lines == 0) {
2803                 /* Clear the old view and let the incremental updating refill
2804                  * the screen. */
2805                 werase(view->win);
2806                 report("");
2807         } else if (view_is_displayed(view)) {
2808                 redraw_view(view);
2809                 report("");
2810         }
2812         /* If the view is backgrounded the above calls to report()
2813          * won't redraw the view title. */
2814         if (backgrounded)
2815                 update_view_title(view);
2818 static void
2819 open_external_viewer(const char *argv[], const char *dir)
2821         def_prog_mode();           /* save current tty modes */
2822         endwin();                  /* restore original tty modes */
2823         run_io_fg(argv, dir);
2824         fprintf(stderr, "Press Enter to continue");
2825         getc(opt_tty);
2826         reset_prog_mode();
2827         redraw_display();
2830 static void
2831 open_mergetool(const char *file)
2833         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2835         open_external_viewer(mergetool_argv, NULL);
2838 static void
2839 open_editor(bool from_root, const char *file)
2841         const char *editor_argv[] = { "vi", file, NULL };
2842         const char *editor;
2844         editor = getenv("GIT_EDITOR");
2845         if (!editor && *opt_editor)
2846                 editor = opt_editor;
2847         if (!editor)
2848                 editor = getenv("VISUAL");
2849         if (!editor)
2850                 editor = getenv("EDITOR");
2851         if (!editor)
2852                 editor = "vi";
2854         editor_argv[0] = editor;
2855         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2858 static void
2859 open_run_request(enum request request)
2861         struct run_request *req = get_run_request(request);
2862         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2864         if (!req) {
2865                 report("Unknown run request");
2866                 return;
2867         }
2869         if (format_argv(argv, req->argv, FORMAT_ALL))
2870                 open_external_viewer(argv, NULL);
2871         free_argv(argv);
2874 /*
2875  * User request switch noodle
2876  */
2878 static int
2879 view_driver(struct view *view, enum request request)
2881         int i;
2883         if (request == REQ_NONE) {
2884                 doupdate();
2885                 return TRUE;
2886         }
2888         if (request > REQ_NONE) {
2889                 open_run_request(request);
2890                 /* FIXME: When all views can refresh always do this. */
2891                 if (view == VIEW(REQ_VIEW_STATUS) ||
2892                     view == VIEW(REQ_VIEW_MAIN) ||
2893                     view == VIEW(REQ_VIEW_LOG) ||
2894                     view == VIEW(REQ_VIEW_STAGE))
2895                         request = REQ_REFRESH;
2896                 else
2897                         return TRUE;
2898         }
2900         if (view && view->lines) {
2901                 request = view->ops->request(view, request, &view->line[view->lineno]);
2902                 if (request == REQ_NONE)
2903                         return TRUE;
2904         }
2906         switch (request) {
2907         case REQ_MOVE_UP:
2908         case REQ_MOVE_DOWN:
2909         case REQ_MOVE_PAGE_UP:
2910         case REQ_MOVE_PAGE_DOWN:
2911         case REQ_MOVE_FIRST_LINE:
2912         case REQ_MOVE_LAST_LINE:
2913                 move_view(view, request);
2914                 break;
2916         case REQ_SCROLL_LINE_DOWN:
2917         case REQ_SCROLL_LINE_UP:
2918         case REQ_SCROLL_PAGE_DOWN:
2919         case REQ_SCROLL_PAGE_UP:
2920                 scroll_view(view, request);
2921                 break;
2923         case REQ_VIEW_BLAME:
2924                 if (!opt_file[0]) {
2925                         report("No file chosen, press %s to open tree view",
2926                                get_key(REQ_VIEW_TREE));
2927                         break;
2928                 }
2929                 open_view(view, request, OPEN_DEFAULT);
2930                 break;
2932         case REQ_VIEW_BLOB:
2933                 if (!ref_blob[0]) {
2934                         report("No file chosen, press %s to open tree view",
2935                                get_key(REQ_VIEW_TREE));
2936                         break;
2937                 }
2938                 open_view(view, request, OPEN_DEFAULT);
2939                 break;
2941         case REQ_VIEW_PAGER:
2942                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2943                         report("No pager content, press %s to run command from prompt",
2944                                get_key(REQ_PROMPT));
2945                         break;
2946                 }
2947                 open_view(view, request, OPEN_DEFAULT);
2948                 break;
2950         case REQ_VIEW_STAGE:
2951                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2952                         report("No stage content, press %s to open the status view and choose file",
2953                                get_key(REQ_VIEW_STATUS));
2954                         break;
2955                 }
2956                 open_view(view, request, OPEN_DEFAULT);
2957                 break;
2959         case REQ_VIEW_STATUS:
2960                 if (opt_is_inside_work_tree == FALSE) {
2961                         report("The status view requires a working tree");
2962                         break;
2963                 }
2964                 open_view(view, request, OPEN_DEFAULT);
2965                 break;
2967         case REQ_VIEW_MAIN:
2968         case REQ_VIEW_DIFF:
2969         case REQ_VIEW_LOG:
2970         case REQ_VIEW_TREE:
2971         case REQ_VIEW_HELP:
2972                 open_view(view, request, OPEN_DEFAULT);
2973                 break;
2975         case REQ_NEXT:
2976         case REQ_PREVIOUS:
2977                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2979                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2980                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2981                    (view == VIEW(REQ_VIEW_DIFF) &&
2982                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2983                    (view == VIEW(REQ_VIEW_STAGE) &&
2984                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2985                    (view == VIEW(REQ_VIEW_BLOB) &&
2986                      view->parent == VIEW(REQ_VIEW_TREE))) {
2987                         int line;
2989                         view = view->parent;
2990                         line = view->lineno;
2991                         move_view(view, request);
2992                         if (view_is_displayed(view))
2993                                 update_view_title(view);
2994                         if (line != view->lineno)
2995                                 view->ops->request(view, REQ_ENTER,
2996                                                    &view->line[view->lineno]);
2998                 } else {
2999                         move_view(view, request);
3000                 }
3001                 break;
3003         case REQ_VIEW_NEXT:
3004         {
3005                 int nviews = displayed_views();
3006                 int next_view = (current_view + 1) % nviews;
3008                 if (next_view == current_view) {
3009                         report("Only one view is displayed");
3010                         break;
3011                 }
3013                 current_view = next_view;
3014                 /* Blur out the title of the previous view. */
3015                 update_view_title(view);
3016                 report("");
3017                 break;
3018         }
3019         case REQ_REFRESH:
3020                 report("Refreshing is not yet supported for the %s view", view->name);
3021                 break;
3023         case REQ_MAXIMIZE:
3024                 if (displayed_views() == 2)
3025                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3026                 break;
3028         case REQ_TOGGLE_LINENO:
3029                 opt_line_number = !opt_line_number;
3030                 redraw_display();
3031                 break;
3033         case REQ_TOGGLE_DATE:
3034                 opt_date = !opt_date;
3035                 redraw_display();
3036                 break;
3038         case REQ_TOGGLE_AUTHOR:
3039                 opt_author = !opt_author;
3040                 redraw_display();
3041                 break;
3043         case REQ_TOGGLE_REV_GRAPH:
3044                 opt_rev_graph = !opt_rev_graph;
3045                 redraw_display();
3046                 break;
3048         case REQ_TOGGLE_REFS:
3049                 opt_show_refs = !opt_show_refs;
3050                 redraw_display();
3051                 break;
3053         case REQ_SEARCH:
3054         case REQ_SEARCH_BACK:
3055                 search_view(view, request);
3056                 break;
3058         case REQ_FIND_NEXT:
3059         case REQ_FIND_PREV:
3060                 find_next(view, request);
3061                 break;
3063         case REQ_STOP_LOADING:
3064                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3065                         view = &views[i];
3066                         if (view->pipe)
3067                                 report("Stopped loading the %s view", view->name),
3068                         end_update(view, TRUE);
3069                 }
3070                 break;
3072         case REQ_SHOW_VERSION:
3073                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3074                 return TRUE;
3076         case REQ_SCREEN_RESIZE:
3077                 resize_display();
3078                 /* Fall-through */
3079         case REQ_SCREEN_REDRAW:
3080                 redraw_display();
3081                 break;
3083         case REQ_EDIT:
3084                 report("Nothing to edit");
3085                 break;
3087         case REQ_ENTER:
3088                 report("Nothing to enter");
3089                 break;
3091         case REQ_VIEW_CLOSE:
3092                 /* XXX: Mark closed views by letting view->parent point to the
3093                  * view itself. Parents to closed view should never be
3094                  * followed. */
3095                 if (view->parent &&
3096                     view->parent->parent != view->parent) {
3097                         memset(display, 0, sizeof(display));
3098                         current_view = 0;
3099                         display[current_view] = view->parent;
3100                         view->parent = view;
3101                         resize_display();
3102                         redraw_display();
3103                         report("");
3104                         break;
3105                 }
3106                 /* Fall-through */
3107         case REQ_QUIT:
3108                 return FALSE;
3110         default:
3111                 report("Unknown key, press 'h' for help");
3112                 return TRUE;
3113         }
3115         return TRUE;
3119 /*
3120  * Pager backend
3121  */
3123 static bool
3124 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3126         char *text = line->data;
3128         if (opt_line_number && draw_lineno(view, lineno))
3129                 return TRUE;
3131         draw_text(view, line->type, text, TRUE);
3132         return TRUE;
3135 static bool
3136 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3138         char refbuf[SIZEOF_STR];
3139         char *ref = NULL;
3140         FILE *pipe;
3142         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3143                 return TRUE;
3145         pipe = popen(refbuf, "r");
3146         if (!pipe)
3147                 return TRUE;
3149         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3150                 ref = chomp_string(ref);
3151         pclose(pipe);
3153         if (!ref || !*ref)
3154                 return TRUE;
3156         /* This is the only fatal call, since it can "corrupt" the buffer. */
3157         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3158                 return FALSE;
3160         return TRUE;
3163 static void
3164 add_pager_refs(struct view *view, struct line *line)
3166         char buf[SIZEOF_STR];
3167         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3168         struct ref **refs;
3169         size_t bufpos = 0, refpos = 0;
3170         const char *sep = "Refs: ";
3171         bool is_tag = FALSE;
3173         assert(line->type == LINE_COMMIT);
3175         refs = get_refs(commit_id);
3176         if (!refs) {
3177                 if (view == VIEW(REQ_VIEW_DIFF))
3178                         goto try_add_describe_ref;
3179                 return;
3180         }
3182         do {
3183                 struct ref *ref = refs[refpos];
3184                 const char *fmt = ref->tag    ? "%s[%s]" :
3185                                   ref->remote ? "%s<%s>" : "%s%s";
3187                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3188                         return;
3189                 sep = ", ";
3190                 if (ref->tag)
3191                         is_tag = TRUE;
3192         } while (refs[refpos++]->next);
3194         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3195 try_add_describe_ref:
3196                 /* Add <tag>-g<commit_id> "fake" reference. */
3197                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3198                         return;
3199         }
3201         if (bufpos == 0)
3202                 return;
3204         if (!realloc_lines(view, view->line_size + 1))
3205                 return;
3207         add_line_text(view, buf, LINE_PP_REFS);
3210 static bool
3211 pager_read(struct view *view, char *data)
3213         struct line *line;
3215         if (!data)
3216                 return TRUE;
3218         line = add_line_text(view, data, get_line_type(data));
3219         if (!line)
3220                 return FALSE;
3222         if (line->type == LINE_COMMIT &&
3223             (view == VIEW(REQ_VIEW_DIFF) ||
3224              view == VIEW(REQ_VIEW_LOG)))
3225                 add_pager_refs(view, line);
3227         return TRUE;
3230 static enum request
3231 pager_request(struct view *view, enum request request, struct line *line)
3233         int split = 0;
3235         if (request != REQ_ENTER)
3236                 return request;
3238         if (line->type == LINE_COMMIT &&
3239            (view == VIEW(REQ_VIEW_LOG) ||
3240             view == VIEW(REQ_VIEW_PAGER))) {
3241                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3242                 split = 1;
3243         }
3245         /* Always scroll the view even if it was split. That way
3246          * you can use Enter to scroll through the log view and
3247          * split open each commit diff. */
3248         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3250         /* FIXME: A minor workaround. Scrolling the view will call report("")
3251          * but if we are scrolling a non-current view this won't properly
3252          * update the view title. */
3253         if (split)
3254                 update_view_title(view);
3256         return REQ_NONE;
3259 static bool
3260 pager_grep(struct view *view, struct line *line)
3262         regmatch_t pmatch;
3263         char *text = line->data;
3265         if (!*text)
3266                 return FALSE;
3268         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3269                 return FALSE;
3271         return TRUE;
3274 static void
3275 pager_select(struct view *view, struct line *line)
3277         if (line->type == LINE_COMMIT) {
3278                 char *text = (char *)line->data + STRING_SIZE("commit ");
3280                 if (view != VIEW(REQ_VIEW_PAGER))
3281                         string_copy_rev(view->ref, text);
3282                 string_copy_rev(ref_commit, text);
3283         }
3286 static struct view_ops pager_ops = {
3287         "line",
3288         NULL,
3289         NULL,
3290         pager_read,
3291         pager_draw,
3292         pager_request,
3293         pager_grep,
3294         pager_select,
3295 };
3297 static const char *log_argv[SIZEOF_ARG] = {
3298         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3299 };
3301 static enum request
3302 log_request(struct view *view, enum request request, struct line *line)
3304         switch (request) {
3305         case REQ_REFRESH:
3306                 load_refs();
3307                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3308                 return REQ_NONE;
3309         default:
3310                 return pager_request(view, request, line);
3311         }
3314 static struct view_ops log_ops = {
3315         "line",
3316         log_argv,
3317         NULL,
3318         pager_read,
3319         pager_draw,
3320         log_request,
3321         pager_grep,
3322         pager_select,
3323 };
3325 static const char *diff_argv[SIZEOF_ARG] = {
3326         "git", "show", "--pretty=fuller", "--no-color", "--root",
3327                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3328 };
3330 static struct view_ops diff_ops = {
3331         "line",
3332         diff_argv,
3333         NULL,
3334         pager_read,
3335         pager_draw,
3336         pager_request,
3337         pager_grep,
3338         pager_select,
3339 };
3341 /*
3342  * Help backend
3343  */
3345 static bool
3346 help_open(struct view *view)
3348         char buf[BUFSIZ];
3349         int lines = ARRAY_SIZE(req_info) + 2;
3350         int i;
3352         if (view->lines > 0)
3353                 return TRUE;
3355         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3356                 if (!req_info[i].request)
3357                         lines++;
3359         lines += run_requests + 1;
3361         view->line = calloc(lines, sizeof(*view->line));
3362         if (!view->line)
3363                 return FALSE;
3365         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3367         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3368                 const char *key;
3370                 if (req_info[i].request == REQ_NONE)
3371                         continue;
3373                 if (!req_info[i].request) {
3374                         add_line_text(view, "", LINE_DEFAULT);
3375                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3376                         continue;
3377                 }
3379                 key = get_key(req_info[i].request);
3380                 if (!*key)
3381                         key = "(no key defined)";
3383                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3384                         continue;
3386                 add_line_text(view, buf, LINE_DEFAULT);
3387         }
3389         if (run_requests) {
3390                 add_line_text(view, "", LINE_DEFAULT);
3391                 add_line_text(view, "External commands:", LINE_DEFAULT);
3392         }
3394         for (i = 0; i < run_requests; i++) {
3395                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3396                 const char *key;
3397                 char cmd[SIZEOF_STR];
3398                 size_t bufpos;
3399                 int argc;
3401                 if (!req)
3402                         continue;
3404                 key = get_key_name(req->key);
3405                 if (!*key)
3406                         key = "(no key defined)";
3408                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3409                         if (!string_format_from(cmd, &bufpos, "%s%s",
3410                                                 argc ? " " : "", req->argv[argc]))
3411                                 return REQ_NONE;
3413                 if (!string_format(buf, "    %-10s %-14s `%s`",
3414                                    keymap_table[req->keymap].name, key, cmd))
3415                         continue;
3417                 add_line_text(view, buf, LINE_DEFAULT);
3418         }
3420         return TRUE;
3423 static struct view_ops help_ops = {
3424         "line",
3425         NULL,
3426         help_open,
3427         NULL,
3428         pager_draw,
3429         pager_request,
3430         pager_grep,
3431         pager_select,
3432 };
3435 /*
3436  * Tree backend
3437  */
3439 struct tree_stack_entry {
3440         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3441         unsigned long lineno;           /* Line number to restore */
3442         char *name;                     /* Position of name in opt_path */
3443 };
3445 /* The top of the path stack. */
3446 static struct tree_stack_entry *tree_stack = NULL;
3447 unsigned long tree_lineno = 0;
3449 static void
3450 pop_tree_stack_entry(void)
3452         struct tree_stack_entry *entry = tree_stack;
3454         tree_lineno = entry->lineno;
3455         entry->name[0] = 0;
3456         tree_stack = entry->prev;
3457         free(entry);
3460 static void
3461 push_tree_stack_entry(const char *name, unsigned long lineno)
3463         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3464         size_t pathlen = strlen(opt_path);
3466         if (!entry)
3467                 return;
3469         entry->prev = tree_stack;
3470         entry->name = opt_path + pathlen;
3471         tree_stack = entry;
3473         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3474                 pop_tree_stack_entry();
3475                 return;
3476         }
3478         /* Move the current line to the first tree entry. */
3479         tree_lineno = 1;
3480         entry->lineno = lineno;
3483 /* Parse output from git-ls-tree(1):
3484  *
3485  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3486  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3487  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3488  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3489  */
3491 #define SIZEOF_TREE_ATTR \
3492         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3494 #define TREE_UP_FORMAT "040000 tree %s\t.."
3496 static int
3497 tree_compare_entry(enum line_type type1, const char *name1,
3498                    enum line_type type2, const char *name2)
3500         if (type1 != type2) {
3501                 if (type1 == LINE_TREE_DIR)
3502                         return -1;
3503                 return 1;
3504         }
3506         return strcmp(name1, name2);
3509 static const char *
3510 tree_path(struct line *line)
3512         const char *path = line->data;
3514         return path + SIZEOF_TREE_ATTR;
3517 static bool
3518 tree_read(struct view *view, char *text)
3520         size_t textlen = text ? strlen(text) : 0;
3521         char buf[SIZEOF_STR];
3522         unsigned long pos;
3523         enum line_type type;
3524         bool first_read = view->lines == 0;
3526         if (!text)
3527                 return TRUE;
3528         if (textlen <= SIZEOF_TREE_ATTR)
3529                 return FALSE;
3531         type = text[STRING_SIZE("100644 ")] == 't'
3532              ? LINE_TREE_DIR : LINE_TREE_FILE;
3534         if (first_read) {
3535                 /* Add path info line */
3536                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3537                     !realloc_lines(view, view->line_size + 1) ||
3538                     !add_line_text(view, buf, LINE_DEFAULT))
3539                         return FALSE;
3541                 /* Insert "link" to parent directory. */
3542                 if (*opt_path) {
3543                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3544                             !realloc_lines(view, view->line_size + 1) ||
3545                             !add_line_text(view, buf, LINE_TREE_DIR))
3546                                 return FALSE;
3547                 }
3548         }
3550         /* Strip the path part ... */
3551         if (*opt_path) {
3552                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3553                 size_t striplen = strlen(opt_path);
3554                 char *path = text + SIZEOF_TREE_ATTR;
3556                 if (pathlen > striplen)
3557                         memmove(path, path + striplen,
3558                                 pathlen - striplen + 1);
3559         }
3561         /* Skip "Directory ..." and ".." line. */
3562         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3563                 struct line *line = &view->line[pos];
3564                 const char *path1 = tree_path(line);
3565                 char *path2 = text + SIZEOF_TREE_ATTR;
3566                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3568                 if (cmp <= 0)
3569                         continue;
3571                 text = strdup(text);
3572                 if (!text)
3573                         return FALSE;
3575                 if (view->lines > pos)
3576                         memmove(&view->line[pos + 1], &view->line[pos],
3577                                 (view->lines - pos) * sizeof(*line));
3579                 line = &view->line[pos];
3580                 line->data = text;
3581                 line->type = type;
3582                 view->lines++;
3583                 return TRUE;
3584         }
3586         if (!add_line_text(view, text, type))
3587                 return FALSE;
3589         if (tree_lineno > view->lineno) {
3590                 view->lineno = tree_lineno;
3591                 tree_lineno = 0;
3592         }
3594         return TRUE;
3597 static enum request
3598 tree_request(struct view *view, enum request request, struct line *line)
3600         enum open_flags flags;
3602         switch (request) {
3603         case REQ_VIEW_BLAME:
3604                 if (line->type != LINE_TREE_FILE) {
3605                         report("Blame only supported for files");
3606                         return REQ_NONE;
3607                 }
3609                 string_copy(opt_ref, view->vid);
3610                 return request;
3612         case REQ_EDIT:
3613                 if (line->type != LINE_TREE_FILE) {
3614                         report("Edit only supported for files");
3615                 } else if (!is_head_commit(view->vid)) {
3616                         report("Edit only supported for files in the current work tree");
3617                 } else {
3618                         open_editor(TRUE, opt_file);
3619                 }
3620                 return REQ_NONE;
3622         case REQ_TREE_PARENT:
3623                 if (!*opt_path) {
3624                         /* quit view if at top of tree */
3625                         return REQ_VIEW_CLOSE;
3626                 }
3627                 /* fake 'cd  ..' */
3628                 line = &view->line[1];
3629                 break;
3631         case REQ_ENTER:
3632                 break;
3634         default:
3635                 return request;
3636         }
3638         /* Cleanup the stack if the tree view is at a different tree. */
3639         while (!*opt_path && tree_stack)
3640                 pop_tree_stack_entry();
3642         switch (line->type) {
3643         case LINE_TREE_DIR:
3644                 /* Depending on whether it is a subdir or parent (updir?) link
3645                  * mangle the path buffer. */
3646                 if (line == &view->line[1] && *opt_path) {
3647                         pop_tree_stack_entry();
3649                 } else {
3650                         const char *basename = tree_path(line);
3652                         push_tree_stack_entry(basename, view->lineno);
3653                 }
3655                 /* Trees and subtrees share the same ID, so they are not not
3656                  * unique like blobs. */
3657                 flags = OPEN_RELOAD;
3658                 request = REQ_VIEW_TREE;
3659                 break;
3661         case LINE_TREE_FILE:
3662                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3663                 request = REQ_VIEW_BLOB;
3664                 break;
3666         default:
3667                 return TRUE;
3668         }
3670         open_view(view, request, flags);
3671         if (request == REQ_VIEW_TREE) {
3672                 view->lineno = tree_lineno;
3673         }
3675         return REQ_NONE;
3678 static void
3679 tree_select(struct view *view, struct line *line)
3681         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3683         if (line->type == LINE_TREE_FILE) {
3684                 string_copy_rev(ref_blob, text);
3685                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3687         } else if (line->type != LINE_TREE_DIR) {
3688                 return;
3689         }
3691         string_copy_rev(view->ref, text);
3694 static const char *tree_argv[SIZEOF_ARG] = {
3695         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3696 };
3698 static struct view_ops tree_ops = {
3699         "file",
3700         tree_argv,
3701         NULL,
3702         tree_read,
3703         pager_draw,
3704         tree_request,
3705         pager_grep,
3706         tree_select,
3707 };
3709 static bool
3710 blob_read(struct view *view, char *line)
3712         if (!line)
3713                 return TRUE;
3714         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3717 static const char *blob_argv[SIZEOF_ARG] = {
3718         "git", "cat-file", "blob", "%(blob)", NULL
3719 };
3721 static struct view_ops blob_ops = {
3722         "line",
3723         blob_argv,
3724         NULL,
3725         blob_read,
3726         pager_draw,
3727         pager_request,
3728         pager_grep,
3729         pager_select,
3730 };
3732 /*
3733  * Blame backend
3734  *
3735  * Loading the blame view is a two phase job:
3736  *
3737  *  1. File content is read either using opt_file from the
3738  *     filesystem or using git-cat-file.
3739  *  2. Then blame information is incrementally added by
3740  *     reading output from git-blame.
3741  */
3743 static const char *blame_head_argv[] = {
3744         "git", "blame", "--incremental", "--", "%(file)", NULL
3745 };
3747 static const char *blame_ref_argv[] = {
3748         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3749 };
3751 static const char *blame_cat_file_argv[] = {
3752         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3753 };
3755 struct blame_commit {
3756         char id[SIZEOF_REV];            /* SHA1 ID. */
3757         char title[128];                /* First line of the commit message. */
3758         char author[75];                /* Author of the commit. */
3759         struct tm time;                 /* Date from the author ident. */
3760         char filename[128];             /* Name of file. */
3761 };
3763 struct blame {
3764         struct blame_commit *commit;
3765         char text[1];
3766 };
3768 static bool
3769 blame_open(struct view *view)
3771         if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3772                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3773                         return FALSE;
3774         }
3776         setup_update(view, opt_file);
3777         string_format(view->ref, "%s ...", opt_file);
3779         return TRUE;
3782 static struct blame_commit *
3783 get_blame_commit(struct view *view, const char *id)
3785         size_t i;
3787         for (i = 0; i < view->lines; i++) {
3788                 struct blame *blame = view->line[i].data;
3790                 if (!blame->commit)
3791                         continue;
3793                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3794                         return blame->commit;
3795         }
3797         {
3798                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3800                 if (commit)
3801                         string_ncopy(commit->id, id, SIZEOF_REV);
3802                 return commit;
3803         }
3806 static bool
3807 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3809         const char *pos = *posref;
3811         *posref = NULL;
3812         pos = strchr(pos + 1, ' ');
3813         if (!pos || !isdigit(pos[1]))
3814                 return FALSE;
3815         *number = atoi(pos + 1);
3816         if (*number < min || *number > max)
3817                 return FALSE;
3819         *posref = pos;
3820         return TRUE;
3823 static struct blame_commit *
3824 parse_blame_commit(struct view *view, const char *text, int *blamed)
3826         struct blame_commit *commit;
3827         struct blame *blame;
3828         const char *pos = text + SIZEOF_REV - 1;
3829         size_t lineno;
3830         size_t group;
3832         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3833                 return NULL;
3835         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3836             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3837                 return NULL;
3839         commit = get_blame_commit(view, text);
3840         if (!commit)
3841                 return NULL;
3843         *blamed += group;
3844         while (group--) {
3845                 struct line *line = &view->line[lineno + group - 1];
3847                 blame = line->data;
3848                 blame->commit = commit;
3849                 line->dirty = 1;
3850         }
3852         return commit;
3855 static bool
3856 blame_read_file(struct view *view, const char *line, bool *read_file)
3858         if (!line) {
3859                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3860                 struct io io = {};
3862                 if (view->lines == 0 && !view->parent)
3863                         die("No blame exist for %s", view->vid);
3865                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3866                         report("Failed to load blame data");
3867                         return TRUE;
3868                 }
3870                 done_io(view->pipe);
3871                 view->io = io;
3872                 *read_file = FALSE;
3873                 return FALSE;
3875         } else {
3876                 size_t linelen = strlen(line);
3877                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3879                 blame->commit = NULL;
3880                 strncpy(blame->text, line, linelen);
3881                 blame->text[linelen] = 0;
3882                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3883         }
3886 static bool
3887 match_blame_header(const char *name, char **line)
3889         size_t namelen = strlen(name);
3890         bool matched = !strncmp(name, *line, namelen);
3892         if (matched)
3893                 *line += namelen;
3895         return matched;
3898 static bool
3899 blame_read(struct view *view, char *line)
3901         static struct blame_commit *commit = NULL;
3902         static int blamed = 0;
3903         static time_t author_time;
3904         static bool read_file = TRUE;
3906         if (read_file)
3907                 return blame_read_file(view, line, &read_file);
3909         if (!line) {
3910                 /* Reset all! */
3911                 commit = NULL;
3912                 blamed = 0;
3913                 read_file = TRUE;
3914                 string_format(view->ref, "%s", view->vid);
3915                 if (view_is_displayed(view)) {
3916                         update_view_title(view);
3917                         redraw_view_from(view, 0);
3918                 }
3919                 return TRUE;
3920         }
3922         if (!commit) {
3923                 commit = parse_blame_commit(view, line, &blamed);
3924                 string_format(view->ref, "%s %2d%%", view->vid,
3925                               blamed * 100 / view->lines);
3927         } else if (match_blame_header("author ", &line)) {
3928                 string_ncopy(commit->author, line, strlen(line));
3930         } else if (match_blame_header("author-time ", &line)) {
3931                 author_time = (time_t) atol(line);
3933         } else if (match_blame_header("author-tz ", &line)) {
3934                 long tz;
3936                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3937                 tz += ('0' - line[2]) * 60 * 60;
3938                 tz += ('0' - line[3]) * 60;
3939                 tz += ('0' - line[4]) * 60;
3941                 if (line[0] == '-')
3942                         tz = -tz;
3944                 author_time -= tz;
3945                 gmtime_r(&author_time, &commit->time);
3947         } else if (match_blame_header("summary ", &line)) {
3948                 string_ncopy(commit->title, line, strlen(line));
3950         } else if (match_blame_header("filename ", &line)) {
3951                 string_ncopy(commit->filename, line, strlen(line));
3952                 commit = NULL;
3953         }
3955         return TRUE;
3958 static bool
3959 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3961         struct blame *blame = line->data;
3962         struct tm *time = NULL;
3963         const char *id = NULL, *author = NULL;
3965         if (blame->commit && *blame->commit->filename) {
3966                 id = blame->commit->id;
3967                 author = blame->commit->author;
3968                 time = &blame->commit->time;
3969         }
3971         if (opt_date && draw_date(view, time))
3972                 return TRUE;
3974         if (opt_author &&
3975             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3976                 return TRUE;
3978         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3979                 return TRUE;
3981         if (draw_lineno(view, lineno))
3982                 return TRUE;
3984         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3985         return TRUE;
3988 static enum request
3989 blame_request(struct view *view, enum request request, struct line *line)
3991         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3992         struct blame *blame = line->data;
3994         switch (request) {
3995         case REQ_VIEW_BLAME:
3996                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3997                         report("Commit ID unknown");
3998                         break;
3999                 }
4000                 string_copy(opt_ref, blame->commit->id);
4001                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4002                 return request;
4004         case REQ_ENTER:
4005                 if (!blame->commit) {
4006                         report("No commit loaded yet");
4007                         break;
4008                 }
4010                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4011                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4012                         break;
4014                 if (!strcmp(blame->commit->id, NULL_ID)) {
4015                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4016                         const char *diff_index_argv[] = {
4017                                 "git", "diff-index", "--root", "--cached",
4018                                         "--patch-with-stat", "-C", "-M",
4019                                         "HEAD", "--", view->vid, NULL
4020                         };
4022                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4023                                 report("Failed to allocate diff command");
4024                                 break;
4025                         }
4026                         flags |= OPEN_PREPARED;
4027                 }
4029                 open_view(view, REQ_VIEW_DIFF, flags);
4030                 break;
4032         default:
4033                 return request;
4034         }
4036         return REQ_NONE;
4039 static bool
4040 blame_grep(struct view *view, struct line *line)
4042         struct blame *blame = line->data;
4043         struct blame_commit *commit = blame->commit;
4044         regmatch_t pmatch;
4046 #define MATCH(text, on)                                                 \
4047         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4049         if (commit) {
4050                 char buf[DATE_COLS + 1];
4052                 if (MATCH(commit->title, 1) ||
4053                     MATCH(commit->author, opt_author) ||
4054                     MATCH(commit->id, opt_date))
4055                         return TRUE;
4057                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4058                     MATCH(buf, 1))
4059                         return TRUE;
4060         }
4062         return MATCH(blame->text, 1);
4064 #undef MATCH
4067 static void
4068 blame_select(struct view *view, struct line *line)
4070         struct blame *blame = line->data;
4071         struct blame_commit *commit = blame->commit;
4073         if (!commit)
4074                 return;
4076         if (!strcmp(commit->id, NULL_ID))
4077                 string_ncopy(ref_commit, "HEAD", 4);
4078         else
4079                 string_copy_rev(ref_commit, commit->id);
4082 static struct view_ops blame_ops = {
4083         "line",
4084         NULL,
4085         blame_open,
4086         blame_read,
4087         blame_draw,
4088         blame_request,
4089         blame_grep,
4090         blame_select,
4091 };
4093 /*
4094  * Status backend
4095  */
4097 struct status {
4098         char status;
4099         struct {
4100                 mode_t mode;
4101                 char rev[SIZEOF_REV];
4102                 char name[SIZEOF_STR];
4103         } old;
4104         struct {
4105                 mode_t mode;
4106                 char rev[SIZEOF_REV];
4107                 char name[SIZEOF_STR];
4108         } new;
4109 };
4111 static char status_onbranch[SIZEOF_STR];
4112 static struct status stage_status;
4113 static enum line_type stage_line_type;
4114 static size_t stage_chunks;
4115 static int *stage_chunk;
4117 /* This should work even for the "On branch" line. */
4118 static inline bool
4119 status_has_none(struct view *view, struct line *line)
4121         return line < view->line + view->lines && !line[1].data;
4124 /* Get fields from the diff line:
4125  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4126  */
4127 static inline bool
4128 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4130         const char *old_mode = buf +  1;
4131         const char *new_mode = buf +  8;
4132         const char *old_rev  = buf + 15;
4133         const char *new_rev  = buf + 56;
4134         const char *status   = buf + 97;
4136         if (bufsize < 99 ||
4137             old_mode[-1] != ':' ||
4138             new_mode[-1] != ' ' ||
4139             old_rev[-1]  != ' ' ||
4140             new_rev[-1]  != ' ' ||
4141             status[-1]   != ' ')
4142                 return FALSE;
4144         file->status = *status;
4146         string_copy_rev(file->old.rev, old_rev);
4147         string_copy_rev(file->new.rev, new_rev);
4149         file->old.mode = strtoul(old_mode, NULL, 8);
4150         file->new.mode = strtoul(new_mode, NULL, 8);
4152         file->old.name[0] = file->new.name[0] = 0;
4154         return TRUE;
4157 static bool
4158 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4160         struct status *file = NULL;
4161         struct status *unmerged = NULL;
4162         char buf[SIZEOF_STR * 4];
4163         size_t bufsize = 0;
4164         FILE *pipe;
4166         pipe = popen(cmd, "r");
4167         if (!pipe)
4168                 return FALSE;
4170         add_line_data(view, NULL, type);
4172         while (!feof(pipe) && !ferror(pipe)) {
4173                 char *sep;
4174                 size_t readsize;
4176                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4177                 if (!readsize)
4178                         break;
4179                 bufsize += readsize;
4181                 /* Process while we have NUL chars. */
4182                 while ((sep = memchr(buf, 0, bufsize))) {
4183                         size_t sepsize = sep - buf + 1;
4185                         if (!file) {
4186                                 if (!realloc_lines(view, view->line_size + 1))
4187                                         goto error_out;
4189                                 file = calloc(1, sizeof(*file));
4190                                 if (!file)
4191                                         goto error_out;
4193                                 add_line_data(view, file, type);
4194                         }
4196                         /* Parse diff info part. */
4197                         if (status) {
4198                                 file->status = status;
4199                                 if (status == 'A')
4200                                         string_copy(file->old.rev, NULL_ID);
4202                         } else if (!file->status) {
4203                                 if (!status_get_diff(file, buf, sepsize))
4204                                         goto error_out;
4206                                 bufsize -= sepsize;
4207                                 memmove(buf, sep + 1, bufsize);
4209                                 sep = memchr(buf, 0, bufsize);
4210                                 if (!sep)
4211                                         break;
4212                                 sepsize = sep - buf + 1;
4214                                 /* Collapse all 'M'odified entries that
4215                                  * follow a associated 'U'nmerged entry.
4216                                  */
4217                                 if (file->status == 'U') {
4218                                         unmerged = file;
4220                                 } else if (unmerged) {
4221                                         int collapse = !strcmp(buf, unmerged->new.name);
4223                                         unmerged = NULL;
4224                                         if (collapse) {
4225                                                 free(file);
4226                                                 view->lines--;
4227                                                 continue;
4228                                         }
4229                                 }
4230                         }
4232                         /* Grab the old name for rename/copy. */
4233                         if (!*file->old.name &&
4234                             (file->status == 'R' || file->status == 'C')) {
4235                                 sepsize = sep - buf + 1;
4236                                 string_ncopy(file->old.name, buf, sepsize);
4237                                 bufsize -= sepsize;
4238                                 memmove(buf, sep + 1, bufsize);
4240                                 sep = memchr(buf, 0, bufsize);
4241                                 if (!sep)
4242                                         break;
4243                                 sepsize = sep - buf + 1;
4244                         }
4246                         /* git-ls-files just delivers a NUL separated
4247                          * list of file names similar to the second half
4248                          * of the git-diff-* output. */
4249                         string_ncopy(file->new.name, buf, sepsize);
4250                         if (!*file->old.name)
4251                                 string_copy(file->old.name, file->new.name);
4252                         bufsize -= sepsize;
4253                         memmove(buf, sep + 1, bufsize);
4254                         file = NULL;
4255                 }
4256         }
4258         if (ferror(pipe)) {
4259 error_out:
4260                 pclose(pipe);
4261                 return FALSE;
4262         }
4264         if (!view->line[view->lines - 1].data)
4265                 add_line_data(view, NULL, LINE_STAT_NONE);
4267         pclose(pipe);
4268         return TRUE;
4271 /* Don't show unmerged entries in the staged section. */
4272 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4273 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4274 #define STATUS_LIST_OTHER_CMD \
4275         "git ls-files -z --others --exclude-standard"
4276 #define STATUS_LIST_NO_HEAD_CMD \
4277         "git ls-files -z --cached --exclude-standard"
4279 #define STATUS_DIFF_INDEX_SHOW_CMD \
4280         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4282 #define STATUS_DIFF_FILES_SHOW_CMD \
4283         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4285 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4286         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4288 /* First parse staged info using git-diff-index(1), then parse unstaged
4289  * info using git-diff-files(1), and finally untracked files using
4290  * git-ls-files(1). */
4291 static bool
4292 status_open(struct view *view)
4294         unsigned long prev_lineno = view->lineno;
4296         reset_view(view);
4298         if (!realloc_lines(view, view->line_size + 7))
4299                 return FALSE;
4301         add_line_data(view, NULL, LINE_STAT_HEAD);
4302         if (is_initial_commit())
4303                 string_copy(status_onbranch, "Initial commit");
4304         else if (!*opt_head)
4305                 string_copy(status_onbranch, "Not currently on any branch");
4306         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4307                 return FALSE;
4309         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4311         if (is_initial_commit()) {
4312                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4313                         return FALSE;
4314         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4315                 return FALSE;
4316         }
4318         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4319             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4320                 return FALSE;
4322         /* If all went well restore the previous line number to stay in
4323          * the context or select a line with something that can be
4324          * updated. */
4325         if (prev_lineno >= view->lines)
4326                 prev_lineno = view->lines - 1;
4327         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4328                 prev_lineno++;
4329         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4330                 prev_lineno--;
4332         /* If the above fails, always skip the "On branch" line. */
4333         if (prev_lineno < view->lines)
4334                 view->lineno = prev_lineno;
4335         else
4336                 view->lineno = 1;
4338         if (view->lineno < view->offset)
4339                 view->offset = view->lineno;
4340         else if (view->offset + view->height <= view->lineno)
4341                 view->offset = view->lineno - view->height + 1;
4343         return TRUE;
4346 static bool
4347 status_draw(struct view *view, struct line *line, unsigned int lineno)
4349         struct status *status = line->data;
4350         enum line_type type;
4351         const char *text;
4353         if (!status) {
4354                 switch (line->type) {
4355                 case LINE_STAT_STAGED:
4356                         type = LINE_STAT_SECTION;
4357                         text = "Changes to be committed:";
4358                         break;
4360                 case LINE_STAT_UNSTAGED:
4361                         type = LINE_STAT_SECTION;
4362                         text = "Changed but not updated:";
4363                         break;
4365                 case LINE_STAT_UNTRACKED:
4366                         type = LINE_STAT_SECTION;
4367                         text = "Untracked files:";
4368                         break;
4370                 case LINE_STAT_NONE:
4371                         type = LINE_DEFAULT;
4372                         text = "    (no files)";
4373                         break;
4375                 case LINE_STAT_HEAD:
4376                         type = LINE_STAT_HEAD;
4377                         text = status_onbranch;
4378                         break;
4380                 default:
4381                         return FALSE;
4382                 }
4383         } else {
4384                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4386                 buf[0] = status->status;
4387                 if (draw_text(view, line->type, buf, TRUE))
4388                         return TRUE;
4389                 type = LINE_DEFAULT;
4390                 text = status->new.name;
4391         }
4393         draw_text(view, type, text, TRUE);
4394         return TRUE;
4397 static enum request
4398 status_enter(struct view *view, struct line *line)
4400         struct status *status = line->data;
4401         char oldpath[SIZEOF_STR] = "";
4402         char newpath[SIZEOF_STR] = "";
4403         const char *info;
4404         size_t cmdsize = 0;
4405         enum open_flags split;
4407         if (line->type == LINE_STAT_NONE ||
4408             (!status && line[1].type == LINE_STAT_NONE)) {
4409                 report("No file to diff");
4410                 return REQ_NONE;
4411         }
4413         if (status) {
4414                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4415                         return REQ_QUIT;
4416                 /* Diffs for unmerged entries are empty when pasing the
4417                  * new path, so leave it empty. */
4418                 if (status->status != 'U' &&
4419                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4420                         return REQ_QUIT;
4421         }
4423         if (opt_cdup[0] &&
4424             line->type != LINE_STAT_UNTRACKED &&
4425             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4426                 return REQ_QUIT;
4428         switch (line->type) {
4429         case LINE_STAT_STAGED:
4430                 if (is_initial_commit()) {
4431                         if (!string_format_from(opt_cmd, &cmdsize,
4432                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4433                                                 newpath))
4434                                 return REQ_QUIT;
4435                 } else {
4436                         if (!string_format_from(opt_cmd, &cmdsize,
4437                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4438                                                 oldpath, newpath))
4439                                 return REQ_QUIT;
4440                 }
4442                 if (status)
4443                         info = "Staged changes to %s";
4444                 else
4445                         info = "Staged changes";
4446                 break;
4448         case LINE_STAT_UNSTAGED:
4449                 if (!string_format_from(opt_cmd, &cmdsize,
4450                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4451                         return REQ_QUIT;
4452                 if (status)
4453                         info = "Unstaged changes to %s";
4454                 else
4455                         info = "Unstaged changes";
4456                 break;
4458         case LINE_STAT_UNTRACKED:
4459                 if (opt_pipe)
4460                         return REQ_QUIT;
4462                 if (!status) {
4463                         report("No file to show");
4464                         return REQ_NONE;
4465                 }
4467                 if (!suffixcmp(status->new.name, -1, "/")) {
4468                         report("Cannot display a directory");
4469                         return REQ_NONE;
4470                 }
4472                 opt_pipe = fopen(status->new.name, "r");
4473                 info = "Untracked file %s";
4474                 break;
4476         case LINE_STAT_HEAD:
4477                 return REQ_NONE;
4479         default:
4480                 die("line type %d not handled in switch", line->type);
4481         }
4483         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4484         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4485         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4486                 if (status) {
4487                         stage_status = *status;
4488                 } else {
4489                         memset(&stage_status, 0, sizeof(stage_status));
4490                 }
4492                 stage_line_type = line->type;
4493                 stage_chunks = 0;
4494                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4495         }
4497         return REQ_NONE;
4500 static bool
4501 status_exists(struct status *status, enum line_type type)
4503         struct view *view = VIEW(REQ_VIEW_STATUS);
4504         struct line *line;
4506         for (line = view->line; line < view->line + view->lines; line++) {
4507                 struct status *pos = line->data;
4509                 if (line->type == type && pos &&
4510                     !strcmp(status->new.name, pos->new.name))
4511                         return TRUE;
4512         }
4514         return FALSE;
4518 static FILE *
4519 status_update_prepare(enum line_type type)
4521         char cmd[SIZEOF_STR];
4522         size_t cmdsize = 0;
4524         if (opt_cdup[0] &&
4525             type != LINE_STAT_UNTRACKED &&
4526             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4527                 return NULL;
4529         switch (type) {
4530         case LINE_STAT_STAGED:
4531                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4532                 break;
4534         case LINE_STAT_UNSTAGED:
4535         case LINE_STAT_UNTRACKED:
4536                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4537                 break;
4539         default:
4540                 die("line type %d not handled in switch", type);
4541         }
4543         return popen(cmd, "w");
4546 static bool
4547 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4549         char buf[SIZEOF_STR];
4550         size_t bufsize = 0;
4551         size_t written = 0;
4553         switch (type) {
4554         case LINE_STAT_STAGED:
4555                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4556                                         status->old.mode,
4557                                         status->old.rev,
4558                                         status->old.name, 0))
4559                         return FALSE;
4560                 break;
4562         case LINE_STAT_UNSTAGED:
4563         case LINE_STAT_UNTRACKED:
4564                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4565                         return FALSE;
4566                 break;
4568         default:
4569                 die("line type %d not handled in switch", type);
4570         }
4572         while (!ferror(pipe) && written < bufsize) {
4573                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4574         }
4576         return written == bufsize;
4579 static bool
4580 status_update_file(struct status *status, enum line_type type)
4582         FILE *pipe = status_update_prepare(type);
4583         bool result;
4585         if (!pipe)
4586                 return FALSE;
4588         result = status_update_write(pipe, status, type);
4589         pclose(pipe);
4590         return result;
4593 static bool
4594 status_update_files(struct view *view, struct line *line)
4596         FILE *pipe = status_update_prepare(line->type);
4597         bool result = TRUE;
4598         struct line *pos = view->line + view->lines;
4599         int files = 0;
4600         int file, done;
4602         if (!pipe)
4603                 return FALSE;
4605         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4606                 files++;
4608         for (file = 0, done = 0; result && file < files; line++, file++) {
4609                 int almost_done = file * 100 / files;
4611                 if (almost_done > done) {
4612                         done = almost_done;
4613                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4614                                       file, files, done);
4615                         update_view_title(view);
4616                 }
4617                 result = status_update_write(pipe, line->data, line->type);
4618         }
4620         pclose(pipe);
4621         return result;
4624 static bool
4625 status_update(struct view *view)
4627         struct line *line = &view->line[view->lineno];
4629         assert(view->lines);
4631         if (!line->data) {
4632                 /* This should work even for the "On branch" line. */
4633                 if (line < view->line + view->lines && !line[1].data) {
4634                         report("Nothing to update");
4635                         return FALSE;
4636                 }
4638                 if (!status_update_files(view, line + 1)) {
4639                         report("Failed to update file status");
4640                         return FALSE;
4641                 }
4643         } else if (!status_update_file(line->data, line->type)) {
4644                 report("Failed to update file status");
4645                 return FALSE;
4646         }
4648         return TRUE;
4651 static bool
4652 status_revert(struct status *status, enum line_type type, bool has_none)
4654         if (!status || type != LINE_STAT_UNSTAGED) {
4655                 if (type == LINE_STAT_STAGED) {
4656                         report("Cannot revert changes to staged files");
4657                 } else if (type == LINE_STAT_UNTRACKED) {
4658                         report("Cannot revert changes to untracked files");
4659                 } else if (has_none) {
4660                         report("Nothing to revert");
4661                 } else {
4662                         report("Cannot revert changes to multiple files");
4663                 }
4664                 return FALSE;
4666         } else {
4667                 const char *checkout_argv[] = {
4668                         "git", "checkout", "--", status->old.name, NULL
4669                 };
4671                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4672                         return FALSE;
4673                 return run_io_fg(checkout_argv, opt_cdup);
4674         }
4677 static enum request
4678 status_request(struct view *view, enum request request, struct line *line)
4680         struct status *status = line->data;
4682         switch (request) {
4683         case REQ_STATUS_UPDATE:
4684                 if (!status_update(view))
4685                         return REQ_NONE;
4686                 break;
4688         case REQ_STATUS_REVERT:
4689                 if (!status_revert(status, line->type, status_has_none(view, line)))
4690                         return REQ_NONE;
4691                 break;
4693         case REQ_STATUS_MERGE:
4694                 if (!status || status->status != 'U') {
4695                         report("Merging only possible for files with unmerged status ('U').");
4696                         return REQ_NONE;
4697                 }
4698                 open_mergetool(status->new.name);
4699                 break;
4701         case REQ_EDIT:
4702                 if (!status)
4703                         return request;
4704                 if (status->status == 'D') {
4705                         report("File has been deleted.");
4706                         return REQ_NONE;
4707                 }
4709                 open_editor(status->status != '?', status->new.name);
4710                 break;
4712         case REQ_VIEW_BLAME:
4713                 if (status) {
4714                         string_copy(opt_file, status->new.name);
4715                         opt_ref[0] = 0;
4716                 }
4717                 return request;
4719         case REQ_ENTER:
4720                 /* After returning the status view has been split to
4721                  * show the stage view. No further reloading is
4722                  * necessary. */
4723                 status_enter(view, line);
4724                 return REQ_NONE;
4726         case REQ_REFRESH:
4727                 /* Simply reload the view. */
4728                 break;
4730         default:
4731                 return request;
4732         }
4734         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4736         return REQ_NONE;
4739 static void
4740 status_select(struct view *view, struct line *line)
4742         struct status *status = line->data;
4743         char file[SIZEOF_STR] = "all files";
4744         const char *text;
4745         const char *key;
4747         if (status && !string_format(file, "'%s'", status->new.name))
4748                 return;
4750         if (!status && line[1].type == LINE_STAT_NONE)
4751                 line++;
4753         switch (line->type) {
4754         case LINE_STAT_STAGED:
4755                 text = "Press %s to unstage %s for commit";
4756                 break;
4758         case LINE_STAT_UNSTAGED:
4759                 text = "Press %s to stage %s for commit";
4760                 break;
4762         case LINE_STAT_UNTRACKED:
4763                 text = "Press %s to stage %s for addition";
4764                 break;
4766         case LINE_STAT_HEAD:
4767         case LINE_STAT_NONE:
4768                 text = "Nothing to update";
4769                 break;
4771         default:
4772                 die("line type %d not handled in switch", line->type);
4773         }
4775         if (status && status->status == 'U') {
4776                 text = "Press %s to resolve conflict in %s";
4777                 key = get_key(REQ_STATUS_MERGE);
4779         } else {
4780                 key = get_key(REQ_STATUS_UPDATE);
4781         }
4783         string_format(view->ref, text, key, file);
4786 static bool
4787 status_grep(struct view *view, struct line *line)
4789         struct status *status = line->data;
4790         enum { S_STATUS, S_NAME, S_END } state;
4791         char buf[2] = "?";
4792         regmatch_t pmatch;
4794         if (!status)
4795                 return FALSE;
4797         for (state = S_STATUS; state < S_END; state++) {
4798                 const char *text;
4800                 switch (state) {
4801                 case S_NAME:    text = status->new.name;        break;
4802                 case S_STATUS:
4803                         buf[0] = status->status;
4804                         text = buf;
4805                         break;
4807                 default:
4808                         return FALSE;
4809                 }
4811                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4812                         return TRUE;
4813         }
4815         return FALSE;
4818 static struct view_ops status_ops = {
4819         "file",
4820         NULL,
4821         status_open,
4822         NULL,
4823         status_draw,
4824         status_request,
4825         status_grep,
4826         status_select,
4827 };
4830 static bool
4831 stage_diff_line(FILE *pipe, struct line *line)
4833         const char *buf = line->data;
4834         size_t bufsize = strlen(buf);
4835         size_t written = 0;
4837         while (!ferror(pipe) && written < bufsize) {
4838                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4839         }
4841         fputc('\n', pipe);
4843         return written == bufsize;
4846 static bool
4847 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4849         while (line < end) {
4850                 if (!stage_diff_line(pipe, line++))
4851                         return FALSE;
4852                 if (line->type == LINE_DIFF_CHUNK ||
4853                     line->type == LINE_DIFF_HEADER)
4854                         break;
4855         }
4857         return TRUE;
4860 static struct line *
4861 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4863         for (; view->line < line; line--)
4864                 if (line->type == type)
4865                         return line;
4867         return NULL;
4870 static bool
4871 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4873         char cmd[SIZEOF_STR];
4874         size_t cmdsize = 0;
4875         struct line *diff_hdr;
4876         FILE *pipe;
4878         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4879         if (!diff_hdr)
4880                 return FALSE;
4882         if (opt_cdup[0] &&
4883             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4884                 return FALSE;
4886         if (!string_format_from(cmd, &cmdsize,
4887                                 "git apply --whitespace=nowarn %s %s - && "
4888                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4889                                 revert ? "" : "--cached",
4890                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4891                 return FALSE;
4893         pipe = popen(cmd, "w");
4894         if (!pipe)
4895                 return FALSE;
4897         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4898             !stage_diff_write(pipe, chunk, view->line + view->lines))
4899                 chunk = NULL;
4901         pclose(pipe);
4903         return chunk ? TRUE : FALSE;
4906 static bool
4907 stage_update(struct view *view, struct line *line)
4909         struct line *chunk = NULL;
4911         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4912                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4914         if (chunk) {
4915                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4916                         report("Failed to apply chunk");
4917                         return FALSE;
4918                 }
4920         } else if (!stage_status.status) {
4921                 view = VIEW(REQ_VIEW_STATUS);
4923                 for (line = view->line; line < view->line + view->lines; line++)
4924                         if (line->type == stage_line_type)
4925                                 break;
4927                 if (!status_update_files(view, line + 1)) {
4928                         report("Failed to update files");
4929                         return FALSE;
4930                 }
4932         } else if (!status_update_file(&stage_status, stage_line_type)) {
4933                 report("Failed to update file");
4934                 return FALSE;
4935         }
4937         return TRUE;
4940 static bool
4941 stage_revert(struct view *view, struct line *line)
4943         struct line *chunk = NULL;
4945         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4946                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4948         if (chunk) {
4949                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4950                         return FALSE;
4952                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4953                         report("Failed to revert chunk");
4954                         return FALSE;
4955                 }
4956                 return TRUE;
4958         } else {
4959                 return status_revert(stage_status.status ? &stage_status : NULL,
4960                                      stage_line_type, FALSE);
4961         }
4965 static void
4966 stage_next(struct view *view, struct line *line)
4968         int i;
4970         if (!stage_chunks) {
4971                 static size_t alloc = 0;
4972                 int *tmp;
4974                 for (line = view->line; line < view->line + view->lines; line++) {
4975                         if (line->type != LINE_DIFF_CHUNK)
4976                                 continue;
4978                         tmp = realloc_items(stage_chunk, &alloc,
4979                                             stage_chunks, sizeof(*tmp));
4980                         if (!tmp) {
4981                                 report("Allocation failure");
4982                                 return;
4983                         }
4985                         stage_chunk = tmp;
4986                         stage_chunk[stage_chunks++] = line - view->line;
4987                 }
4988         }
4990         for (i = 0; i < stage_chunks; i++) {
4991                 if (stage_chunk[i] > view->lineno) {
4992                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4993                         report("Chunk %d of %d", i + 1, stage_chunks);
4994                         return;
4995                 }
4996         }
4998         report("No next chunk found");
5001 static enum request
5002 stage_request(struct view *view, enum request request, struct line *line)
5004         switch (request) {
5005         case REQ_STATUS_UPDATE:
5006                 if (!stage_update(view, line))
5007                         return REQ_NONE;
5008                 break;
5010         case REQ_STATUS_REVERT:
5011                 if (!stage_revert(view, line))
5012                         return REQ_NONE;
5013                 break;
5015         case REQ_STAGE_NEXT:
5016                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5017                         report("File is untracked; press %s to add",
5018                                get_key(REQ_STATUS_UPDATE));
5019                         return REQ_NONE;
5020                 }
5021                 stage_next(view, line);
5022                 return REQ_NONE;
5024         case REQ_EDIT:
5025                 if (!stage_status.new.name[0])
5026                         return request;
5027                 if (stage_status.status == 'D') {
5028                         report("File has been deleted.");
5029                         return REQ_NONE;
5030                 }
5032                 open_editor(stage_status.status != '?', stage_status.new.name);
5033                 break;
5035         case REQ_REFRESH:
5036                 /* Reload everything ... */
5037                 break;
5039         case REQ_VIEW_BLAME:
5040                 if (stage_status.new.name[0]) {
5041                         string_copy(opt_file, stage_status.new.name);
5042                         opt_ref[0] = 0;
5043                 }
5044                 return request;
5046         case REQ_ENTER:
5047                 return pager_request(view, request, line);
5049         default:
5050                 return request;
5051         }
5053         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5055         /* Check whether the staged entry still exists, and close the
5056          * stage view if it doesn't. */
5057         if (!status_exists(&stage_status, stage_line_type))
5058                 return REQ_VIEW_CLOSE;
5060         if (stage_line_type == LINE_STAT_UNTRACKED) {
5061                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5062                         report("Cannot display a directory");
5063                         return REQ_NONE;
5064                 }
5066                 opt_pipe = fopen(stage_status.new.name, "r");
5067         }
5068         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5070         return REQ_NONE;
5073 static struct view_ops stage_ops = {
5074         "line",
5075         NULL,
5076         NULL,
5077         pager_read,
5078         pager_draw,
5079         stage_request,
5080         pager_grep,
5081         pager_select,
5082 };
5085 /*
5086  * Revision graph
5087  */
5089 struct commit {
5090         char id[SIZEOF_REV];            /* SHA1 ID. */
5091         char title[128];                /* First line of the commit message. */
5092         char author[75];                /* Author of the commit. */
5093         struct tm time;                 /* Date from the author ident. */
5094         struct ref **refs;              /* Repository references. */
5095         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5096         size_t graph_size;              /* The width of the graph array. */
5097         bool has_parents;               /* Rewritten --parents seen. */
5098 };
5100 /* Size of rev graph with no  "padding" columns */
5101 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5103 struct rev_graph {
5104         struct rev_graph *prev, *next, *parents;
5105         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5106         size_t size;
5107         struct commit *commit;
5108         size_t pos;
5109         unsigned int boundary:1;
5110 };
5112 /* Parents of the commit being visualized. */
5113 static struct rev_graph graph_parents[4];
5115 /* The current stack of revisions on the graph. */
5116 static struct rev_graph graph_stacks[4] = {
5117         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5118         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5119         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5120         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5121 };
5123 static inline bool
5124 graph_parent_is_merge(struct rev_graph *graph)
5126         return graph->parents->size > 1;
5129 static inline void
5130 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5132         struct commit *commit = graph->commit;
5134         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5135                 commit->graph[commit->graph_size++] = symbol;
5138 static void
5139 clear_rev_graph(struct rev_graph *graph)
5141         graph->boundary = 0;
5142         graph->size = graph->pos = 0;
5143         graph->commit = NULL;
5144         memset(graph->parents, 0, sizeof(*graph->parents));
5147 static void
5148 done_rev_graph(struct rev_graph *graph)
5150         if (graph_parent_is_merge(graph) &&
5151             graph->pos < graph->size - 1 &&
5152             graph->next->size == graph->size + graph->parents->size - 1) {
5153                 size_t i = graph->pos + graph->parents->size - 1;
5155                 graph->commit->graph_size = i * 2;
5156                 while (i < graph->next->size - 1) {
5157                         append_to_rev_graph(graph, ' ');
5158                         append_to_rev_graph(graph, '\\');
5159                         i++;
5160                 }
5161         }
5163         clear_rev_graph(graph);
5166 static void
5167 push_rev_graph(struct rev_graph *graph, const char *parent)
5169         int i;
5171         /* "Collapse" duplicate parents lines.
5172          *
5173          * FIXME: This needs to also update update the drawn graph but
5174          * for now it just serves as a method for pruning graph lines. */
5175         for (i = 0; i < graph->size; i++)
5176                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5177                         return;
5179         if (graph->size < SIZEOF_REVITEMS) {
5180                 string_copy_rev(graph->rev[graph->size++], parent);
5181         }
5184 static chtype
5185 get_rev_graph_symbol(struct rev_graph *graph)
5187         chtype symbol;
5189         if (graph->boundary)
5190                 symbol = REVGRAPH_BOUND;
5191         else if (graph->parents->size == 0)
5192                 symbol = REVGRAPH_INIT;
5193         else if (graph_parent_is_merge(graph))
5194                 symbol = REVGRAPH_MERGE;
5195         else if (graph->pos >= graph->size)
5196                 symbol = REVGRAPH_BRANCH;
5197         else
5198                 symbol = REVGRAPH_COMMIT;
5200         return symbol;
5203 static void
5204 draw_rev_graph(struct rev_graph *graph)
5206         struct rev_filler {
5207                 chtype separator, line;
5208         };
5209         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5210         static struct rev_filler fillers[] = {
5211                 { ' ',  '|' },
5212                 { '`',  '.' },
5213                 { '\'', ' ' },
5214                 { '/',  ' ' },
5215         };
5216         chtype symbol = get_rev_graph_symbol(graph);
5217         struct rev_filler *filler;
5218         size_t i;
5220         if (opt_line_graphics)
5221                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5223         filler = &fillers[DEFAULT];
5225         for (i = 0; i < graph->pos; i++) {
5226                 append_to_rev_graph(graph, filler->line);
5227                 if (graph_parent_is_merge(graph->prev) &&
5228                     graph->prev->pos == i)
5229                         filler = &fillers[RSHARP];
5231                 append_to_rev_graph(graph, filler->separator);
5232         }
5234         /* Place the symbol for this revision. */
5235         append_to_rev_graph(graph, symbol);
5237         if (graph->prev->size > graph->size)
5238                 filler = &fillers[RDIAG];
5239         else
5240                 filler = &fillers[DEFAULT];
5242         i++;
5244         for (; i < graph->size; i++) {
5245                 append_to_rev_graph(graph, filler->separator);
5246                 append_to_rev_graph(graph, filler->line);
5247                 if (graph_parent_is_merge(graph->prev) &&
5248                     i < graph->prev->pos + graph->parents->size)
5249                         filler = &fillers[RSHARP];
5250                 if (graph->prev->size > graph->size)
5251                         filler = &fillers[LDIAG];
5252         }
5254         if (graph->prev->size > graph->size) {
5255                 append_to_rev_graph(graph, filler->separator);
5256                 if (filler->line != ' ')
5257                         append_to_rev_graph(graph, filler->line);
5258         }
5261 /* Prepare the next rev graph */
5262 static void
5263 prepare_rev_graph(struct rev_graph *graph)
5265         size_t i;
5267         /* First, traverse all lines of revisions up to the active one. */
5268         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5269                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5270                         break;
5272                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5273         }
5275         /* Interleave the new revision parent(s). */
5276         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5277                 push_rev_graph(graph->next, graph->parents->rev[i]);
5279         /* Lastly, put any remaining revisions. */
5280         for (i = graph->pos + 1; i < graph->size; i++)
5281                 push_rev_graph(graph->next, graph->rev[i]);
5284 static void
5285 update_rev_graph(struct rev_graph *graph)
5287         /* If this is the finalizing update ... */
5288         if (graph->commit)
5289                 prepare_rev_graph(graph);
5291         /* Graph visualization needs a one rev look-ahead,
5292          * so the first update doesn't visualize anything. */
5293         if (!graph->prev->commit)
5294                 return;
5296         draw_rev_graph(graph->prev);
5297         done_rev_graph(graph->prev->prev);
5301 /*
5302  * Main view backend
5303  */
5305 static const char *main_argv[SIZEOF_ARG] = {
5306         "git", "log", "--no-color", "--pretty=raw", "--parents",
5307                       "--topo-order", "%(head)", NULL
5308 };
5310 static bool
5311 main_draw(struct view *view, struct line *line, unsigned int lineno)
5313         struct commit *commit = line->data;
5315         if (!*commit->author)
5316                 return FALSE;
5318         if (opt_date && draw_date(view, &commit->time))
5319                 return TRUE;
5321         if (opt_author &&
5322             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5323                 return TRUE;
5325         if (opt_rev_graph && commit->graph_size &&
5326             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5327                 return TRUE;
5329         if (opt_show_refs && commit->refs) {
5330                 size_t i = 0;
5332                 do {
5333                         enum line_type type;
5335                         if (commit->refs[i]->head)
5336                                 type = LINE_MAIN_HEAD;
5337                         else if (commit->refs[i]->ltag)
5338                                 type = LINE_MAIN_LOCAL_TAG;
5339                         else if (commit->refs[i]->tag)
5340                                 type = LINE_MAIN_TAG;
5341                         else if (commit->refs[i]->tracked)
5342                                 type = LINE_MAIN_TRACKED;
5343                         else if (commit->refs[i]->remote)
5344                                 type = LINE_MAIN_REMOTE;
5345                         else
5346                                 type = LINE_MAIN_REF;
5348                         if (draw_text(view, type, "[", TRUE) ||
5349                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5350                             draw_text(view, type, "]", TRUE))
5351                                 return TRUE;
5353                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5354                                 return TRUE;
5355                 } while (commit->refs[i++]->next);
5356         }
5358         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5359         return TRUE;
5362 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5363 static bool
5364 main_read(struct view *view, char *line)
5366         static struct rev_graph *graph = graph_stacks;
5367         enum line_type type;
5368         struct commit *commit;
5370         if (!line) {
5371                 int i;
5373                 if (!view->lines && !view->parent)
5374                         die("No revisions match the given arguments.");
5375                 if (view->lines > 0) {
5376                         commit = view->line[view->lines - 1].data;
5377                         if (!*commit->author) {
5378                                 view->lines--;
5379                                 free(commit);
5380                                 graph->commit = NULL;
5381                         }
5382                 }
5383                 update_rev_graph(graph);
5385                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5386                         clear_rev_graph(&graph_stacks[i]);
5387                 return TRUE;
5388         }
5390         type = get_line_type(line);
5391         if (type == LINE_COMMIT) {
5392                 commit = calloc(1, sizeof(struct commit));
5393                 if (!commit)
5394                         return FALSE;
5396                 line += STRING_SIZE("commit ");
5397                 if (*line == '-') {
5398                         graph->boundary = 1;
5399                         line++;
5400                 }
5402                 string_copy_rev(commit->id, line);
5403                 commit->refs = get_refs(commit->id);
5404                 graph->commit = commit;
5405                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5407                 while ((line = strchr(line, ' '))) {
5408                         line++;
5409                         push_rev_graph(graph->parents, line);
5410                         commit->has_parents = TRUE;
5411                 }
5412                 return TRUE;
5413         }
5415         if (!view->lines)
5416                 return TRUE;
5417         commit = view->line[view->lines - 1].data;
5419         switch (type) {
5420         case LINE_PARENT:
5421                 if (commit->has_parents)
5422                         break;
5423                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5424                 break;
5426         case LINE_AUTHOR:
5427         {
5428                 /* Parse author lines where the name may be empty:
5429                  *      author  <email@address.tld> 1138474660 +0100
5430                  */
5431                 char *ident = line + STRING_SIZE("author ");
5432                 char *nameend = strchr(ident, '<');
5433                 char *emailend = strchr(ident, '>');
5435                 if (!nameend || !emailend)
5436                         break;
5438                 update_rev_graph(graph);
5439                 graph = graph->next;
5441                 *nameend = *emailend = 0;
5442                 ident = chomp_string(ident);
5443                 if (!*ident) {
5444                         ident = chomp_string(nameend + 1);
5445                         if (!*ident)
5446                                 ident = "Unknown";
5447                 }
5449                 string_ncopy(commit->author, ident, strlen(ident));
5451                 /* Parse epoch and timezone */
5452                 if (emailend[1] == ' ') {
5453                         char *secs = emailend + 2;
5454                         char *zone = strchr(secs, ' ');
5455                         time_t time = (time_t) atol(secs);
5457                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5458                                 long tz;
5460                                 zone++;
5461                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5462                                 tz += ('0' - zone[2]) * 60 * 60;
5463                                 tz += ('0' - zone[3]) * 60;
5464                                 tz += ('0' - zone[4]) * 60;
5466                                 if (zone[0] == '-')
5467                                         tz = -tz;
5469                                 time -= tz;
5470                         }
5472                         gmtime_r(&time, &commit->time);
5473                 }
5474                 break;
5475         }
5476         default:
5477                 /* Fill in the commit title if it has not already been set. */
5478                 if (commit->title[0])
5479                         break;
5481                 /* Require titles to start with a non-space character at the
5482                  * offset used by git log. */
5483                 if (strncmp(line, "    ", 4))
5484                         break;
5485                 line += 4;
5486                 /* Well, if the title starts with a whitespace character,
5487                  * try to be forgiving.  Otherwise we end up with no title. */
5488                 while (isspace(*line))
5489                         line++;
5490                 if (*line == '\0')
5491                         break;
5492                 /* FIXME: More graceful handling of titles; append "..." to
5493                  * shortened titles, etc. */
5495                 string_ncopy(commit->title, line, strlen(line));
5496         }
5498         return TRUE;
5501 static enum request
5502 main_request(struct view *view, enum request request, struct line *line)
5504         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5506         switch (request) {
5507         case REQ_ENTER:
5508                 open_view(view, REQ_VIEW_DIFF, flags);
5509                 break;
5510         case REQ_REFRESH:
5511                 load_refs();
5512                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5513                 break;
5514         default:
5515                 return request;
5516         }
5518         return REQ_NONE;
5521 static bool
5522 grep_refs(struct ref **refs, regex_t *regex)
5524         regmatch_t pmatch;
5525         size_t i = 0;
5527         if (!refs)
5528                 return FALSE;
5529         do {
5530                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5531                         return TRUE;
5532         } while (refs[i++]->next);
5534         return FALSE;
5537 static bool
5538 main_grep(struct view *view, struct line *line)
5540         struct commit *commit = line->data;
5541         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5542         char buf[DATE_COLS + 1];
5543         regmatch_t pmatch;
5545         for (state = S_TITLE; state < S_END; state++) {
5546                 char *text;
5548                 switch (state) {
5549                 case S_TITLE:   text = commit->title;   break;
5550                 case S_AUTHOR:
5551                         if (!opt_author)
5552                                 continue;
5553                         text = commit->author;
5554                         break;
5555                 case S_DATE:
5556                         if (!opt_date)
5557                                 continue;
5558                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5559                                 continue;
5560                         text = buf;
5561                         break;
5562                 case S_REFS:
5563                         if (!opt_show_refs)
5564                                 continue;
5565                         if (grep_refs(commit->refs, view->regex) == TRUE)
5566                                 return TRUE;
5567                         continue;
5568                 default:
5569                         return FALSE;
5570                 }
5572                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5573                         return TRUE;
5574         }
5576         return FALSE;
5579 static void
5580 main_select(struct view *view, struct line *line)
5582         struct commit *commit = line->data;
5584         string_copy_rev(view->ref, commit->id);
5585         string_copy_rev(ref_commit, view->ref);
5588 static struct view_ops main_ops = {
5589         "commit",
5590         main_argv,
5591         NULL,
5592         main_read,
5593         main_draw,
5594         main_request,
5595         main_grep,
5596         main_select,
5597 };
5600 /*
5601  * Unicode / UTF-8 handling
5602  *
5603  * NOTE: Much of the following code for dealing with unicode is derived from
5604  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5605  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5606  */
5608 /* I've (over)annotated a lot of code snippets because I am not entirely
5609  * confident that the approach taken by this small UTF-8 interface is correct.
5610  * --jonas */
5612 static inline int
5613 unicode_width(unsigned long c)
5615         if (c >= 0x1100 &&
5616            (c <= 0x115f                         /* Hangul Jamo */
5617             || c == 0x2329
5618             || c == 0x232a
5619             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5620                                                 /* CJK ... Yi */
5621             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5622             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5623             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5624             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5625             || (c >= 0xffe0  && c <= 0xffe6)
5626             || (c >= 0x20000 && c <= 0x2fffd)
5627             || (c >= 0x30000 && c <= 0x3fffd)))
5628                 return 2;
5630         if (c == '\t')
5631                 return opt_tab_size;
5633         return 1;
5636 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5637  * Illegal bytes are set one. */
5638 static const unsigned char utf8_bytes[256] = {
5639         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,
5640         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,
5641         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,
5642         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,
5643         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,
5644         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,
5645         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,
5646         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,
5647 };
5649 /* Decode UTF-8 multi-byte representation into a unicode character. */
5650 static inline unsigned long
5651 utf8_to_unicode(const char *string, size_t length)
5653         unsigned long unicode;
5655         switch (length) {
5656         case 1:
5657                 unicode  =   string[0];
5658                 break;
5659         case 2:
5660                 unicode  =  (string[0] & 0x1f) << 6;
5661                 unicode +=  (string[1] & 0x3f);
5662                 break;
5663         case 3:
5664                 unicode  =  (string[0] & 0x0f) << 12;
5665                 unicode += ((string[1] & 0x3f) << 6);
5666                 unicode +=  (string[2] & 0x3f);
5667                 break;
5668         case 4:
5669                 unicode  =  (string[0] & 0x0f) << 18;
5670                 unicode += ((string[1] & 0x3f) << 12);
5671                 unicode += ((string[2] & 0x3f) << 6);
5672                 unicode +=  (string[3] & 0x3f);
5673                 break;
5674         case 5:
5675                 unicode  =  (string[0] & 0x0f) << 24;
5676                 unicode += ((string[1] & 0x3f) << 18);
5677                 unicode += ((string[2] & 0x3f) << 12);
5678                 unicode += ((string[3] & 0x3f) << 6);
5679                 unicode +=  (string[4] & 0x3f);
5680                 break;
5681         case 6:
5682                 unicode  =  (string[0] & 0x01) << 30;
5683                 unicode += ((string[1] & 0x3f) << 24);
5684                 unicode += ((string[2] & 0x3f) << 18);
5685                 unicode += ((string[3] & 0x3f) << 12);
5686                 unicode += ((string[4] & 0x3f) << 6);
5687                 unicode +=  (string[5] & 0x3f);
5688                 break;
5689         default:
5690                 die("Invalid unicode length");
5691         }
5693         /* Invalid characters could return the special 0xfffd value but NUL
5694          * should be just as good. */
5695         return unicode > 0xffff ? 0 : unicode;
5698 /* Calculates how much of string can be shown within the given maximum width
5699  * and sets trimmed parameter to non-zero value if all of string could not be
5700  * shown. If the reserve flag is TRUE, it will reserve at least one
5701  * trailing character, which can be useful when drawing a delimiter.
5702  *
5703  * Returns the number of bytes to output from string to satisfy max_width. */
5704 static size_t
5705 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5707         const char *start = string;
5708         const char *end = strchr(string, '\0');
5709         unsigned char last_bytes = 0;
5710         size_t last_ucwidth = 0;
5712         *width = 0;
5713         *trimmed = 0;
5715         while (string < end) {
5716                 int c = *(unsigned char *) string;
5717                 unsigned char bytes = utf8_bytes[c];
5718                 size_t ucwidth;
5719                 unsigned long unicode;
5721                 if (string + bytes > end)
5722                         break;
5724                 /* Change representation to figure out whether
5725                  * it is a single- or double-width character. */
5727                 unicode = utf8_to_unicode(string, bytes);
5728                 /* FIXME: Graceful handling of invalid unicode character. */
5729                 if (!unicode)
5730                         break;
5732                 ucwidth = unicode_width(unicode);
5733                 *width  += ucwidth;
5734                 if (*width > max_width) {
5735                         *trimmed = 1;
5736                         *width -= ucwidth;
5737                         if (reserve && *width == max_width) {
5738                                 string -= last_bytes;
5739                                 *width -= last_ucwidth;
5740                         }
5741                         break;
5742                 }
5744                 string  += bytes;
5745                 last_bytes = bytes;
5746                 last_ucwidth = ucwidth;
5747         }
5749         return string - start;
5753 /*
5754  * Status management
5755  */
5757 /* Whether or not the curses interface has been initialized. */
5758 static bool cursed = FALSE;
5760 /* The status window is used for polling keystrokes. */
5761 static WINDOW *status_win;
5763 static bool status_empty = TRUE;
5765 /* Update status and title window. */
5766 static void
5767 report(const char *msg, ...)
5769         struct view *view = display[current_view];
5771         if (input_mode)
5772                 return;
5774         if (!view) {
5775                 char buf[SIZEOF_STR];
5776                 va_list args;
5778                 va_start(args, msg);
5779                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5780                         buf[sizeof(buf) - 1] = 0;
5781                         buf[sizeof(buf) - 2] = '.';
5782                         buf[sizeof(buf) - 3] = '.';
5783                         buf[sizeof(buf) - 4] = '.';
5784                 }
5785                 va_end(args);
5786                 die("%s", buf);
5787         }
5789         if (!status_empty || *msg) {
5790                 va_list args;
5792                 va_start(args, msg);
5794                 wmove(status_win, 0, 0);
5795                 if (*msg) {
5796                         vwprintw(status_win, msg, args);
5797                         status_empty = FALSE;
5798                 } else {
5799                         status_empty = TRUE;
5800                 }
5801                 wclrtoeol(status_win);
5802                 wrefresh(status_win);
5804                 va_end(args);
5805         }
5807         update_view_title(view);
5808         update_display_cursor(view);
5811 /* Controls when nodelay should be in effect when polling user input. */
5812 static void
5813 set_nonblocking_input(bool loading)
5815         static unsigned int loading_views;
5817         if ((loading == FALSE && loading_views-- == 1) ||
5818             (loading == TRUE  && loading_views++ == 0))
5819                 nodelay(status_win, loading);
5822 static void
5823 init_display(void)
5825         int x, y;
5827         /* Initialize the curses library */
5828         if (isatty(STDIN_FILENO)) {
5829                 cursed = !!initscr();
5830                 opt_tty = stdin;
5831         } else {
5832                 /* Leave stdin and stdout alone when acting as a pager. */
5833                 opt_tty = fopen("/dev/tty", "r+");
5834                 if (!opt_tty)
5835                         die("Failed to open /dev/tty");
5836                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5837         }
5839         if (!cursed)
5840                 die("Failed to initialize curses");
5842         nonl();         /* Tell curses not to do NL->CR/NL on output */
5843         cbreak();       /* Take input chars one at a time, no wait for \n */
5844         noecho();       /* Don't echo input */
5845         leaveok(stdscr, TRUE);
5847         if (has_colors())
5848                 init_colors();
5850         getmaxyx(stdscr, y, x);
5851         status_win = newwin(1, 0, y - 1, 0);
5852         if (!status_win)
5853                 die("Failed to create status window");
5855         /* Enable keyboard mapping */
5856         keypad(status_win, TRUE);
5857         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5859         TABSIZE = opt_tab_size;
5860         if (opt_line_graphics) {
5861                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5862         }
5865 static bool
5866 prompt_yesno(const char *prompt)
5868         enum { WAIT, STOP, CANCEL  } status = WAIT;
5869         bool answer = FALSE;
5871         while (status == WAIT) {
5872                 struct view *view;
5873                 int i, key;
5875                 input_mode = TRUE;
5877                 foreach_view (view, i)
5878                         update_view(view);
5880                 input_mode = FALSE;
5882                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5883                 wclrtoeol(status_win);
5885                 /* Refresh, accept single keystroke of input */
5886                 key = wgetch(status_win);
5887                 switch (key) {
5888                 case ERR:
5889                         break;
5891                 case 'y':
5892                 case 'Y':
5893                         answer = TRUE;
5894                         status = STOP;
5895                         break;
5897                 case KEY_ESC:
5898                 case KEY_RETURN:
5899                 case KEY_ENTER:
5900                 case KEY_BACKSPACE:
5901                 case 'n':
5902                 case 'N':
5903                 case '\n':
5904                 default:
5905                         answer = FALSE;
5906                         status = CANCEL;
5907                 }
5908         }
5910         /* Clear the status window */
5911         status_empty = FALSE;
5912         report("");
5914         return answer;
5917 static char *
5918 read_prompt(const char *prompt)
5920         enum { READING, STOP, CANCEL } status = READING;
5921         static char buf[SIZEOF_STR];
5922         int pos = 0;
5924         while (status == READING) {
5925                 struct view *view;
5926                 int i, key;
5928                 input_mode = TRUE;
5930                 foreach_view (view, i)
5931                         update_view(view);
5933                 input_mode = FALSE;
5935                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5936                 wclrtoeol(status_win);
5938                 /* Refresh, accept single keystroke of input */
5939                 key = wgetch(status_win);
5940                 switch (key) {
5941                 case KEY_RETURN:
5942                 case KEY_ENTER:
5943                 case '\n':
5944                         status = pos ? STOP : CANCEL;
5945                         break;
5947                 case KEY_BACKSPACE:
5948                         if (pos > 0)
5949                                 pos--;
5950                         else
5951                                 status = CANCEL;
5952                         break;
5954                 case KEY_ESC:
5955                         status = CANCEL;
5956                         break;
5958                 case ERR:
5959                         break;
5961                 default:
5962                         if (pos >= sizeof(buf)) {
5963                                 report("Input string too long");
5964                                 return NULL;
5965                         }
5967                         if (isprint(key))
5968                                 buf[pos++] = (char) key;
5969                 }
5970         }
5972         /* Clear the status window */
5973         status_empty = FALSE;
5974         report("");
5976         if (status == CANCEL)
5977                 return NULL;
5979         buf[pos++] = 0;
5981         return buf;
5984 /*
5985  * Repository references
5986  */
5988 static struct ref *refs = NULL;
5989 static size_t refs_alloc = 0;
5990 static size_t refs_size = 0;
5992 /* Id <-> ref store */
5993 static struct ref ***id_refs = NULL;
5994 static size_t id_refs_alloc = 0;
5995 static size_t id_refs_size = 0;
5997 static int
5998 compare_refs(const void *ref1_, const void *ref2_)
6000         const struct ref *ref1 = *(const struct ref **)ref1_;
6001         const struct ref *ref2 = *(const struct ref **)ref2_;
6003         if (ref1->tag != ref2->tag)
6004                 return ref2->tag - ref1->tag;
6005         if (ref1->ltag != ref2->ltag)
6006                 return ref2->ltag - ref2->ltag;
6007         if (ref1->head != ref2->head)
6008                 return ref2->head - ref1->head;
6009         if (ref1->tracked != ref2->tracked)
6010                 return ref2->tracked - ref1->tracked;
6011         if (ref1->remote != ref2->remote)
6012                 return ref2->remote - ref1->remote;
6013         return strcmp(ref1->name, ref2->name);
6016 static struct ref **
6017 get_refs(const char *id)
6019         struct ref ***tmp_id_refs;
6020         struct ref **ref_list = NULL;
6021         size_t ref_list_alloc = 0;
6022         size_t ref_list_size = 0;
6023         size_t i;
6025         for (i = 0; i < id_refs_size; i++)
6026                 if (!strcmp(id, id_refs[i][0]->id))
6027                         return id_refs[i];
6029         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6030                                     sizeof(*id_refs));
6031         if (!tmp_id_refs)
6032                 return NULL;
6034         id_refs = tmp_id_refs;
6036         for (i = 0; i < refs_size; i++) {
6037                 struct ref **tmp;
6039                 if (strcmp(id, refs[i].id))
6040                         continue;
6042                 tmp = realloc_items(ref_list, &ref_list_alloc,
6043                                     ref_list_size + 1, sizeof(*ref_list));
6044                 if (!tmp) {
6045                         if (ref_list)
6046                                 free(ref_list);
6047                         return NULL;
6048                 }
6050                 ref_list = tmp;
6051                 ref_list[ref_list_size] = &refs[i];
6052                 /* XXX: The properties of the commit chains ensures that we can
6053                  * safely modify the shared ref. The repo references will
6054                  * always be similar for the same id. */
6055                 ref_list[ref_list_size]->next = 1;
6057                 ref_list_size++;
6058         }
6060         if (ref_list) {
6061                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6062                 ref_list[ref_list_size - 1]->next = 0;
6063                 id_refs[id_refs_size++] = ref_list;
6064         }
6066         return ref_list;
6069 static int
6070 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6072         struct ref *ref;
6073         bool tag = FALSE;
6074         bool ltag = FALSE;
6075         bool remote = FALSE;
6076         bool tracked = FALSE;
6077         bool check_replace = FALSE;
6078         bool head = FALSE;
6080         if (!prefixcmp(name, "refs/tags/")) {
6081                 if (!suffixcmp(name, namelen, "^{}")) {
6082                         namelen -= 3;
6083                         name[namelen] = 0;
6084                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6085                                 check_replace = TRUE;
6086                 } else {
6087                         ltag = TRUE;
6088                 }
6090                 tag = TRUE;
6091                 namelen -= STRING_SIZE("refs/tags/");
6092                 name    += STRING_SIZE("refs/tags/");
6094         } else if (!prefixcmp(name, "refs/remotes/")) {
6095                 remote = TRUE;
6096                 namelen -= STRING_SIZE("refs/remotes/");
6097                 name    += STRING_SIZE("refs/remotes/");
6098                 tracked  = !strcmp(opt_remote, name);
6100         } else if (!prefixcmp(name, "refs/heads/")) {
6101                 namelen -= STRING_SIZE("refs/heads/");
6102                 name    += STRING_SIZE("refs/heads/");
6103                 head     = !strncmp(opt_head, name, namelen);
6105         } else if (!strcmp(name, "HEAD")) {
6106                 string_ncopy(opt_head_rev, id, idlen);
6107                 return OK;
6108         }
6110         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6111                 /* it's an annotated tag, replace the previous sha1 with the
6112                  * resolved commit id; relies on the fact git-ls-remote lists
6113                  * the commit id of an annotated tag right before the commit id
6114                  * it points to. */
6115                 refs[refs_size - 1].ltag = ltag;
6116                 string_copy_rev(refs[refs_size - 1].id, id);
6118                 return OK;
6119         }
6120         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6121         if (!refs)
6122                 return ERR;
6124         ref = &refs[refs_size++];
6125         ref->name = malloc(namelen + 1);
6126         if (!ref->name)
6127                 return ERR;
6129         strncpy(ref->name, name, namelen);
6130         ref->name[namelen] = 0;
6131         ref->head = head;
6132         ref->tag = tag;
6133         ref->ltag = ltag;
6134         ref->remote = remote;
6135         ref->tracked = tracked;
6136         string_copy_rev(ref->id, id);
6138         return OK;
6141 static int
6142 load_refs(void)
6144         const char *cmd_env = getenv("TIG_LS_REMOTE");
6145         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6147         if (!*opt_git_dir)
6148                 return OK;
6150         while (refs_size > 0)
6151                 free(refs[--refs_size].name);
6152         while (id_refs_size > 0)
6153                 free(id_refs[--id_refs_size]);
6155         return read_properties(popen(cmd, "r"), "\t", read_ref);
6158 static int
6159 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6161         if (!strcmp(name, "i18n.commitencoding"))
6162                 string_ncopy(opt_encoding, value, valuelen);
6164         if (!strcmp(name, "core.editor"))
6165                 string_ncopy(opt_editor, value, valuelen);
6167         /* branch.<head>.remote */
6168         if (*opt_head &&
6169             !strncmp(name, "branch.", 7) &&
6170             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6171             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6172                 string_ncopy(opt_remote, value, valuelen);
6174         if (*opt_head && *opt_remote &&
6175             !strncmp(name, "branch.", 7) &&
6176             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6177             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6178                 size_t from = strlen(opt_remote);
6180                 if (!prefixcmp(value, "refs/heads/")) {
6181                         value += STRING_SIZE("refs/heads/");
6182                         valuelen -= STRING_SIZE("refs/heads/");
6183                 }
6185                 if (!string_format_from(opt_remote, &from, "/%s", value))
6186                         opt_remote[0] = 0;
6187         }
6189         return OK;
6192 static int
6193 load_git_config(void)
6195         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6196                                "=", read_repo_config_option);
6199 static int
6200 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6202         if (!opt_git_dir[0]) {
6203                 string_ncopy(opt_git_dir, name, namelen);
6205         } else if (opt_is_inside_work_tree == -1) {
6206                 /* This can be 3 different values depending on the
6207                  * version of git being used. If git-rev-parse does not
6208                  * understand --is-inside-work-tree it will simply echo
6209                  * the option else either "true" or "false" is printed.
6210                  * Default to true for the unknown case. */
6211                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6213         } else if (opt_cdup[0] == ' ') {
6214                 string_ncopy(opt_cdup, name, namelen);
6215         } else {
6216                 if (!prefixcmp(name, "refs/heads/")) {
6217                         namelen -= STRING_SIZE("refs/heads/");
6218                         name    += STRING_SIZE("refs/heads/");
6219                         string_ncopy(opt_head, name, namelen);
6220                 }
6221         }
6223         return OK;
6226 static int
6227 load_repo_info(void)
6229         int result;
6230         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6231                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6233         /* XXX: The line outputted by "--show-cdup" can be empty so
6234          * initialize it to something invalid to make it possible to
6235          * detect whether it has been set or not. */
6236         opt_cdup[0] = ' ';
6238         result = read_properties(pipe, "=", read_repo_info);
6239         if (opt_cdup[0] == ' ')
6240                 opt_cdup[0] = 0;
6242         return result;
6245 static int
6246 read_properties(FILE *pipe, const char *separators,
6247                 int (*read_property)(char *, size_t, char *, size_t))
6249         char buffer[BUFSIZ];
6250         char *name;
6251         int state = OK;
6253         if (!pipe)
6254                 return ERR;
6256         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6257                 char *value;
6258                 size_t namelen;
6259                 size_t valuelen;
6261                 name = chomp_string(name);
6262                 namelen = strcspn(name, separators);
6264                 if (name[namelen]) {
6265                         name[namelen] = 0;
6266                         value = chomp_string(name + namelen + 1);
6267                         valuelen = strlen(value);
6269                 } else {
6270                         value = "";
6271                         valuelen = 0;
6272                 }
6274                 state = read_property(name, namelen, value, valuelen);
6275         }
6277         if (state != ERR && ferror(pipe))
6278                 state = ERR;
6280         pclose(pipe);
6282         return state;
6286 /*
6287  * Main
6288  */
6290 static void __NORETURN
6291 quit(int sig)
6293         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6294         if (cursed)
6295                 endwin();
6296         exit(0);
6299 static void __NORETURN
6300 die(const char *err, ...)
6302         va_list args;
6304         endwin();
6306         va_start(args, err);
6307         fputs("tig: ", stderr);
6308         vfprintf(stderr, err, args);
6309         fputs("\n", stderr);
6310         va_end(args);
6312         exit(1);
6315 static void
6316 warn(const char *msg, ...)
6318         va_list args;
6320         va_start(args, msg);
6321         fputs("tig warning: ", stderr);
6322         vfprintf(stderr, msg, args);
6323         fputs("\n", stderr);
6324         va_end(args);
6327 int
6328 main(int argc, const char *argv[])
6330         struct view *view;
6331         enum request request;
6332         size_t i;
6334         signal(SIGINT, quit);
6336         if (setlocale(LC_ALL, "")) {
6337                 char *codeset = nl_langinfo(CODESET);
6339                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6340         }
6342         if (load_repo_info() == ERR)
6343                 die("Failed to load repo info.");
6345         if (load_options() == ERR)
6346                 die("Failed to load user config.");
6348         if (load_git_config() == ERR)
6349                 die("Failed to load repo config.");
6351         request = parse_options(argc, argv);
6352         if (request == REQ_NONE)
6353                 return 0;
6355         /* Require a git repository unless when running in pager mode. */
6356         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6357                 die("Not a git repository");
6359         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6360                 opt_utf8 = FALSE;
6362         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6363                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6364                 if (opt_iconv == ICONV_NONE)
6365                         die("Failed to initialize character set conversion");
6366         }
6368         if (load_refs() == ERR)
6369                 die("Failed to load refs.");
6371         foreach_view (view, i)
6372                 argv_from_env(view->ops->argv, view->cmd_env);
6374         init_display();
6376         while (view_driver(display[current_view], request)) {
6377                 int key;
6378                 int i;
6380                 foreach_view (view, i)
6381                         update_view(view);
6382                 view = display[current_view];
6384                 /* Refresh, accept single keystroke of input */
6385                 key = wgetch(status_win);
6387                 /* wgetch() with nodelay() enabled returns ERR when there's no
6388                  * input. */
6389                 if (key == ERR) {
6390                         request = REQ_NONE;
6391                         continue;
6392                 }
6394                 request = get_keybinding(view->keymap, key);
6396                 /* Some low-level request handling. This keeps access to
6397                  * status_win restricted. */
6398                 switch (request) {
6399                 case REQ_PROMPT:
6400                 {
6401                         char *cmd = read_prompt(":");
6403                         if (cmd) {
6404                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6405                                 const char *argv[SIZEOF_ARG] = { "git" };
6406                                 int argc = 1;
6408                                 /* When running random commands, initially show the
6409                                  * command in the title. However, it maybe later be
6410                                  * overwritten if a commit line is selected. */
6411                                 string_ncopy(next->ref, cmd, strlen(cmd));
6413                                 if (!argv_from_string(argv, &argc, cmd)) {
6414                                         report("Too many arguments");
6415                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6416                                         report("Failed to format command");
6417                                 } else {
6418                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6419                                 }
6420                         }
6422                         request = REQ_NONE;
6423                         break;
6424                 }
6425                 case REQ_SEARCH:
6426                 case REQ_SEARCH_BACK:
6427                 {
6428                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6429                         char *search = read_prompt(prompt);
6431                         if (search)
6432                                 string_ncopy(opt_search, search, strlen(search));
6433                         else
6434                                 request = REQ_NONE;
6435                         break;
6436                 }
6437                 case REQ_SCREEN_RESIZE:
6438                 {
6439                         int height, width;
6441                         getmaxyx(stdscr, height, width);
6443                         /* Resize the status view and let the view driver take
6444                          * care of resizing the displayed views. */
6445                         wresize(status_win, 1, width);
6446                         mvwin(status_win, height - 1, 0);
6447                         wrefresh(status_win);
6448                         break;
6449                 }
6450                 default:
6451                         break;
6452                 }
6453         }
6455         quit(0);
6457         return 0;