Use warn() for warnings emitted during config file loading
[tig.git] / tig.c
blobfa03109e7fa2836835d309c9268d408cfa364bf4
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
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.
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.
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE 8
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB '\t'
125 #define KEY_RETURN '\r'
126 #define KEY_ESC 27
129 struct ref {
130 char *name; /* Ref name; tag or head names are shortened. */
131 char id[SIZEOF_REV]; /* Commit SHA1 ID */
132 unsigned int head:1; /* Is it the current HEAD? */
133 unsigned int tag:1; /* Is it a tag? */
134 unsigned int ltag:1; /* If so, is the tag local? */
135 unsigned int remote:1; /* Is it a remote ref? */
136 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137 unsigned int next:1; /* For ref lists: are there more refs? */
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143 FORMAT_ALL, /* Perform replacement in all arguments. */
144 FORMAT_DASH, /* Perform replacement up until "--". */
145 FORMAT_NONE /* No replacement should be performed. */
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151 const char *name;
152 int namelen;
153 int value;
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158 int *value, const char *name, int namelen)
161 int i;
163 for (i = 0; i < map_size; i++)
164 if (namelen == map[i].namelen &&
165 !strncasecmp(name, map[i].name, namelen)) {
166 *value = map[i].value;
167 return OK;
170 return ERR;
173 enum input_status {
174 INPUT_OK,
175 INPUT_SKIP,
176 INPUT_STOP,
177 INPUT_CANCEL
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
186 * String helpers
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
216 int namelen;
218 while (isspace(*name))
219 name++;
221 namelen = strlen(name) - 1;
222 while (namelen > 0 && isspace(name[namelen]))
223 name[namelen--] = 0;
225 return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231 va_list args;
232 size_t pos = bufpos ? *bufpos : 0;
234 va_start(args, fmt);
235 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236 va_end(args);
238 if (bufpos)
239 *bufpos = pos;
241 return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245 string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248 string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253 size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257 /* Diff-Header == DIFF_HEADER */
258 for (i = 0; i < len; i++) {
259 if (toupper(str1[i]) == toupper(str2[i]))
260 continue;
262 if (string_enum_sep(str1[i]) &&
263 string_enum_sep(str2[i]))
264 continue;
266 return str1[i] - str2[i];
269 return 0;
272 #define prefixcmp(str1, str2) \
273 strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278 size_t len = slen >= 0 ? slen : strlen(str);
279 size_t suffixlen = strlen(suffix);
281 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288 int valuelen;
290 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291 bool advance = cmd[valuelen] != 0;
293 cmd[valuelen] = 0;
294 argv[(*argc)++] = chomp_string(cmd);
295 cmd = chomp_string(cmd + valuelen + advance);
298 if (*argc < SIZEOF_ARG)
299 argv[*argc] = NULL;
300 return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306 char *env = argv ? getenv(name) : NULL;
307 int argc = 0;
309 if (env && *env)
310 env = strdup(env);
311 if (env && !argv_from_string(argv, &argc, env))
312 die("Too many arguments in the `%s` environment variable", name);
317 * Executing external commands.
320 enum io_type {
321 IO_FD, /* File descriptor based IO. */
322 IO_BG, /* Execute command in the background. */
323 IO_FG, /* Execute command with same std{in,out,err}. */
324 IO_RD, /* Read only fork+exec IO. */
325 IO_WR, /* Write only fork+exec IO. */
326 IO_AP, /* Append fork+exec output to file. */
329 struct io {
330 enum io_type type; /* The requested type of pipe. */
331 const char *dir; /* Directory from which to execute. */
332 pid_t pid; /* Pipe for reading or writing. */
333 int pipe; /* Pipe end for reading or writing. */
334 int error; /* Error status. */
335 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
336 char *buf; /* Read buffer. */
337 size_t bufalloc; /* Allocated buffer size. */
338 size_t bufsize; /* Buffer content size. */
339 char *bufpos; /* Current buffer position. */
340 unsigned int eof:1; /* Has end of file been reached. */
343 static void
344 reset_io(struct io *io)
346 io->pipe = -1;
347 io->pid = 0;
348 io->buf = io->bufpos = NULL;
349 io->bufalloc = io->bufsize = 0;
350 io->error = 0;
351 io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357 reset_io(io);
358 io->type = type;
359 io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364 enum format_flags flags)
366 init_io(io, dir, IO_RD);
367 return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373 init_io(io, NULL, IO_FD);
374 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375 return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387 pid_t pid = io->pid;
389 if (io->pipe != -1)
390 close(io->pipe);
391 free(io->buf);
392 reset_io(io);
394 while (pid > 0) {
395 int status;
396 pid_t waiting = waitpid(pid, &status, 0);
398 if (waiting < 0) {
399 if (errno == EINTR)
400 continue;
401 report("waitpid failed (%s)", strerror(errno));
402 return FALSE;
405 return waiting == pid &&
406 !WIFSIGNALED(status) &&
407 WIFEXITED(status) &&
408 !WEXITSTATUS(status);
411 return TRUE;
414 static bool
415 start_io(struct io *io)
417 int pipefds[2] = { -1, -1 };
419 if (io->type == IO_FD)
420 return TRUE;
422 if ((io->type == IO_RD || io->type == IO_WR) &&
423 pipe(pipefds) < 0)
424 return FALSE;
425 else if (io->type == IO_AP)
426 pipefds[1] = io->pipe;
428 if ((io->pid = fork())) {
429 if (pipefds[!(io->type == IO_WR)] != -1)
430 close(pipefds[!(io->type == IO_WR)]);
431 if (io->pid != -1) {
432 io->pipe = pipefds[!!(io->type == IO_WR)];
433 return TRUE;
436 } else {
437 if (io->type != IO_FG) {
438 int devnull = open("/dev/null", O_RDWR);
439 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
440 int writefd = (io->type == IO_RD || io->type == IO_AP)
441 ? pipefds[1] : devnull;
443 dup2(readfd, STDIN_FILENO);
444 dup2(writefd, STDOUT_FILENO);
445 dup2(devnull, STDERR_FILENO);
447 close(devnull);
448 if (pipefds[0] != -1)
449 close(pipefds[0]);
450 if (pipefds[1] != -1)
451 close(pipefds[1]);
454 if (io->dir && *io->dir && chdir(io->dir) == -1)
455 die("Failed to change directory: %s", strerror(errno));
457 execvp(io->argv[0], (char *const*) io->argv);
458 die("Failed to execute program: %s", strerror(errno));
461 if (pipefds[!!(io->type == IO_WR)] != -1)
462 close(pipefds[!!(io->type == IO_WR)]);
463 return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469 init_io(io, dir, type);
470 if (!format_argv(io->argv, argv, FORMAT_NONE))
471 return FALSE;
472 return start_io(io);
475 static int
476 run_io_do(struct io *io)
478 return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484 struct io io = {};
486 init_io(&io, NULL, IO_BG);
487 if (!format_argv(io.argv, argv, FORMAT_NONE))
488 return FALSE;
489 return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495 struct io io = {};
497 init_io(&io, dir, IO_FG);
498 if (!format_argv(io.argv, argv, FORMAT_NONE))
499 return FALSE;
500 return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506 struct io io = {};
508 init_io(&io, NULL, IO_AP);
509 io.pipe = fd;
510 if (format_argv(io.argv, argv, flags))
511 return run_io_do(&io);
512 close(fd);
513 return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519 return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525 return io->eof;
528 static int
529 io_error(struct io *io)
531 return io->error;
534 static bool
535 io_strerror(struct io *io)
537 return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543 struct timeval tv = { 0, 500 };
544 fd_set fds;
546 FD_ZERO(&fds);
547 FD_SET(io->pipe, &fds);
549 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555 do {
556 ssize_t readsize = read(io->pipe, buf, bufsize);
558 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559 continue;
560 else if (readsize == -1)
561 io->error = errno;
562 else if (readsize == 0)
563 io->eof = 1;
564 return readsize;
565 } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571 char *eol;
572 ssize_t readsize;
574 if (!io->buf) {
575 io->buf = io->bufpos = malloc(BUFSIZ);
576 if (!io->buf)
577 return NULL;
578 io->bufalloc = BUFSIZ;
579 io->bufsize = 0;
582 while (TRUE) {
583 if (io->bufsize > 0) {
584 eol = memchr(io->bufpos, c, io->bufsize);
585 if (eol) {
586 char *line = io->bufpos;
588 *eol = 0;
589 io->bufpos = eol + 1;
590 io->bufsize -= io->bufpos - line;
591 return line;
595 if (io_eof(io)) {
596 if (io->bufsize) {
597 io->bufpos[io->bufsize] = 0;
598 io->bufsize = 0;
599 return io->bufpos;
601 return NULL;
604 if (!can_read)
605 return NULL;
607 if (io->bufsize > 0 && io->bufpos > io->buf)
608 memmove(io->buf, io->bufpos, io->bufsize);
610 io->bufpos = io->buf;
611 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612 if (io_error(io))
613 return NULL;
614 io->bufsize += readsize;
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621 size_t written = 0;
623 while (!io_error(io) && written < bufsize) {
624 ssize_t size;
626 size = write(io->pipe, buf + written, bufsize - written);
627 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628 continue;
629 else if (size == -1)
630 io->error = errno;
631 else
632 written += size;
635 return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641 struct io io = {};
642 bool error;
644 if (!run_io_rd(&io, argv, FORMAT_NONE))
645 return FALSE;
647 io.buf = io.bufpos = buf;
648 io.bufalloc = bufsize;
649 error = !io_get(&io, '\n', TRUE) && io_error(&io);
650 io.buf = NULL;
652 return done_io(&io) || error;
655 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
658 * User requests
661 #define REQ_INFO \
662 /* XXX: Keep the view request first and in sync with views[]. */ \
663 REQ_GROUP("View switching") \
664 REQ_(VIEW_MAIN, "Show main view"), \
665 REQ_(VIEW_DIFF, "Show diff view"), \
666 REQ_(VIEW_LOG, "Show log view"), \
667 REQ_(VIEW_TREE, "Show tree view"), \
668 REQ_(VIEW_BLOB, "Show blob view"), \
669 REQ_(VIEW_BLAME, "Show blame view"), \
670 REQ_(VIEW_HELP, "Show help page"), \
671 REQ_(VIEW_PAGER, "Show pager view"), \
672 REQ_(VIEW_STATUS, "Show status view"), \
673 REQ_(VIEW_STAGE, "Show stage view"), \
675 REQ_GROUP("View manipulation") \
676 REQ_(ENTER, "Enter current line and scroll"), \
677 REQ_(NEXT, "Move to next"), \
678 REQ_(PREVIOUS, "Move to previous"), \
679 REQ_(PARENT, "Move to parent"), \
680 REQ_(VIEW_NEXT, "Move focus to next view"), \
681 REQ_(REFRESH, "Reload and refresh"), \
682 REQ_(MAXIMIZE, "Maximize the current view"), \
683 REQ_(VIEW_CLOSE, "Close the current view"), \
684 REQ_(QUIT, "Close all views and quit"), \
686 REQ_GROUP("View specific requests") \
687 REQ_(STATUS_UPDATE, "Update file status"), \
688 REQ_(STATUS_REVERT, "Revert file changes"), \
689 REQ_(STATUS_MERGE, "Merge file using external tool"), \
690 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
692 REQ_GROUP("Cursor navigation") \
693 REQ_(MOVE_UP, "Move cursor one line up"), \
694 REQ_(MOVE_DOWN, "Move cursor one line down"), \
695 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
696 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
697 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
698 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
700 REQ_GROUP("Scrolling") \
701 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
702 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
703 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
704 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
706 REQ_GROUP("Searching") \
707 REQ_(SEARCH, "Search the view"), \
708 REQ_(SEARCH_BACK, "Search backwards in the view"), \
709 REQ_(FIND_NEXT, "Find next search match"), \
710 REQ_(FIND_PREV, "Find previous search match"), \
712 REQ_GROUP("Option manipulation") \
713 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
714 REQ_(TOGGLE_DATE, "Toggle date display"), \
715 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
716 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
717 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
719 REQ_GROUP("Misc") \
720 REQ_(PROMPT, "Bring up the prompt"), \
721 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
722 REQ_(SHOW_VERSION, "Show version information"), \
723 REQ_(STOP_LOADING, "Stop all loading views"), \
724 REQ_(EDIT, "Open in editor"), \
725 REQ_(NONE, "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733 /* Offset all requests to avoid conflicts with ncurses getch values. */
734 REQ_OFFSET = KEY_MAX + 1,
735 REQ_INFO
737 #undef REQ_GROUP
738 #undef REQ_
741 struct request_info {
742 enum request request;
743 const char *name;
744 int namelen;
745 const char *help;
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751 REQ_INFO
752 #undef REQ_GROUP
753 #undef REQ_
756 static enum request
757 get_request(const char *name)
759 int namelen = strlen(name);
760 int i;
762 for (i = 0; i < ARRAY_SIZE(req_info); i++)
763 if (req_info[i].namelen == namelen &&
764 !string_enum_compare(req_info[i].name, name, namelen))
765 return req_info[i].request;
767 return REQ_NONE;
772 * Options
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig [options] [revs] [--] [paths]\n"
779 " or: tig show [options] [revs] [--] [paths]\n"
780 " or: tig blame [rev] path\n"
781 " or: tig status\n"
782 " or: tig < [git command output]\n"
783 "\n"
784 "Options:\n"
785 " -v, --version Show version and exit\n"
786 " -h, --help Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date = TRUE;
790 static bool opt_author = TRUE;
791 static bool opt_line_number = FALSE;
792 static bool opt_line_graphics = TRUE;
793 static bool opt_rev_graph = FALSE;
794 static bool opt_show_refs = TRUE;
795 static int opt_num_interval = NUMBER_INTERVAL;
796 static int opt_tab_size = TAB_SIZE;
797 static int opt_author_cols = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR] = "";
799 static char opt_file[SIZEOF_STR] = "";
800 static char opt_ref[SIZEOF_REF] = "";
801 static char opt_head[SIZEOF_REF] = "";
802 static char opt_head_rev[SIZEOF_REV] = "";
803 static char opt_remote[SIZEOF_REF] = "";
804 static char opt_encoding[20] = "UTF-8";
805 static bool opt_utf8 = TRUE;
806 static char opt_codeset[20] = "UTF-8";
807 static iconv_t opt_iconv = ICONV_NONE;
808 static char opt_search[SIZEOF_STR] = "";
809 static char opt_cdup[SIZEOF_STR] = "";
810 static char opt_prefix[SIZEOF_STR] = "";
811 static char opt_git_dir[SIZEOF_STR] = "";
812 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR] = "";
814 static FILE *opt_tty = NULL;
816 #define is_initial_commit() (!*opt_head_rev)
817 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
822 enum request request = REQ_VIEW_MAIN;
823 const char *subcommand;
824 bool seen_dashdash = FALSE;
825 /* XXX: This is vulnerable to the user overriding options
826 * required for the main view parser. */
827 static const char *custom_argv[SIZEOF_ARG] = {
828 "git", "log", "--no-color", "--pretty=raw", "--parents",
829 "--topo-order", NULL
831 int i, j = 6;
833 if (!isatty(STDIN_FILENO))
834 return REQ_VIEW_PAGER;
836 if (argc <= 1)
837 return REQ_VIEW_MAIN;
839 subcommand = argv[1];
840 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841 if (!strcmp(subcommand, "-S"))
842 warn("`-S' has been deprecated; use `tig status' instead");
843 if (argc > 2)
844 warn("ignoring arguments after `%s'", subcommand);
845 return REQ_VIEW_STATUS;
847 } else if (!strcmp(subcommand, "blame")) {
848 if (argc <= 2 || argc > 4)
849 die("invalid number of options to blame\n\n%s", usage);
851 i = 2;
852 if (argc == 4) {
853 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854 i++;
857 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858 return REQ_VIEW_BLAME;
860 } else if (!strcmp(subcommand, "show")) {
861 request = REQ_VIEW_DIFF;
863 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865 warn("`tig %s' has been deprecated", subcommand);
867 } else {
868 subcommand = NULL;
871 if (subcommand) {
872 custom_argv[1] = subcommand;
873 j = 2;
876 for (i = 1 + !!subcommand; i < argc; i++) {
877 const char *opt = argv[i];
879 if (seen_dashdash || !strcmp(opt, "--")) {
880 seen_dashdash = TRUE;
882 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883 printf("tig version %s\n", TIG_VERSION);
884 return REQ_NONE;
886 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887 printf("%s\n", usage);
888 return REQ_NONE;
891 custom_argv[j++] = opt;
892 if (j >= ARRAY_SIZE(custom_argv))
893 die("command too long");
896 custom_argv[j] = NULL;
897 *run_argv = custom_argv;
899 return request;
904 * Line-oriented content detection.
907 #define LINE_INFO \
908 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
910 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
911 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
912 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
913 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
923 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
924 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
925 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
926 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
927 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
929 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
930 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
931 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
932 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
933 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
934 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
937 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
938 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
939 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
940 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
941 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
942 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
943 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
944 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
945 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
946 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
948 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
950 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
952 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
954 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
955 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
956 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
957 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
958 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
959 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
960 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
961 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
962 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967 LINE_##type
968 LINE_INFO,
969 LINE_NONE
970 #undef LINE
973 struct line_info {
974 const char *name; /* Option name. */
975 int namelen; /* Size of option name. */
976 const char *line; /* The start of line to match. */
977 int linelen; /* Size of string to match. */
978 int fg, bg, attr; /* Color and text attributes for the lines. */
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984 LINE_INFO
985 #undef LINE
988 static enum line_type
989 get_line_type(const char *line)
991 int linelen = strlen(line);
992 enum line_type type;
994 for (type = 0; type < ARRAY_SIZE(line_info); type++)
995 /* Case insensitive search matches Signed-off-by lines better. */
996 if (linelen >= line_info[type].linelen &&
997 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998 return type;
1000 return LINE_DEFAULT;
1003 static inline int
1004 get_line_attr(enum line_type type)
1006 assert(type < ARRAY_SIZE(line_info));
1007 return COLOR_PAIR(type) | line_info[type].attr;
1010 static struct line_info *
1011 get_line_info(const char *name)
1013 size_t namelen = strlen(name);
1014 enum line_type type;
1016 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017 if (namelen == line_info[type].namelen &&
1018 !string_enum_compare(line_info[type].name, name, namelen))
1019 return &line_info[type];
1021 return NULL;
1024 static void
1025 init_colors(void)
1027 int default_bg = line_info[LINE_DEFAULT].bg;
1028 int default_fg = line_info[LINE_DEFAULT].fg;
1029 enum line_type type;
1031 start_color();
1033 if (assume_default_colors(default_fg, default_bg) == ERR) {
1034 default_bg = COLOR_BLACK;
1035 default_fg = COLOR_WHITE;
1038 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039 struct line_info *info = &line_info[type];
1040 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043 init_pair(type, fg, bg);
1047 struct line {
1048 enum line_type type;
1050 /* State flags */
1051 unsigned int selected:1;
1052 unsigned int dirty:1;
1053 unsigned int cleareol:1;
1055 void *data; /* User data */
1060 * Keys
1063 struct keybinding {
1064 int alias;
1065 enum request request;
1068 static struct keybinding default_keybindings[] = {
1069 /* View switching */
1070 { 'm', REQ_VIEW_MAIN },
1071 { 'd', REQ_VIEW_DIFF },
1072 { 'l', REQ_VIEW_LOG },
1073 { 't', REQ_VIEW_TREE },
1074 { 'f', REQ_VIEW_BLOB },
1075 { 'B', REQ_VIEW_BLAME },
1076 { 'p', REQ_VIEW_PAGER },
1077 { 'h', REQ_VIEW_HELP },
1078 { 'S', REQ_VIEW_STATUS },
1079 { 'c', REQ_VIEW_STAGE },
1081 /* View manipulation */
1082 { 'q', REQ_VIEW_CLOSE },
1083 { KEY_TAB, REQ_VIEW_NEXT },
1084 { KEY_RETURN, REQ_ENTER },
1085 { KEY_UP, REQ_PREVIOUS },
1086 { KEY_DOWN, REQ_NEXT },
1087 { 'R', REQ_REFRESH },
1088 { KEY_F(5), REQ_REFRESH },
1089 { 'O', REQ_MAXIMIZE },
1091 /* Cursor navigation */
1092 { 'k', REQ_MOVE_UP },
1093 { 'j', REQ_MOVE_DOWN },
1094 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1095 { KEY_END, REQ_MOVE_LAST_LINE },
1096 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1097 { ' ', REQ_MOVE_PAGE_DOWN },
1098 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1099 { 'b', REQ_MOVE_PAGE_UP },
1100 { '-', REQ_MOVE_PAGE_UP },
1102 /* Scrolling */
1103 { KEY_IC, REQ_SCROLL_LINE_UP },
1104 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1105 { 'w', REQ_SCROLL_PAGE_UP },
1106 { 's', REQ_SCROLL_PAGE_DOWN },
1108 /* Searching */
1109 { '/', REQ_SEARCH },
1110 { '?', REQ_SEARCH_BACK },
1111 { 'n', REQ_FIND_NEXT },
1112 { 'N', REQ_FIND_PREV },
1114 /* Misc */
1115 { 'Q', REQ_QUIT },
1116 { 'z', REQ_STOP_LOADING },
1117 { 'v', REQ_SHOW_VERSION },
1118 { 'r', REQ_SCREEN_REDRAW },
1119 { '.', REQ_TOGGLE_LINENO },
1120 { 'D', REQ_TOGGLE_DATE },
1121 { 'A', REQ_TOGGLE_AUTHOR },
1122 { 'g', REQ_TOGGLE_REV_GRAPH },
1123 { 'F', REQ_TOGGLE_REFS },
1124 { ':', REQ_PROMPT },
1125 { 'u', REQ_STATUS_UPDATE },
1126 { '!', REQ_STATUS_REVERT },
1127 { 'M', REQ_STATUS_MERGE },
1128 { '@', REQ_STAGE_NEXT },
1129 { ',', REQ_PARENT },
1130 { 'e', REQ_EDIT },
1133 #define KEYMAP_INFO \
1134 KEYMAP_(GENERIC), \
1135 KEYMAP_(MAIN), \
1136 KEYMAP_(DIFF), \
1137 KEYMAP_(LOG), \
1138 KEYMAP_(TREE), \
1139 KEYMAP_(BLOB), \
1140 KEYMAP_(BLAME), \
1141 KEYMAP_(PAGER), \
1142 KEYMAP_(HELP), \
1143 KEYMAP_(STATUS), \
1144 KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148 KEYMAP_INFO
1149 #undef KEYMAP_
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154 KEYMAP_INFO
1155 #undef KEYMAP_
1158 #define set_keymap(map, name) \
1159 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162 struct keybinding *data;
1163 size_t size;
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1171 struct keybinding_table *table = &keybindings[keymap];
1173 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174 if (!table->data)
1175 die("Failed to allocate keybinding");
1176 table->data[table->size].alias = key;
1177 table->data[table->size++].request = request;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181 * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1185 size_t i;
1187 for (i = 0; i < keybindings[keymap].size; i++)
1188 if (keybindings[keymap].data[i].alias == key)
1189 return keybindings[keymap].data[i].request;
1191 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193 return keybindings[KEYMAP_GENERIC].data[i].request;
1195 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196 if (default_keybindings[i].alias == key)
1197 return default_keybindings[i].request;
1199 return (enum request) key;
1203 struct key {
1204 const char *name;
1205 int value;
1208 static struct key key_table[] = {
1209 { "Enter", KEY_RETURN },
1210 { "Space", ' ' },
1211 { "Backspace", KEY_BACKSPACE },
1212 { "Tab", KEY_TAB },
1213 { "Escape", KEY_ESC },
1214 { "Left", KEY_LEFT },
1215 { "Right", KEY_RIGHT },
1216 { "Up", KEY_UP },
1217 { "Down", KEY_DOWN },
1218 { "Insert", KEY_IC },
1219 { "Delete", KEY_DC },
1220 { "Hash", '#' },
1221 { "Home", KEY_HOME },
1222 { "End", KEY_END },
1223 { "PageUp", KEY_PPAGE },
1224 { "PageDown", KEY_NPAGE },
1225 { "F1", KEY_F(1) },
1226 { "F2", KEY_F(2) },
1227 { "F3", KEY_F(3) },
1228 { "F4", KEY_F(4) },
1229 { "F5", KEY_F(5) },
1230 { "F6", KEY_F(6) },
1231 { "F7", KEY_F(7) },
1232 { "F8", KEY_F(8) },
1233 { "F9", KEY_F(9) },
1234 { "F10", KEY_F(10) },
1235 { "F11", KEY_F(11) },
1236 { "F12", KEY_F(12) },
1239 static int
1240 get_key_value(const char *name)
1242 int i;
1244 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245 if (!strcasecmp(key_table[i].name, name))
1246 return key_table[i].value;
1248 if (strlen(name) == 1 && isprint(*name))
1249 return (int) *name;
1251 return ERR;
1254 static const char *
1255 get_key_name(int key_value)
1257 static char key_char[] = "'X'";
1258 const char *seq = NULL;
1259 int key;
1261 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262 if (key_table[key].value == key_value)
1263 seq = key_table[key].name;
1265 if (seq == NULL &&
1266 key_value < 127 &&
1267 isprint(key_value)) {
1268 key_char[1] = (char) key_value;
1269 seq = key_char;
1272 return seq ? seq : "(no key)";
1275 static const char *
1276 get_key(enum request request)
1278 static char buf[BUFSIZ];
1279 size_t pos = 0;
1280 char *sep = "";
1281 int i;
1283 buf[pos] = 0;
1285 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286 struct keybinding *keybinding = &default_keybindings[i];
1288 if (keybinding->request != request)
1289 continue;
1291 if (!string_format_from(buf, &pos, "%s%s", sep,
1292 get_key_name(keybinding->alias)))
1293 return "Too many keybindings!";
1294 sep = ", ";
1297 return buf;
1300 struct run_request {
1301 enum keymap keymap;
1302 int key;
1303 const char *argv[SIZEOF_ARG];
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1312 struct run_request *req;
1314 if (argc >= ARRAY_SIZE(req->argv) - 1)
1315 return REQ_NONE;
1317 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318 if (!req)
1319 return REQ_NONE;
1321 run_request = req;
1322 req = &run_request[run_requests];
1323 req->keymap = keymap;
1324 req->key = key;
1325 req->argv[0] = NULL;
1327 if (!format_argv(req->argv, argv, FORMAT_NONE))
1328 return REQ_NONE;
1330 return REQ_NONE + ++run_requests;
1333 static struct run_request *
1334 get_run_request(enum request request)
1336 if (request <= REQ_NONE)
1337 return NULL;
1338 return &run_request[request - REQ_NONE - 1];
1341 static void
1342 add_builtin_run_requests(void)
1344 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345 const char *gc[] = { "git", "gc", NULL };
1346 struct {
1347 enum keymap keymap;
1348 int key;
1349 int argc;
1350 const char **argv;
1351 } reqs[] = {
1352 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1355 int i;
1357 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358 enum request req;
1360 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361 if (req != REQ_NONE)
1362 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1367 * User config file handling.
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372 COLOR_MAP(DEFAULT),
1373 COLOR_MAP(BLACK),
1374 COLOR_MAP(BLUE),
1375 COLOR_MAP(CYAN),
1376 COLOR_MAP(GREEN),
1377 COLOR_MAP(MAGENTA),
1378 COLOR_MAP(RED),
1379 COLOR_MAP(WHITE),
1380 COLOR_MAP(YELLOW),
1383 #define set_color(color, name) \
1384 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388 ATTR_MAP(NORMAL),
1389 ATTR_MAP(BLINK),
1390 ATTR_MAP(BOLD),
1391 ATTR_MAP(DIM),
1392 ATTR_MAP(REVERSE),
1393 ATTR_MAP(STANDOUT),
1394 ATTR_MAP(UNDERLINE),
1397 #define set_attribute(attr, name) \
1398 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int config_lineno;
1401 static bool config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1408 struct line_info *info;
1410 if (argc != 3 && argc != 4) {
1411 config_msg = "Wrong number of arguments given to color command";
1412 return ERR;
1415 info = get_line_info(argv[0]);
1416 if (!info) {
1417 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418 info = get_line_info("delimiter");
1420 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421 info = get_line_info("date");
1423 } else {
1424 config_msg = "Unknown color name";
1425 return ERR;
1429 if (set_color(&info->fg, argv[1]) == ERR ||
1430 set_color(&info->bg, argv[2]) == ERR) {
1431 config_msg = "Unknown color";
1432 return ERR;
1435 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436 config_msg = "Unknown attribute";
1437 return ERR;
1440 return OK;
1443 static bool parse_bool(const char *s)
1445 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446 !strcmp(s, "yes")) ? TRUE : FALSE;
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1452 int value = atoi(s);
1454 return (value < min || value > max) ? default_value : value;
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1461 if (argc != 3) {
1462 config_msg = "Wrong number of arguments given to set command";
1463 return ERR;
1466 if (strcmp(argv[1], "=")) {
1467 config_msg = "No value assigned";
1468 return ERR;
1471 if (!strcmp(argv[0], "show-author")) {
1472 opt_author = parse_bool(argv[2]);
1473 return OK;
1476 if (!strcmp(argv[0], "show-date")) {
1477 opt_date = parse_bool(argv[2]);
1478 return OK;
1481 if (!strcmp(argv[0], "show-rev-graph")) {
1482 opt_rev_graph = parse_bool(argv[2]);
1483 return OK;
1486 if (!strcmp(argv[0], "show-refs")) {
1487 opt_show_refs = parse_bool(argv[2]);
1488 return OK;
1491 if (!strcmp(argv[0], "show-line-numbers")) {
1492 opt_line_number = parse_bool(argv[2]);
1493 return OK;
1496 if (!strcmp(argv[0], "line-graphics")) {
1497 opt_line_graphics = parse_bool(argv[2]);
1498 return OK;
1501 if (!strcmp(argv[0], "line-number-interval")) {
1502 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503 return OK;
1506 if (!strcmp(argv[0], "author-width")) {
1507 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508 return OK;
1511 if (!strcmp(argv[0], "tab-size")) {
1512 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513 return OK;
1516 if (!strcmp(argv[0], "commit-encoding")) {
1517 const char *arg = argv[2];
1518 int arglen = strlen(arg);
1520 switch (arg[0]) {
1521 case '"':
1522 case '\'':
1523 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524 config_msg = "Unmatched quotation";
1525 return ERR;
1527 arg += 1; arglen -= 2;
1528 default:
1529 string_ncopy(opt_encoding, arg, strlen(arg));
1530 return OK;
1534 config_msg = "Unknown variable name";
1535 return ERR;
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1542 enum request request;
1543 int keymap;
1544 int key;
1546 if (argc < 3) {
1547 config_msg = "Wrong number of arguments given to bind command";
1548 return ERR;
1551 if (set_keymap(&keymap, argv[0]) == ERR) {
1552 config_msg = "Unknown key map";
1553 return ERR;
1556 key = get_key_value(argv[1]);
1557 if (key == ERR) {
1558 config_msg = "Unknown key";
1559 return ERR;
1562 request = get_request(argv[2]);
1563 if (request == REQ_NONE) {
1564 struct {
1565 const char *name;
1566 enum request request;
1567 } obsolete[] = {
1568 { "cherry-pick", REQ_NONE },
1569 { "screen-resize", REQ_NONE },
1570 { "tree-parent", REQ_PARENT },
1572 size_t namelen = strlen(argv[2]);
1573 int i;
1575 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576 if (namelen != strlen(obsolete[i].name) ||
1577 string_enum_compare(obsolete[i].name, argv[2], namelen))
1578 continue;
1579 if (obsolete[i].request != REQ_NONE)
1580 add_keybinding(keymap, obsolete[i].request, key);
1581 config_msg = "Obsolete request name";
1582 return ERR;
1585 if (request == REQ_NONE && *argv[2]++ == '!')
1586 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587 if (request == REQ_NONE) {
1588 config_msg = "Unknown request name";
1589 return ERR;
1592 add_keybinding(keymap, request, key);
1594 return OK;
1597 static int
1598 set_option(const char *opt, char *value)
1600 const char *argv[SIZEOF_ARG];
1601 int argc = 0;
1603 if (!argv_from_string(argv, &argc, value)) {
1604 config_msg = "Too many option arguments";
1605 return ERR;
1608 if (!strcmp(opt, "color"))
1609 return option_color_command(argc, argv);
1611 if (!strcmp(opt, "set"))
1612 return option_set_command(argc, argv);
1614 if (!strcmp(opt, "bind"))
1615 return option_bind_command(argc, argv);
1617 config_msg = "Unknown option command";
1618 return ERR;
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1624 int status = OK;
1626 config_lineno++;
1627 config_msg = "Internal error";
1629 /* Check for comment markers, since read_properties() will
1630 * only ensure opt and value are split at first " \t". */
1631 optlen = strcspn(opt, "#");
1632 if (optlen == 0)
1633 return OK;
1635 if (opt[optlen] != 0) {
1636 config_msg = "No option value";
1637 status = ERR;
1639 } else {
1640 /* Look for comment endings in the value. */
1641 size_t len = strcspn(value, "#");
1643 if (len < valuelen) {
1644 valuelen = len;
1645 value[valuelen] = 0;
1648 status = set_option(opt, value);
1651 if (status == ERR) {
1652 warn("Error on line %d, near '%.*s': %s",
1653 config_lineno, (int) optlen, opt, config_msg);
1654 config_errors = TRUE;
1657 /* Always keep going if errors are encountered. */
1658 return OK;
1661 static void
1662 load_option_file(const char *path)
1664 struct io io = {};
1666 /* It's ok that the file doesn't exist. */
1667 if (!io_open(&io, path))
1668 return;
1670 config_lineno = 0;
1671 config_errors = FALSE;
1673 if (read_properties(&io, " \t", read_option) == ERR ||
1674 config_errors == TRUE)
1675 warn("Errors while loading %s.", path);
1678 static int
1679 load_options(void)
1681 const char *home = getenv("HOME");
1682 const char *tigrc_user = getenv("TIGRC_USER");
1683 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684 char buf[SIZEOF_STR];
1686 add_builtin_run_requests();
1688 if (!tigrc_system) {
1689 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690 return ERR;
1691 tigrc_system = buf;
1693 load_option_file(tigrc_system);
1695 if (!tigrc_user) {
1696 if (!home || !string_format(buf, "%s/.tigrc", home))
1697 return ERR;
1698 tigrc_user = buf;
1700 load_option_file(tigrc_user);
1702 return OK;
1707 * The viewer
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 #define foreach_displayed_view(view, i) \
1718 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1720 #define displayed_views() (display[1] != NULL ? 2 : 1)
1722 /* Current head and commit ID */
1723 static char ref_blob[SIZEOF_REF] = "";
1724 static char ref_commit[SIZEOF_REF] = "HEAD";
1725 static char ref_head[SIZEOF_REF] = "HEAD";
1727 struct view {
1728 const char *name; /* View name */
1729 const char *cmd_env; /* Command line set via environment */
1730 const char *id; /* Points to either of ref_{head,commit,blob} */
1732 struct view_ops *ops; /* View operations */
1734 enum keymap keymap; /* What keymap does this view have */
1735 bool git_dir; /* Whether the view requires a git directory. */
1737 char ref[SIZEOF_REF]; /* Hovered commit reference */
1738 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1740 int height, width; /* The width and height of the main window */
1741 WINDOW *win; /* The main window */
1742 WINDOW *title; /* The title window living below the main window */
1744 /* Navigation */
1745 unsigned long offset; /* Offset of the window top */
1746 unsigned long lineno; /* Current line number */
1747 unsigned long p_offset; /* Previous offset of the window top */
1748 unsigned long p_lineno; /* Previous current line number */
1749 bool p_restore; /* Should the previous position be restored. */
1751 /* Searching */
1752 char grep[SIZEOF_STR]; /* Search string */
1753 regex_t *regex; /* Pre-compiled regex */
1755 /* If non-NULL, points to the view that opened this view. If this view
1756 * is closed tig will switch back to the parent view. */
1757 struct view *parent;
1759 /* Buffering */
1760 size_t lines; /* Total number of lines */
1761 struct line *line; /* Line index */
1762 size_t line_alloc; /* Total number of allocated lines */
1763 unsigned int digits; /* Number of digits in the lines member. */
1765 /* Drawing */
1766 struct line *curline; /* Line currently being drawn. */
1767 enum line_type curtype; /* Attribute currently used for drawing. */
1768 unsigned long col; /* Column when drawing. */
1769 bool has_scrolled; /* View was scrolled. */
1771 /* Loading */
1772 struct io io;
1773 struct io *pipe;
1774 time_t start_time;
1775 time_t update_secs;
1778 struct view_ops {
1779 /* What type of content being displayed. Used in the title bar. */
1780 const char *type;
1781 /* Default command arguments. */
1782 const char **argv;
1783 /* Open and reads in all view content. */
1784 bool (*open)(struct view *view);
1785 /* Read one line; updates view->line. */
1786 bool (*read)(struct view *view, char *data);
1787 /* Draw one line; @lineno must be < view->height. */
1788 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1789 /* Depending on view handle a special requests. */
1790 enum request (*request)(struct view *view, enum request request, struct line *line);
1791 /* Search for regex in a line. */
1792 bool (*grep)(struct view *view, struct line *line);
1793 /* Select line */
1794 void (*select)(struct view *view, struct line *line);
1797 static struct view_ops blame_ops;
1798 static struct view_ops blob_ops;
1799 static struct view_ops diff_ops;
1800 static struct view_ops help_ops;
1801 static struct view_ops log_ops;
1802 static struct view_ops main_ops;
1803 static struct view_ops pager_ops;
1804 static struct view_ops stage_ops;
1805 static struct view_ops status_ops;
1806 static struct view_ops tree_ops;
1808 #define VIEW_STR(name, env, ref, ops, map, git) \
1809 { name, #env, ref, ops, map, git }
1811 #define VIEW_(id, name, ops, git, ref) \
1812 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1815 static struct view views[] = {
1816 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1817 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1818 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1819 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1820 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1821 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1822 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1823 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1824 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1825 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1828 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1829 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1831 #define foreach_view(view, i) \
1832 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1834 #define view_is_displayed(view) \
1835 (view == display[0] || view == display[1])
1838 enum line_graphic {
1839 LINE_GRAPHIC_VLINE
1842 static int line_graphics[] = {
1843 /* LINE_GRAPHIC_VLINE: */ '|'
1846 static inline void
1847 set_view_attr(struct view *view, enum line_type type)
1849 if (!view->curline->selected && view->curtype != type) {
1850 wattrset(view->win, get_line_attr(type));
1851 wchgat(view->win, -1, 0, type, NULL);
1852 view->curtype = type;
1856 static int
1857 draw_chars(struct view *view, enum line_type type, const char *string,
1858 int max_len, bool use_tilde)
1860 int len = 0;
1861 int col = 0;
1862 int trimmed = FALSE;
1864 if (max_len <= 0)
1865 return 0;
1867 if (opt_utf8) {
1868 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1869 } else {
1870 col = len = strlen(string);
1871 if (len > max_len) {
1872 if (use_tilde) {
1873 max_len -= 1;
1875 col = len = max_len;
1876 trimmed = TRUE;
1880 set_view_attr(view, type);
1881 waddnstr(view->win, string, len);
1882 if (trimmed && use_tilde) {
1883 set_view_attr(view, LINE_DELIMITER);
1884 waddch(view->win, '~');
1885 col++;
1888 return col;
1891 static int
1892 draw_space(struct view *view, enum line_type type, int max, int spaces)
1894 static char space[] = " ";
1895 int col = 0;
1897 spaces = MIN(max, spaces);
1899 while (spaces > 0) {
1900 int len = MIN(spaces, sizeof(space) - 1);
1902 col += draw_chars(view, type, space, spaces, FALSE);
1903 spaces -= len;
1906 return col;
1909 static bool
1910 draw_lineno(struct view *view, unsigned int lineno)
1912 char number[10];
1913 int digits3 = view->digits < 3 ? 3 : view->digits;
1914 int max_number = MIN(digits3, STRING_SIZE(number));
1915 int max = view->width - view->col;
1916 int col;
1918 if (max < max_number)
1919 max_number = max;
1921 lineno += view->offset + 1;
1922 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1923 static char fmt[] = "%1ld";
1925 if (view->digits <= 9)
1926 fmt[1] = '0' + digits3;
1928 if (!string_format(number, fmt, lineno))
1929 number[0] = 0;
1930 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1931 } else {
1932 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1935 if (col < max) {
1936 set_view_attr(view, LINE_DEFAULT);
1937 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1938 col++;
1941 if (col < max)
1942 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1943 view->col += col;
1945 return view->width - view->col <= 0;
1948 static bool
1949 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1951 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1952 return view->width - view->col <= 0;
1955 static bool
1956 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1958 int max = view->width - view->col;
1959 int i;
1961 if (max < size)
1962 size = max;
1964 set_view_attr(view, type);
1965 /* Using waddch() instead of waddnstr() ensures that
1966 * they'll be rendered correctly for the cursor line. */
1967 for (i = 0; i < size; i++)
1968 waddch(view->win, graphic[i]);
1970 view->col += size;
1971 if (size < max) {
1972 waddch(view->win, ' ');
1973 view->col++;
1976 return view->width - view->col <= 0;
1979 static bool
1980 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1982 int max = MIN(view->width - view->col, len);
1983 int col;
1985 if (text)
1986 col = draw_chars(view, type, text, max - 1, trim);
1987 else
1988 col = draw_space(view, type, max - 1, max - 1);
1990 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1991 return view->width - view->col <= 0;
1994 static bool
1995 draw_date(struct view *view, struct tm *time)
1997 char buf[DATE_COLS];
1998 char *date;
1999 int timelen = 0;
2001 if (time)
2002 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2003 date = timelen ? buf : NULL;
2005 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2008 static bool
2009 draw_author(struct view *view, const char *author)
2011 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2013 if (!trim) {
2014 static char initials[10];
2015 size_t pos;
2017 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2019 memset(initials, 0, sizeof(initials));
2020 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2021 while (is_initial_sep(*author))
2022 author++;
2023 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2024 while (*author && !is_initial_sep(author[1]))
2025 author++;
2028 author = initials;
2031 return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2034 static bool
2035 draw_view_line(struct view *view, unsigned int lineno)
2037 struct line *line;
2038 bool selected = (view->offset + lineno == view->lineno);
2040 assert(view_is_displayed(view));
2042 if (view->offset + lineno >= view->lines)
2043 return FALSE;
2045 line = &view->line[view->offset + lineno];
2047 wmove(view->win, lineno, 0);
2048 if (line->cleareol)
2049 wclrtoeol(view->win);
2050 view->col = 0;
2051 view->curline = line;
2052 view->curtype = LINE_NONE;
2053 line->selected = FALSE;
2054 line->dirty = line->cleareol = 0;
2056 if (selected) {
2057 set_view_attr(view, LINE_CURSOR);
2058 line->selected = TRUE;
2059 view->ops->select(view, line);
2062 return view->ops->draw(view, line, lineno);
2065 static void
2066 redraw_view_dirty(struct view *view)
2068 bool dirty = FALSE;
2069 int lineno;
2071 for (lineno = 0; lineno < view->height; lineno++) {
2072 if (view->offset + lineno >= view->lines)
2073 break;
2074 if (!view->line[view->offset + lineno].dirty)
2075 continue;
2076 dirty = TRUE;
2077 if (!draw_view_line(view, lineno))
2078 break;
2081 if (!dirty)
2082 return;
2083 wnoutrefresh(view->win);
2086 static void
2087 redraw_view_from(struct view *view, int lineno)
2089 assert(0 <= lineno && lineno < view->height);
2091 for (; lineno < view->height; lineno++) {
2092 if (!draw_view_line(view, lineno))
2093 break;
2096 wnoutrefresh(view->win);
2099 static void
2100 redraw_view(struct view *view)
2102 werase(view->win);
2103 redraw_view_from(view, 0);
2107 static void
2108 update_view_title(struct view *view)
2110 char buf[SIZEOF_STR];
2111 char state[SIZEOF_STR];
2112 size_t bufpos = 0, statelen = 0;
2114 assert(view_is_displayed(view));
2116 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2117 unsigned int view_lines = view->offset + view->height;
2118 unsigned int lines = view->lines
2119 ? MIN(view_lines, view->lines) * 100 / view->lines
2120 : 0;
2122 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2123 view->ops->type,
2124 view->lineno + 1,
2125 view->lines,
2126 lines);
2130 if (view->pipe) {
2131 time_t secs = time(NULL) - view->start_time;
2133 /* Three git seconds are a long time ... */
2134 if (secs > 2)
2135 string_format_from(state, &statelen, " loading %lds", secs);
2138 string_format_from(buf, &bufpos, "[%s]", view->name);
2139 if (*view->ref && bufpos < view->width) {
2140 size_t refsize = strlen(view->ref);
2141 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2143 if (minsize < view->width)
2144 refsize = view->width - minsize + 7;
2145 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2148 if (statelen && bufpos < view->width) {
2149 string_format_from(buf, &bufpos, "%s", state);
2152 if (view == display[current_view])
2153 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2154 else
2155 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2157 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2158 wclrtoeol(view->title);
2159 wnoutrefresh(view->title);
2162 static void
2163 resize_display(void)
2165 int offset, i;
2166 struct view *base = display[0];
2167 struct view *view = display[1] ? display[1] : display[0];
2169 /* Setup window dimensions */
2171 getmaxyx(stdscr, base->height, base->width);
2173 /* Make room for the status window. */
2174 base->height -= 1;
2176 if (view != base) {
2177 /* Horizontal split. */
2178 view->width = base->width;
2179 view->height = SCALE_SPLIT_VIEW(base->height);
2180 base->height -= view->height;
2182 /* Make room for the title bar. */
2183 view->height -= 1;
2186 /* Make room for the title bar. */
2187 base->height -= 1;
2189 offset = 0;
2191 foreach_displayed_view (view, i) {
2192 if (!view->win) {
2193 view->win = newwin(view->height, 0, offset, 0);
2194 if (!view->win)
2195 die("Failed to create %s view", view->name);
2197 scrollok(view->win, FALSE);
2199 view->title = newwin(1, 0, offset + view->height, 0);
2200 if (!view->title)
2201 die("Failed to create title window");
2203 } else {
2204 wresize(view->win, view->height, view->width);
2205 mvwin(view->win, offset, 0);
2206 mvwin(view->title, offset + view->height, 0);
2209 offset += view->height + 1;
2213 static void
2214 redraw_display(bool clear)
2216 struct view *view;
2217 int i;
2219 foreach_displayed_view (view, i) {
2220 if (clear)
2221 wclear(view->win);
2222 redraw_view(view);
2223 update_view_title(view);
2227 static void
2228 toggle_view_option(bool *option, const char *help)
2230 *option = !*option;
2231 redraw_display(FALSE);
2232 report("%sabling %s", *option ? "En" : "Dis", help);
2236 * Navigation
2239 /* Scrolling backend */
2240 static void
2241 do_scroll_view(struct view *view, int lines)
2243 bool redraw_current_line = FALSE;
2245 /* The rendering expects the new offset. */
2246 view->offset += lines;
2248 assert(0 <= view->offset && view->offset < view->lines);
2249 assert(lines);
2251 /* Move current line into the view. */
2252 if (view->lineno < view->offset) {
2253 view->lineno = view->offset;
2254 redraw_current_line = TRUE;
2255 } else if (view->lineno >= view->offset + view->height) {
2256 view->lineno = view->offset + view->height - 1;
2257 redraw_current_line = TRUE;
2260 assert(view->offset <= view->lineno && view->lineno < view->lines);
2262 /* Redraw the whole screen if scrolling is pointless. */
2263 if (view->height < ABS(lines)) {
2264 redraw_view(view);
2266 } else {
2267 int line = lines > 0 ? view->height - lines : 0;
2268 int end = line + ABS(lines);
2270 scrollok(view->win, TRUE);
2271 wscrl(view->win, lines);
2272 scrollok(view->win, FALSE);
2274 while (line < end && draw_view_line(view, line))
2275 line++;
2277 if (redraw_current_line)
2278 draw_view_line(view, view->lineno - view->offset);
2279 wnoutrefresh(view->win);
2282 view->has_scrolled = TRUE;
2283 report("");
2286 /* Scroll frontend */
2287 static void
2288 scroll_view(struct view *view, enum request request)
2290 int lines = 1;
2292 assert(view_is_displayed(view));
2294 switch (request) {
2295 case REQ_SCROLL_PAGE_DOWN:
2296 lines = view->height;
2297 case REQ_SCROLL_LINE_DOWN:
2298 if (view->offset + lines > view->lines)
2299 lines = view->lines - view->offset;
2301 if (lines == 0 || view->offset + view->height >= view->lines) {
2302 report("Cannot scroll beyond the last line");
2303 return;
2305 break;
2307 case REQ_SCROLL_PAGE_UP:
2308 lines = view->height;
2309 case REQ_SCROLL_LINE_UP:
2310 if (lines > view->offset)
2311 lines = view->offset;
2313 if (lines == 0) {
2314 report("Cannot scroll beyond the first line");
2315 return;
2318 lines = -lines;
2319 break;
2321 default:
2322 die("request %d not handled in switch", request);
2325 do_scroll_view(view, lines);
2328 /* Cursor moving */
2329 static void
2330 move_view(struct view *view, enum request request)
2332 int scroll_steps = 0;
2333 int steps;
2335 switch (request) {
2336 case REQ_MOVE_FIRST_LINE:
2337 steps = -view->lineno;
2338 break;
2340 case REQ_MOVE_LAST_LINE:
2341 steps = view->lines - view->lineno - 1;
2342 break;
2344 case REQ_MOVE_PAGE_UP:
2345 steps = view->height > view->lineno
2346 ? -view->lineno : -view->height;
2347 break;
2349 case REQ_MOVE_PAGE_DOWN:
2350 steps = view->lineno + view->height >= view->lines
2351 ? view->lines - view->lineno - 1 : view->height;
2352 break;
2354 case REQ_MOVE_UP:
2355 steps = -1;
2356 break;
2358 case REQ_MOVE_DOWN:
2359 steps = 1;
2360 break;
2362 default:
2363 die("request %d not handled in switch", request);
2366 if (steps <= 0 && view->lineno == 0) {
2367 report("Cannot move beyond the first line");
2368 return;
2370 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2371 report("Cannot move beyond the last line");
2372 return;
2375 /* Move the current line */
2376 view->lineno += steps;
2377 assert(0 <= view->lineno && view->lineno < view->lines);
2379 /* Check whether the view needs to be scrolled */
2380 if (view->lineno < view->offset ||
2381 view->lineno >= view->offset + view->height) {
2382 scroll_steps = steps;
2383 if (steps < 0 && -steps > view->offset) {
2384 scroll_steps = -view->offset;
2386 } else if (steps > 0) {
2387 if (view->lineno == view->lines - 1 &&
2388 view->lines > view->height) {
2389 scroll_steps = view->lines - view->offset - 1;
2390 if (scroll_steps >= view->height)
2391 scroll_steps -= view->height - 1;
2396 if (!view_is_displayed(view)) {
2397 view->offset += scroll_steps;
2398 assert(0 <= view->offset && view->offset < view->lines);
2399 view->ops->select(view, &view->line[view->lineno]);
2400 return;
2403 /* Repaint the old "current" line if we be scrolling */
2404 if (ABS(steps) < view->height)
2405 draw_view_line(view, view->lineno - steps - view->offset);
2407 if (scroll_steps) {
2408 do_scroll_view(view, scroll_steps);
2409 return;
2412 /* Draw the current line */
2413 draw_view_line(view, view->lineno - view->offset);
2415 wnoutrefresh(view->win);
2416 report("");
2421 * Searching
2424 static void search_view(struct view *view, enum request request);
2426 static void
2427 select_view_line(struct view *view, unsigned long lineno)
2429 if (lineno - view->offset >= view->height) {
2430 view->offset = lineno;
2431 view->lineno = lineno;
2432 if (view_is_displayed(view))
2433 redraw_view(view);
2435 } else {
2436 unsigned long old_lineno = view->lineno - view->offset;
2438 view->lineno = lineno;
2439 if (view_is_displayed(view)) {
2440 draw_view_line(view, old_lineno);
2441 draw_view_line(view, view->lineno - view->offset);
2442 wnoutrefresh(view->win);
2443 } else {
2444 view->ops->select(view, &view->line[view->lineno]);
2449 static void
2450 find_next(struct view *view, enum request request)
2452 unsigned long lineno = view->lineno;
2453 int direction;
2455 if (!*view->grep) {
2456 if (!*opt_search)
2457 report("No previous search");
2458 else
2459 search_view(view, request);
2460 return;
2463 switch (request) {
2464 case REQ_SEARCH:
2465 case REQ_FIND_NEXT:
2466 direction = 1;
2467 break;
2469 case REQ_SEARCH_BACK:
2470 case REQ_FIND_PREV:
2471 direction = -1;
2472 break;
2474 default:
2475 return;
2478 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2479 lineno += direction;
2481 /* Note, lineno is unsigned long so will wrap around in which case it
2482 * will become bigger than view->lines. */
2483 for (; lineno < view->lines; lineno += direction) {
2484 if (view->ops->grep(view, &view->line[lineno])) {
2485 select_view_line(view, lineno);
2486 report("Line %ld matches '%s'", lineno + 1, view->grep);
2487 return;
2491 report("No match found for '%s'", view->grep);
2494 static void
2495 search_view(struct view *view, enum request request)
2497 int regex_err;
2499 if (view->regex) {
2500 regfree(view->regex);
2501 *view->grep = 0;
2502 } else {
2503 view->regex = calloc(1, sizeof(*view->regex));
2504 if (!view->regex)
2505 return;
2508 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2509 if (regex_err != 0) {
2510 char buf[SIZEOF_STR] = "unknown error";
2512 regerror(regex_err, view->regex, buf, sizeof(buf));
2513 report("Search failed: %s", buf);
2514 return;
2517 string_copy(view->grep, opt_search);
2519 find_next(view, request);
2523 * Incremental updating
2526 static void
2527 reset_view(struct view *view)
2529 int i;
2531 for (i = 0; i < view->lines; i++)
2532 free(view->line[i].data);
2533 free(view->line);
2535 view->p_offset = view->offset;
2536 view->p_lineno = view->lineno;
2538 view->line = NULL;
2539 view->offset = 0;
2540 view->lines = 0;
2541 view->lineno = 0;
2542 view->line_alloc = 0;
2543 view->vid[0] = 0;
2544 view->update_secs = 0;
2547 static void
2548 free_argv(const char *argv[])
2550 int argc;
2552 for (argc = 0; argv[argc]; argc++)
2553 free((void *) argv[argc]);
2556 static bool
2557 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2559 char buf[SIZEOF_STR];
2560 int argc;
2561 bool noreplace = flags == FORMAT_NONE;
2563 free_argv(dst_argv);
2565 for (argc = 0; src_argv[argc]; argc++) {
2566 const char *arg = src_argv[argc];
2567 size_t bufpos = 0;
2569 while (arg) {
2570 char *next = strstr(arg, "%(");
2571 int len = next - arg;
2572 const char *value;
2574 if (!next || noreplace) {
2575 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2576 noreplace = TRUE;
2577 len = strlen(arg);
2578 value = "";
2580 } else if (!prefixcmp(next, "%(directory)")) {
2581 value = opt_path;
2583 } else if (!prefixcmp(next, "%(file)")) {
2584 value = opt_file;
2586 } else if (!prefixcmp(next, "%(ref)")) {
2587 value = *opt_ref ? opt_ref : "HEAD";
2589 } else if (!prefixcmp(next, "%(head)")) {
2590 value = ref_head;
2592 } else if (!prefixcmp(next, "%(commit)")) {
2593 value = ref_commit;
2595 } else if (!prefixcmp(next, "%(blob)")) {
2596 value = ref_blob;
2598 } else {
2599 report("Unknown replacement: `%s`", next);
2600 return FALSE;
2603 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2604 return FALSE;
2606 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2609 dst_argv[argc] = strdup(buf);
2610 if (!dst_argv[argc])
2611 break;
2614 dst_argv[argc] = NULL;
2616 return src_argv[argc] == NULL;
2619 static bool
2620 restore_view_position(struct view *view)
2622 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2623 return FALSE;
2625 /* Changing the view position cancels the restoring. */
2626 /* FIXME: Changing back to the first line is not detected. */
2627 if (view->offset != 0 || view->lineno != 0) {
2628 view->p_restore = FALSE;
2629 return FALSE;
2632 if (view->p_lineno >= view->lines) {
2633 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2634 if (view->p_offset >= view->p_lineno) {
2635 unsigned long half = view->height / 2;
2637 if (view->p_lineno > half)
2638 view->p_offset = view->p_lineno - half;
2639 else
2640 view->p_offset = 0;
2644 if (view_is_displayed(view) &&
2645 view->offset != view->p_offset &&
2646 view->lineno != view->p_lineno)
2647 werase(view->win);
2649 view->offset = view->p_offset;
2650 view->lineno = view->p_lineno;
2651 view->p_restore = FALSE;
2653 return TRUE;
2656 static void
2657 end_update(struct view *view, bool force)
2659 if (!view->pipe)
2660 return;
2661 while (!view->ops->read(view, NULL))
2662 if (!force)
2663 return;
2664 set_nonblocking_input(FALSE);
2665 if (force)
2666 kill_io(view->pipe);
2667 done_io(view->pipe);
2668 view->pipe = NULL;
2671 static void
2672 setup_update(struct view *view, const char *vid)
2674 set_nonblocking_input(TRUE);
2675 reset_view(view);
2676 string_copy_rev(view->vid, vid);
2677 view->pipe = &view->io;
2678 view->start_time = time(NULL);
2681 static bool
2682 prepare_update(struct view *view, const char *argv[], const char *dir,
2683 enum format_flags flags)
2685 if (view->pipe)
2686 end_update(view, TRUE);
2687 return init_io_rd(&view->io, argv, dir, flags);
2690 static bool
2691 prepare_update_file(struct view *view, const char *name)
2693 if (view->pipe)
2694 end_update(view, TRUE);
2695 return io_open(&view->io, name);
2698 static bool
2699 begin_update(struct view *view, bool refresh)
2701 if (view->pipe)
2702 end_update(view, TRUE);
2704 if (refresh) {
2705 if (!start_io(&view->io))
2706 return FALSE;
2708 } else {
2709 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2710 opt_path[0] = 0;
2712 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2713 return FALSE;
2715 /* Put the current ref_* value to the view title ref
2716 * member. This is needed by the blob view. Most other
2717 * views sets it automatically after loading because the
2718 * first line is a commit line. */
2719 string_copy_rev(view->ref, view->id);
2722 setup_update(view, view->id);
2724 return TRUE;
2727 #define ITEM_CHUNK_SIZE 256
2728 static void *
2729 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2731 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2732 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2734 if (mem == NULL || num_chunks != num_chunks_new) {
2735 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2736 mem = realloc(mem, *size * item_size);
2739 return mem;
2742 static struct line *
2743 realloc_lines(struct view *view, size_t line_size)
2745 size_t alloc = view->line_alloc;
2746 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2747 sizeof(*view->line));
2749 if (!tmp)
2750 return NULL;
2752 view->line = tmp;
2753 view->line_alloc = alloc;
2754 return view->line;
2757 static bool
2758 update_view(struct view *view)
2760 char out_buffer[BUFSIZ * 2];
2761 char *line;
2762 /* Clear the view and redraw everything since the tree sorting
2763 * might have rearranged things. */
2764 bool redraw = view->lines == 0;
2765 bool can_read = TRUE;
2767 if (!view->pipe)
2768 return TRUE;
2770 if (!io_can_read(view->pipe)) {
2771 if (view->lines == 0) {
2772 time_t secs = time(NULL) - view->start_time;
2774 if (secs > 1 && secs > view->update_secs) {
2775 if (view->update_secs == 0)
2776 redraw_view(view);
2777 update_view_title(view);
2778 view->update_secs = secs;
2781 return TRUE;
2784 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2785 if (opt_iconv != ICONV_NONE) {
2786 ICONV_CONST char *inbuf = line;
2787 size_t inlen = strlen(line) + 1;
2789 char *outbuf = out_buffer;
2790 size_t outlen = sizeof(out_buffer);
2792 size_t ret;
2794 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2795 if (ret != (size_t) -1)
2796 line = out_buffer;
2799 if (!view->ops->read(view, line)) {
2800 report("Allocation failure");
2801 end_update(view, TRUE);
2802 return FALSE;
2807 unsigned long lines = view->lines;
2808 int digits;
2810 for (digits = 0; lines; digits++)
2811 lines /= 10;
2813 /* Keep the displayed view in sync with line number scaling. */
2814 if (digits != view->digits) {
2815 view->digits = digits;
2816 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2817 redraw = TRUE;
2821 if (io_error(view->pipe)) {
2822 report("Failed to read: %s", io_strerror(view->pipe));
2823 end_update(view, TRUE);
2825 } else if (io_eof(view->pipe)) {
2826 report("");
2827 end_update(view, FALSE);
2830 if (restore_view_position(view))
2831 redraw = TRUE;
2833 if (!view_is_displayed(view))
2834 return TRUE;
2836 if (redraw)
2837 redraw_view_from(view, 0);
2838 else
2839 redraw_view_dirty(view);
2841 /* Update the title _after_ the redraw so that if the redraw picks up a
2842 * commit reference in view->ref it'll be available here. */
2843 update_view_title(view);
2844 return TRUE;
2847 static struct line *
2848 add_line_data(struct view *view, void *data, enum line_type type)
2850 struct line *line;
2852 if (!realloc_lines(view, view->lines + 1))
2853 return NULL;
2855 line = &view->line[view->lines++];
2856 memset(line, 0, sizeof(*line));
2857 line->type = type;
2858 line->data = data;
2859 line->dirty = 1;
2861 return line;
2864 static struct line *
2865 add_line_text(struct view *view, const char *text, enum line_type type)
2867 char *data = text ? strdup(text) : NULL;
2869 return data ? add_line_data(view, data, type) : NULL;
2872 static struct line *
2873 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2875 char buf[SIZEOF_STR];
2876 va_list args;
2878 va_start(args, fmt);
2879 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2880 buf[0] = 0;
2881 va_end(args);
2883 return buf[0] ? add_line_text(view, buf, type) : NULL;
2887 * View opening
2890 enum open_flags {
2891 OPEN_DEFAULT = 0, /* Use default view switching. */
2892 OPEN_SPLIT = 1, /* Split current view. */
2893 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2894 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2895 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2896 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2897 OPEN_PREPARED = 32, /* Open already prepared command. */
2900 static void
2901 open_view(struct view *prev, enum request request, enum open_flags flags)
2903 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2904 bool split = !!(flags & OPEN_SPLIT);
2905 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2906 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2907 struct view *view = VIEW(request);
2908 int nviews = displayed_views();
2909 struct view *base_view = display[0];
2911 if (view == prev && nviews == 1 && !reload) {
2912 report("Already in %s view", view->name);
2913 return;
2916 if (view->git_dir && !opt_git_dir[0]) {
2917 report("The %s view is disabled in pager view", view->name);
2918 return;
2921 if (split) {
2922 display[1] = view;
2923 if (!backgrounded)
2924 current_view = 1;
2925 } else if (!nomaximize) {
2926 /* Maximize the current view. */
2927 memset(display, 0, sizeof(display));
2928 current_view = 0;
2929 display[current_view] = view;
2932 /* Resize the view when switching between split- and full-screen,
2933 * or when switching between two different full-screen views. */
2934 if (nviews != displayed_views() ||
2935 (nviews == 1 && base_view != display[0]))
2936 resize_display();
2938 if (view->ops->open) {
2939 if (view->pipe)
2940 end_update(view, TRUE);
2941 if (!view->ops->open(view)) {
2942 report("Failed to load %s view", view->name);
2943 return;
2945 restore_view_position(view);
2947 } else if ((reload || strcmp(view->vid, view->id)) &&
2948 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2949 report("Failed to load %s view", view->name);
2950 return;
2953 if (split && prev->lineno - prev->offset >= prev->height) {
2954 /* Take the title line into account. */
2955 int lines = prev->lineno - prev->offset - prev->height + 1;
2957 /* Scroll the view that was split if the current line is
2958 * outside the new limited view. */
2959 do_scroll_view(prev, lines);
2962 if (prev && view != prev) {
2963 if (split && !backgrounded) {
2964 /* "Blur" the previous view. */
2965 update_view_title(prev);
2968 view->parent = prev;
2971 if (view->pipe && view->lines == 0) {
2972 /* Clear the old view and let the incremental updating refill
2973 * the screen. */
2974 werase(view->win);
2975 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2976 report("");
2977 } else if (view_is_displayed(view)) {
2978 redraw_view(view);
2979 report("");
2982 /* If the view is backgrounded the above calls to report()
2983 * won't redraw the view title. */
2984 if (backgrounded)
2985 update_view_title(view);
2988 static void
2989 open_external_viewer(const char *argv[], const char *dir)
2991 def_prog_mode(); /* save current tty modes */
2992 endwin(); /* restore original tty modes */
2993 run_io_fg(argv, dir);
2994 fprintf(stderr, "Press Enter to continue");
2995 getc(opt_tty);
2996 reset_prog_mode();
2997 redraw_display(TRUE);
3000 static void
3001 open_mergetool(const char *file)
3003 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3005 open_external_viewer(mergetool_argv, opt_cdup);
3008 static void
3009 open_editor(bool from_root, const char *file)
3011 const char *editor_argv[] = { "vi", file, NULL };
3012 const char *editor;
3014 editor = getenv("GIT_EDITOR");
3015 if (!editor && *opt_editor)
3016 editor = opt_editor;
3017 if (!editor)
3018 editor = getenv("VISUAL");
3019 if (!editor)
3020 editor = getenv("EDITOR");
3021 if (!editor)
3022 editor = "vi";
3024 editor_argv[0] = editor;
3025 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3028 static void
3029 open_run_request(enum request request)
3031 struct run_request *req = get_run_request(request);
3032 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3034 if (!req) {
3035 report("Unknown run request");
3036 return;
3039 if (format_argv(argv, req->argv, FORMAT_ALL))
3040 open_external_viewer(argv, NULL);
3041 free_argv(argv);
3045 * User request switch noodle
3048 static int
3049 view_driver(struct view *view, enum request request)
3051 int i;
3053 if (request == REQ_NONE) {
3054 doupdate();
3055 return TRUE;
3058 if (request > REQ_NONE) {
3059 open_run_request(request);
3060 /* FIXME: When all views can refresh always do this. */
3061 if (view == VIEW(REQ_VIEW_STATUS) ||
3062 view == VIEW(REQ_VIEW_MAIN) ||
3063 view == VIEW(REQ_VIEW_LOG) ||
3064 view == VIEW(REQ_VIEW_STAGE))
3065 request = REQ_REFRESH;
3066 else
3067 return TRUE;
3070 if (view && view->lines) {
3071 request = view->ops->request(view, request, &view->line[view->lineno]);
3072 if (request == REQ_NONE)
3073 return TRUE;
3076 switch (request) {
3077 case REQ_MOVE_UP:
3078 case REQ_MOVE_DOWN:
3079 case REQ_MOVE_PAGE_UP:
3080 case REQ_MOVE_PAGE_DOWN:
3081 case REQ_MOVE_FIRST_LINE:
3082 case REQ_MOVE_LAST_LINE:
3083 move_view(view, request);
3084 break;
3086 case REQ_SCROLL_LINE_DOWN:
3087 case REQ_SCROLL_LINE_UP:
3088 case REQ_SCROLL_PAGE_DOWN:
3089 case REQ_SCROLL_PAGE_UP:
3090 scroll_view(view, request);
3091 break;
3093 case REQ_VIEW_BLAME:
3094 if (!opt_file[0]) {
3095 report("No file chosen, press %s to open tree view",
3096 get_key(REQ_VIEW_TREE));
3097 break;
3099 open_view(view, request, OPEN_DEFAULT);
3100 break;
3102 case REQ_VIEW_BLOB:
3103 if (!ref_blob[0]) {
3104 report("No file chosen, press %s to open tree view",
3105 get_key(REQ_VIEW_TREE));
3106 break;
3108 open_view(view, request, OPEN_DEFAULT);
3109 break;
3111 case REQ_VIEW_PAGER:
3112 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3113 report("No pager content, press %s to run command from prompt",
3114 get_key(REQ_PROMPT));
3115 break;
3117 open_view(view, request, OPEN_DEFAULT);
3118 break;
3120 case REQ_VIEW_STAGE:
3121 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3122 report("No stage content, press %s to open the status view and choose file",
3123 get_key(REQ_VIEW_STATUS));
3124 break;
3126 open_view(view, request, OPEN_DEFAULT);
3127 break;
3129 case REQ_VIEW_STATUS:
3130 if (opt_is_inside_work_tree == FALSE) {
3131 report("The status view requires a working tree");
3132 break;
3134 open_view(view, request, OPEN_DEFAULT);
3135 break;
3137 case REQ_VIEW_MAIN:
3138 case REQ_VIEW_DIFF:
3139 case REQ_VIEW_LOG:
3140 case REQ_VIEW_TREE:
3141 case REQ_VIEW_HELP:
3142 open_view(view, request, OPEN_DEFAULT);
3143 break;
3145 case REQ_NEXT:
3146 case REQ_PREVIOUS:
3147 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3149 if ((view == VIEW(REQ_VIEW_DIFF) &&
3150 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3151 (view == VIEW(REQ_VIEW_DIFF) &&
3152 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3153 (view == VIEW(REQ_VIEW_STAGE) &&
3154 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3155 (view == VIEW(REQ_VIEW_BLOB) &&
3156 view->parent == VIEW(REQ_VIEW_TREE))) {
3157 int line;
3159 view = view->parent;
3160 line = view->lineno;
3161 move_view(view, request);
3162 if (view_is_displayed(view))
3163 update_view_title(view);
3164 if (line != view->lineno)
3165 view->ops->request(view, REQ_ENTER,
3166 &view->line[view->lineno]);
3168 } else {
3169 move_view(view, request);
3171 break;
3173 case REQ_VIEW_NEXT:
3175 int nviews = displayed_views();
3176 int next_view = (current_view + 1) % nviews;
3178 if (next_view == current_view) {
3179 report("Only one view is displayed");
3180 break;
3183 current_view = next_view;
3184 /* Blur out the title of the previous view. */
3185 update_view_title(view);
3186 report("");
3187 break;
3189 case REQ_REFRESH:
3190 report("Refreshing is not yet supported for the %s view", view->name);
3191 break;
3193 case REQ_MAXIMIZE:
3194 if (displayed_views() == 2)
3195 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3196 break;
3198 case REQ_TOGGLE_LINENO:
3199 toggle_view_option(&opt_line_number, "line numbers");
3200 break;
3202 case REQ_TOGGLE_DATE:
3203 toggle_view_option(&opt_date, "date display");
3204 break;
3206 case REQ_TOGGLE_AUTHOR:
3207 toggle_view_option(&opt_author, "author display");
3208 break;
3210 case REQ_TOGGLE_REV_GRAPH:
3211 toggle_view_option(&opt_rev_graph, "revision graph display");
3212 break;
3214 case REQ_TOGGLE_REFS:
3215 toggle_view_option(&opt_show_refs, "reference display");
3216 break;
3218 case REQ_SEARCH:
3219 case REQ_SEARCH_BACK:
3220 search_view(view, request);
3221 break;
3223 case REQ_FIND_NEXT:
3224 case REQ_FIND_PREV:
3225 find_next(view, request);
3226 break;
3228 case REQ_STOP_LOADING:
3229 for (i = 0; i < ARRAY_SIZE(views); i++) {
3230 view = &views[i];
3231 if (view->pipe)
3232 report("Stopped loading the %s view", view->name),
3233 end_update(view, TRUE);
3235 break;
3237 case REQ_SHOW_VERSION:
3238 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3239 return TRUE;
3241 case REQ_SCREEN_REDRAW:
3242 redraw_display(TRUE);
3243 break;
3245 case REQ_EDIT:
3246 report("Nothing to edit");
3247 break;
3249 case REQ_ENTER:
3250 report("Nothing to enter");
3251 break;
3253 case REQ_VIEW_CLOSE:
3254 /* XXX: Mark closed views by letting view->parent point to the
3255 * view itself. Parents to closed view should never be
3256 * followed. */
3257 if (view->parent &&
3258 view->parent->parent != view->parent) {
3259 memset(display, 0, sizeof(display));
3260 current_view = 0;
3261 display[current_view] = view->parent;
3262 view->parent = view;
3263 resize_display();
3264 redraw_display(FALSE);
3265 report("");
3266 break;
3268 /* Fall-through */
3269 case REQ_QUIT:
3270 return FALSE;
3272 default:
3273 report("Unknown key, press 'h' for help");
3274 return TRUE;
3277 return TRUE;
3282 * View backend utilities
3285 /* Parse author lines where the name may be empty:
3286 * author <email@address.tld> 1138474660 +0100
3288 static void
3289 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3291 char *nameend = strchr(ident, '<');
3292 char *emailend = strchr(ident, '>');
3294 if (nameend && emailend)
3295 *nameend = *emailend = 0;
3296 ident = chomp_string(ident);
3297 if (!*ident) {
3298 if (nameend)
3299 ident = chomp_string(nameend + 1);
3300 if (!*ident)
3301 ident = "Unknown";
3304 string_ncopy_do(author, authorsize, ident, strlen(ident));
3306 /* Parse epoch and timezone */
3307 if (emailend && emailend[1] == ' ') {
3308 char *secs = emailend + 2;
3309 char *zone = strchr(secs, ' ');
3310 time_t time = (time_t) atol(secs);
3312 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3313 long tz;
3315 zone++;
3316 tz = ('0' - zone[1]) * 60 * 60 * 10;
3317 tz += ('0' - zone[2]) * 60 * 60;
3318 tz += ('0' - zone[3]) * 60;
3319 tz += ('0' - zone[4]) * 60;
3321 if (zone[0] == '-')
3322 tz = -tz;
3324 time -= tz;
3327 gmtime_r(&time, tm);
3331 static enum input_status
3332 select_commit_parent_handler(void *data, char *buf, int c)
3334 size_t parents = *(size_t *) data;
3335 int parent = 0;
3337 if (!isdigit(c))
3338 return INPUT_SKIP;
3340 if (*buf)
3341 parent = atoi(buf) * 10;
3342 parent += c - '0';
3344 if (parent > parents)
3345 return INPUT_SKIP;
3346 return INPUT_OK;
3349 static bool
3350 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3352 char buf[SIZEOF_STR * 4];
3353 const char *revlist_argv[] = {
3354 "git", "rev-list", "-1", "--parents", id, NULL
3356 int parents;
3358 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3359 !*chomp_string(buf) ||
3360 (parents = (strlen(buf) / 40) - 1) < 0) {
3361 report("Failed to get parent information");
3362 return FALSE;
3364 } else if (parents == 0) {
3365 report("The selected commit has no parents");
3366 return FALSE;
3369 if (parents > 1) {
3370 char prompt[SIZEOF_STR];
3371 char *result;
3373 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3374 return FALSE;
3375 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3376 if (!result)
3377 return FALSE;
3378 parents = atoi(result);
3381 string_copy_rev(rev, &buf[41 * parents]);
3382 return TRUE;
3386 * Pager backend
3389 static bool
3390 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3392 char *text = line->data;
3394 if (opt_line_number && draw_lineno(view, lineno))
3395 return TRUE;
3397 draw_text(view, line->type, text, TRUE);
3398 return TRUE;
3401 static bool
3402 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3404 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3405 char refbuf[SIZEOF_STR];
3406 char *ref = NULL;
3408 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3409 ref = chomp_string(refbuf);
3411 if (!ref || !*ref)
3412 return TRUE;
3414 /* This is the only fatal call, since it can "corrupt" the buffer. */
3415 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3416 return FALSE;
3418 return TRUE;
3421 static void
3422 add_pager_refs(struct view *view, struct line *line)
3424 char buf[SIZEOF_STR];
3425 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3426 struct ref **refs;
3427 size_t bufpos = 0, refpos = 0;
3428 const char *sep = "Refs: ";
3429 bool is_tag = FALSE;
3431 assert(line->type == LINE_COMMIT);
3433 refs = get_refs(commit_id);
3434 if (!refs) {
3435 if (view == VIEW(REQ_VIEW_DIFF))
3436 goto try_add_describe_ref;
3437 return;
3440 do {
3441 struct ref *ref = refs[refpos];
3442 const char *fmt = ref->tag ? "%s[%s]" :
3443 ref->remote ? "%s<%s>" : "%s%s";
3445 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3446 return;
3447 sep = ", ";
3448 if (ref->tag)
3449 is_tag = TRUE;
3450 } while (refs[refpos++]->next);
3452 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3453 try_add_describe_ref:
3454 /* Add <tag>-g<commit_id> "fake" reference. */
3455 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3456 return;
3459 if (bufpos == 0)
3460 return;
3462 add_line_text(view, buf, LINE_PP_REFS);
3465 static bool
3466 pager_read(struct view *view, char *data)
3468 struct line *line;
3470 if (!data)
3471 return TRUE;
3473 line = add_line_text(view, data, get_line_type(data));
3474 if (!line)
3475 return FALSE;
3477 if (line->type == LINE_COMMIT &&
3478 (view == VIEW(REQ_VIEW_DIFF) ||
3479 view == VIEW(REQ_VIEW_LOG)))
3480 add_pager_refs(view, line);
3482 return TRUE;
3485 static enum request
3486 pager_request(struct view *view, enum request request, struct line *line)
3488 int split = 0;
3490 if (request != REQ_ENTER)
3491 return request;
3493 if (line->type == LINE_COMMIT &&
3494 (view == VIEW(REQ_VIEW_LOG) ||
3495 view == VIEW(REQ_VIEW_PAGER))) {
3496 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3497 split = 1;
3500 /* Always scroll the view even if it was split. That way
3501 * you can use Enter to scroll through the log view and
3502 * split open each commit diff. */
3503 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3505 /* FIXME: A minor workaround. Scrolling the view will call report("")
3506 * but if we are scrolling a non-current view this won't properly
3507 * update the view title. */
3508 if (split)
3509 update_view_title(view);
3511 return REQ_NONE;
3514 static bool
3515 pager_grep(struct view *view, struct line *line)
3517 regmatch_t pmatch;
3518 char *text = line->data;
3520 if (!*text)
3521 return FALSE;
3523 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3524 return FALSE;
3526 return TRUE;
3529 static void
3530 pager_select(struct view *view, struct line *line)
3532 if (line->type == LINE_COMMIT) {
3533 char *text = (char *)line->data + STRING_SIZE("commit ");
3535 if (view != VIEW(REQ_VIEW_PAGER))
3536 string_copy_rev(view->ref, text);
3537 string_copy_rev(ref_commit, text);
3541 static struct view_ops pager_ops = {
3542 "line",
3543 NULL,
3544 NULL,
3545 pager_read,
3546 pager_draw,
3547 pager_request,
3548 pager_grep,
3549 pager_select,
3552 static const char *log_argv[SIZEOF_ARG] = {
3553 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3556 static enum request
3557 log_request(struct view *view, enum request request, struct line *line)
3559 switch (request) {
3560 case REQ_REFRESH:
3561 load_refs();
3562 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3563 return REQ_NONE;
3564 default:
3565 return pager_request(view, request, line);
3569 static struct view_ops log_ops = {
3570 "line",
3571 log_argv,
3572 NULL,
3573 pager_read,
3574 pager_draw,
3575 log_request,
3576 pager_grep,
3577 pager_select,
3580 static const char *diff_argv[SIZEOF_ARG] = {
3581 "git", "show", "--pretty=fuller", "--no-color", "--root",
3582 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3585 static struct view_ops diff_ops = {
3586 "line",
3587 diff_argv,
3588 NULL,
3589 pager_read,
3590 pager_draw,
3591 pager_request,
3592 pager_grep,
3593 pager_select,
3597 * Help backend
3600 static bool
3601 help_open(struct view *view)
3603 char buf[SIZEOF_STR];
3604 size_t bufpos;
3605 int i;
3607 if (view->lines > 0)
3608 return TRUE;
3610 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3612 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3613 const char *key;
3615 if (req_info[i].request == REQ_NONE)
3616 continue;
3618 if (!req_info[i].request) {
3619 add_line_text(view, "", LINE_DEFAULT);
3620 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3621 continue;
3624 key = get_key(req_info[i].request);
3625 if (!*key)
3626 key = "(no key defined)";
3628 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3629 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3630 if (buf[bufpos] == '_')
3631 buf[bufpos] = '-';
3634 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3635 key, buf, req_info[i].help);
3638 if (run_requests) {
3639 add_line_text(view, "", LINE_DEFAULT);
3640 add_line_text(view, "External commands:", LINE_DEFAULT);
3643 for (i = 0; i < run_requests; i++) {
3644 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3645 const char *key;
3646 int argc;
3648 if (!req)
3649 continue;
3651 key = get_key_name(req->key);
3652 if (!*key)
3653 key = "(no key defined)";
3655 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3656 if (!string_format_from(buf, &bufpos, "%s%s",
3657 argc ? " " : "", req->argv[argc]))
3658 return REQ_NONE;
3660 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3661 keymap_table[req->keymap].name, key, buf);
3664 return TRUE;
3667 static struct view_ops help_ops = {
3668 "line",
3669 NULL,
3670 help_open,
3671 NULL,
3672 pager_draw,
3673 pager_request,
3674 pager_grep,
3675 pager_select,
3680 * Tree backend
3683 struct tree_stack_entry {
3684 struct tree_stack_entry *prev; /* Entry below this in the stack */
3685 unsigned long lineno; /* Line number to restore */
3686 char *name; /* Position of name in opt_path */
3689 /* The top of the path stack. */
3690 static struct tree_stack_entry *tree_stack = NULL;
3691 unsigned long tree_lineno = 0;
3693 static void
3694 pop_tree_stack_entry(void)
3696 struct tree_stack_entry *entry = tree_stack;
3698 tree_lineno = entry->lineno;
3699 entry->name[0] = 0;
3700 tree_stack = entry->prev;
3701 free(entry);
3704 static void
3705 push_tree_stack_entry(const char *name, unsigned long lineno)
3707 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3708 size_t pathlen = strlen(opt_path);
3710 if (!entry)
3711 return;
3713 entry->prev = tree_stack;
3714 entry->name = opt_path + pathlen;
3715 tree_stack = entry;
3717 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3718 pop_tree_stack_entry();
3719 return;
3722 /* Move the current line to the first tree entry. */
3723 tree_lineno = 1;
3724 entry->lineno = lineno;
3727 /* Parse output from git-ls-tree(1):
3729 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3730 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3731 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3732 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3735 #define SIZEOF_TREE_ATTR \
3736 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3738 #define SIZEOF_TREE_MODE \
3739 STRING_SIZE("100644 ")
3741 #define TREE_ID_OFFSET \
3742 STRING_SIZE("100644 blob ")
3744 struct tree_entry {
3745 char id[SIZEOF_REV];
3746 mode_t mode;
3747 struct tm time; /* Date from the author ident. */
3748 char author[75]; /* Author of the commit. */
3749 char name[1];
3752 static const char *
3753 tree_path(struct line *line)
3755 return ((struct tree_entry *) line->data)->name;
3759 static int
3760 tree_compare_entry(struct line *line1, struct line *line2)
3762 if (line1->type != line2->type)
3763 return line1->type == LINE_TREE_DIR ? -1 : 1;
3764 return strcmp(tree_path(line1), tree_path(line2));
3767 static struct line *
3768 tree_entry(struct view *view, enum line_type type, const char *path,
3769 const char *mode, const char *id)
3771 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3772 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3774 if (!entry || !line) {
3775 free(entry);
3776 return NULL;
3779 strncpy(entry->name, path, strlen(path));
3780 if (mode)
3781 entry->mode = strtoul(mode, NULL, 8);
3782 if (id)
3783 string_copy_rev(entry->id, id);
3785 return line;
3788 static bool
3789 tree_read_date(struct view *view, char *text, bool *read_date)
3791 static char author_name[SIZEOF_STR];
3792 static struct tm author_time;
3794 if (!text && *read_date) {
3795 *read_date = FALSE;
3796 return TRUE;
3798 } else if (!text) {
3799 char *path = *opt_path ? opt_path : ".";
3800 /* Find next entry to process */
3801 const char *log_file[] = {
3802 "git", "log", "--no-color", "--pretty=raw",
3803 "--cc", "--raw", view->id, "--", path, NULL
3805 struct io io = {};
3807 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3808 report("Failed to load tree data");
3809 return TRUE;
3812 done_io(view->pipe);
3813 view->io = io;
3814 *read_date = TRUE;
3815 return FALSE;
3817 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3818 parse_author_line(text + STRING_SIZE("author "),
3819 author_name, sizeof(author_name), &author_time);
3821 } else if (*text == ':') {
3822 char *pos;
3823 size_t annotated = 1;
3824 size_t i;
3826 pos = strchr(text, '\t');
3827 if (!pos)
3828 return TRUE;
3829 text = pos + 1;
3830 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3831 text += strlen(opt_prefix);
3832 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3833 text += strlen(opt_path);
3834 pos = strchr(text, '/');
3835 if (pos)
3836 *pos = 0;
3838 for (i = 1; i < view->lines; i++) {
3839 struct line *line = &view->line[i];
3840 struct tree_entry *entry = line->data;
3842 annotated += !!*entry->author;
3843 if (*entry->author || strcmp(entry->name, text))
3844 continue;
3846 string_copy(entry->author, author_name);
3847 memcpy(&entry->time, &author_time, sizeof(entry->time));
3848 line->dirty = 1;
3849 break;
3852 if (annotated == view->lines)
3853 kill_io(view->pipe);
3855 return TRUE;
3858 static bool
3859 tree_read(struct view *view, char *text)
3861 static bool read_date = FALSE;
3862 struct tree_entry *data;
3863 struct line *entry, *line;
3864 enum line_type type;
3865 size_t textlen = text ? strlen(text) : 0;
3866 char *path = text + SIZEOF_TREE_ATTR;
3868 if (read_date || !text)
3869 return tree_read_date(view, text, &read_date);
3871 if (textlen <= SIZEOF_TREE_ATTR)
3872 return FALSE;
3873 if (view->lines == 0 &&
3874 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3875 return FALSE;
3877 /* Strip the path part ... */
3878 if (*opt_path) {
3879 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3880 size_t striplen = strlen(opt_path);
3882 if (pathlen > striplen)
3883 memmove(path, path + striplen,
3884 pathlen - striplen + 1);
3886 /* Insert "link" to parent directory. */
3887 if (view->lines == 1 &&
3888 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3889 return FALSE;
3892 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3893 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3894 if (!entry)
3895 return FALSE;
3896 data = entry->data;
3898 /* Skip "Directory ..." and ".." line. */
3899 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3900 if (tree_compare_entry(line, entry) <= 0)
3901 continue;
3903 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3905 line->data = data;
3906 line->type = type;
3907 for (; line <= entry; line++)
3908 line->dirty = line->cleareol = 1;
3909 return TRUE;
3912 if (tree_lineno > view->lineno) {
3913 view->lineno = tree_lineno;
3914 tree_lineno = 0;
3917 return TRUE;
3920 static bool
3921 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3923 struct tree_entry *entry = line->data;
3925 if (line->type == LINE_TREE_PARENT) {
3926 if (draw_text(view, line->type, "Directory path /", TRUE))
3927 return TRUE;
3928 } else {
3929 char mode[11] = "-r--r--r--";
3931 if (S_ISDIR(entry->mode)) {
3932 mode[3] = mode[6] = mode[9] = 'x';
3933 mode[0] = 'd';
3935 if (S_ISLNK(entry->mode))
3936 mode[0] = 'l';
3937 if (entry->mode & S_IWUSR)
3938 mode[2] = 'w';
3939 if (entry->mode & S_IXUSR)
3940 mode[3] = 'x';
3941 if (entry->mode & S_IXGRP)
3942 mode[6] = 'x';
3943 if (entry->mode & S_IXOTH)
3944 mode[9] = 'x';
3945 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3946 return TRUE;
3948 if (opt_author && draw_author(view, entry->author))
3949 return TRUE;
3951 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3952 return TRUE;
3954 if (draw_text(view, line->type, entry->name, TRUE))
3955 return TRUE;
3956 return TRUE;
3959 static void
3960 open_blob_editor()
3962 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3963 int fd = mkstemp(file);
3965 if (fd == -1)
3966 report("Failed to create temporary file");
3967 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3968 report("Failed to save blob data to file");
3969 else
3970 open_editor(FALSE, file);
3971 if (fd != -1)
3972 unlink(file);
3975 static enum request
3976 tree_request(struct view *view, enum request request, struct line *line)
3978 enum open_flags flags;
3980 switch (request) {
3981 case REQ_VIEW_BLAME:
3982 if (line->type != LINE_TREE_FILE) {
3983 report("Blame only supported for files");
3984 return REQ_NONE;
3987 string_copy(opt_ref, view->vid);
3988 return request;
3990 case REQ_EDIT:
3991 if (line->type != LINE_TREE_FILE) {
3992 report("Edit only supported for files");
3993 } else if (!is_head_commit(view->vid)) {
3994 open_blob_editor();
3995 } else {
3996 open_editor(TRUE, opt_file);
3998 return REQ_NONE;
4000 case REQ_PARENT:
4001 if (!*opt_path) {
4002 /* quit view if at top of tree */
4003 return REQ_VIEW_CLOSE;
4005 /* fake 'cd ..' */
4006 line = &view->line[1];
4007 break;
4009 case REQ_ENTER:
4010 break;
4012 default:
4013 return request;
4016 /* Cleanup the stack if the tree view is at a different tree. */
4017 while (!*opt_path && tree_stack)
4018 pop_tree_stack_entry();
4020 switch (line->type) {
4021 case LINE_TREE_DIR:
4022 /* Depending on whether it is a subdir or parent (updir?) link
4023 * mangle the path buffer. */
4024 if (line == &view->line[1] && *opt_path) {
4025 pop_tree_stack_entry();
4027 } else {
4028 const char *basename = tree_path(line);
4030 push_tree_stack_entry(basename, view->lineno);
4033 /* Trees and subtrees share the same ID, so they are not not
4034 * unique like blobs. */
4035 flags = OPEN_RELOAD;
4036 request = REQ_VIEW_TREE;
4037 break;
4039 case LINE_TREE_FILE:
4040 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4041 request = REQ_VIEW_BLOB;
4042 break;
4044 default:
4045 return REQ_NONE;
4048 open_view(view, request, flags);
4049 if (request == REQ_VIEW_TREE)
4050 view->lineno = tree_lineno;
4052 return REQ_NONE;
4055 static void
4056 tree_select(struct view *view, struct line *line)
4058 struct tree_entry *entry = line->data;
4060 if (line->type == LINE_TREE_FILE) {
4061 string_copy_rev(ref_blob, entry->id);
4062 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4064 } else if (line->type != LINE_TREE_DIR) {
4065 return;
4068 string_copy_rev(view->ref, entry->id);
4071 static const char *tree_argv[SIZEOF_ARG] = {
4072 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4075 static struct view_ops tree_ops = {
4076 "file",
4077 tree_argv,
4078 NULL,
4079 tree_read,
4080 tree_draw,
4081 tree_request,
4082 pager_grep,
4083 tree_select,
4086 static bool
4087 blob_read(struct view *view, char *line)
4089 if (!line)
4090 return TRUE;
4091 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4094 static enum request
4095 blob_request(struct view *view, enum request request, struct line *line)
4097 switch (request) {
4098 case REQ_EDIT:
4099 open_blob_editor();
4100 return REQ_NONE;
4101 default:
4102 return pager_request(view, request, line);
4106 static const char *blob_argv[SIZEOF_ARG] = {
4107 "git", "cat-file", "blob", "%(blob)", NULL
4110 static struct view_ops blob_ops = {
4111 "line",
4112 blob_argv,
4113 NULL,
4114 blob_read,
4115 pager_draw,
4116 blob_request,
4117 pager_grep,
4118 pager_select,
4122 * Blame backend
4124 * Loading the blame view is a two phase job:
4126 * 1. File content is read either using opt_file from the
4127 * filesystem or using git-cat-file.
4128 * 2. Then blame information is incrementally added by
4129 * reading output from git-blame.
4132 static const char *blame_head_argv[] = {
4133 "git", "blame", "--incremental", "--", "%(file)", NULL
4136 static const char *blame_ref_argv[] = {
4137 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4140 static const char *blame_cat_file_argv[] = {
4141 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4144 struct blame_commit {
4145 char id[SIZEOF_REV]; /* SHA1 ID. */
4146 char title[128]; /* First line of the commit message. */
4147 char author[75]; /* Author of the commit. */
4148 struct tm time; /* Date from the author ident. */
4149 char filename[128]; /* Name of file. */
4150 bool has_previous; /* Was a "previous" line detected. */
4153 struct blame {
4154 struct blame_commit *commit;
4155 char text[1];
4158 static bool
4159 blame_open(struct view *view)
4161 if (*opt_ref || !io_open(&view->io, opt_file)) {
4162 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4163 return FALSE;
4166 setup_update(view, opt_file);
4167 string_format(view->ref, "%s ...", opt_file);
4169 return TRUE;
4172 static struct blame_commit *
4173 get_blame_commit(struct view *view, const char *id)
4175 size_t i;
4177 for (i = 0; i < view->lines; i++) {
4178 struct blame *blame = view->line[i].data;
4180 if (!blame->commit)
4181 continue;
4183 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4184 return blame->commit;
4188 struct blame_commit *commit = calloc(1, sizeof(*commit));
4190 if (commit)
4191 string_ncopy(commit->id, id, SIZEOF_REV);
4192 return commit;
4196 static bool
4197 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4199 const char *pos = *posref;
4201 *posref = NULL;
4202 pos = strchr(pos + 1, ' ');
4203 if (!pos || !isdigit(pos[1]))
4204 return FALSE;
4205 *number = atoi(pos + 1);
4206 if (*number < min || *number > max)
4207 return FALSE;
4209 *posref = pos;
4210 return TRUE;
4213 static struct blame_commit *
4214 parse_blame_commit(struct view *view, const char *text, int *blamed)
4216 struct blame_commit *commit;
4217 struct blame *blame;
4218 const char *pos = text + SIZEOF_REV - 1;
4219 size_t lineno;
4220 size_t group;
4222 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4223 return NULL;
4225 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4226 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4227 return NULL;
4229 commit = get_blame_commit(view, text);
4230 if (!commit)
4231 return NULL;
4233 *blamed += group;
4234 while (group--) {
4235 struct line *line = &view->line[lineno + group - 1];
4237 blame = line->data;
4238 blame->commit = commit;
4239 line->dirty = 1;
4242 return commit;
4245 static bool
4246 blame_read_file(struct view *view, const char *line, bool *read_file)
4248 if (!line) {
4249 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4250 struct io io = {};
4252 if (view->lines == 0 && !view->parent)
4253 die("No blame exist for %s", view->vid);
4255 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4256 report("Failed to load blame data");
4257 return TRUE;
4260 done_io(view->pipe);
4261 view->io = io;
4262 *read_file = FALSE;
4263 return FALSE;
4265 } else {
4266 size_t linelen = strlen(line);
4267 struct blame *blame = malloc(sizeof(*blame) + linelen);
4269 blame->commit = NULL;
4270 strncpy(blame->text, line, linelen);
4271 blame->text[linelen] = 0;
4272 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4276 static bool
4277 match_blame_header(const char *name, char **line)
4279 size_t namelen = strlen(name);
4280 bool matched = !strncmp(name, *line, namelen);
4282 if (matched)
4283 *line += namelen;
4285 return matched;
4288 static bool
4289 blame_read(struct view *view, char *line)
4291 static struct blame_commit *commit = NULL;
4292 static int blamed = 0;
4293 static time_t author_time;
4294 static bool read_file = TRUE;
4296 if (read_file)
4297 return blame_read_file(view, line, &read_file);
4299 if (!line) {
4300 /* Reset all! */
4301 commit = NULL;
4302 blamed = 0;
4303 read_file = TRUE;
4304 string_format(view->ref, "%s", view->vid);
4305 if (view_is_displayed(view)) {
4306 update_view_title(view);
4307 redraw_view_from(view, 0);
4309 return TRUE;
4312 if (!commit) {
4313 commit = parse_blame_commit(view, line, &blamed);
4314 string_format(view->ref, "%s %2d%%", view->vid,
4315 view->lines ? blamed * 100 / view->lines : 0);
4317 } else if (match_blame_header("author ", &line)) {
4318 string_ncopy(commit->author, line, strlen(line));
4320 } else if (match_blame_header("author-time ", &line)) {
4321 author_time = (time_t) atol(line);
4323 } else if (match_blame_header("author-tz ", &line)) {
4324 long tz;
4326 tz = ('0' - line[1]) * 60 * 60 * 10;
4327 tz += ('0' - line[2]) * 60 * 60;
4328 tz += ('0' - line[3]) * 60;
4329 tz += ('0' - line[4]) * 60;
4331 if (line[0] == '-')
4332 tz = -tz;
4334 author_time -= tz;
4335 gmtime_r(&author_time, &commit->time);
4337 } else if (match_blame_header("summary ", &line)) {
4338 string_ncopy(commit->title, line, strlen(line));
4340 } else if (match_blame_header("previous ", &line)) {
4341 commit->has_previous = TRUE;
4343 } else if (match_blame_header("filename ", &line)) {
4344 string_ncopy(commit->filename, line, strlen(line));
4345 commit = NULL;
4348 return TRUE;
4351 static bool
4352 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4354 struct blame *blame = line->data;
4355 struct tm *time = NULL;
4356 const char *id = NULL, *author = NULL;
4358 if (blame->commit && *blame->commit->filename) {
4359 id = blame->commit->id;
4360 author = blame->commit->author;
4361 time = &blame->commit->time;
4364 if (opt_date && draw_date(view, time))
4365 return TRUE;
4367 if (opt_author && draw_author(view, author))
4368 return TRUE;
4370 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4371 return TRUE;
4373 if (draw_lineno(view, lineno))
4374 return TRUE;
4376 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4377 return TRUE;
4380 static bool
4381 check_blame_commit(struct blame *blame)
4383 if (!blame->commit)
4384 report("Commit data not loaded yet");
4385 else if (!strcmp(blame->commit->id, NULL_ID))
4386 report("No commit exist for the selected line");
4387 else
4388 return TRUE;
4389 return FALSE;
4392 static enum request
4393 blame_request(struct view *view, enum request request, struct line *line)
4395 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4396 struct blame *blame = line->data;
4398 switch (request) {
4399 case REQ_VIEW_BLAME:
4400 if (check_blame_commit(blame)) {
4401 string_copy(opt_ref, blame->commit->id);
4402 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4404 break;
4406 case REQ_PARENT:
4407 if (check_blame_commit(blame) &&
4408 select_commit_parent(blame->commit->id, opt_ref))
4409 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4410 break;
4412 case REQ_ENTER:
4413 if (!blame->commit) {
4414 report("No commit loaded yet");
4415 break;
4418 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4419 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4420 break;
4422 if (!strcmp(blame->commit->id, NULL_ID)) {
4423 struct view *diff = VIEW(REQ_VIEW_DIFF);
4424 const char *diff_index_argv[] = {
4425 "git", "diff-index", "--root", "--patch-with-stat",
4426 "-C", "-M", "HEAD", "--", view->vid, NULL
4429 if (!blame->commit->has_previous) {
4430 diff_index_argv[1] = "diff";
4431 diff_index_argv[2] = "--no-color";
4432 diff_index_argv[6] = "--";
4433 diff_index_argv[7] = "/dev/null";
4436 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4437 report("Failed to allocate diff command");
4438 break;
4440 flags |= OPEN_PREPARED;
4443 open_view(view, REQ_VIEW_DIFF, flags);
4444 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4445 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4446 break;
4448 default:
4449 return request;
4452 return REQ_NONE;
4455 static bool
4456 blame_grep(struct view *view, struct line *line)
4458 struct blame *blame = line->data;
4459 struct blame_commit *commit = blame->commit;
4460 regmatch_t pmatch;
4462 #define MATCH(text, on) \
4463 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4465 if (commit) {
4466 char buf[DATE_COLS + 1];
4468 if (MATCH(commit->title, 1) ||
4469 MATCH(commit->author, opt_author) ||
4470 MATCH(commit->id, opt_date))
4471 return TRUE;
4473 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4474 MATCH(buf, 1))
4475 return TRUE;
4478 return MATCH(blame->text, 1);
4480 #undef MATCH
4483 static void
4484 blame_select(struct view *view, struct line *line)
4486 struct blame *blame = line->data;
4487 struct blame_commit *commit = blame->commit;
4489 if (!commit)
4490 return;
4492 if (!strcmp(commit->id, NULL_ID))
4493 string_ncopy(ref_commit, "HEAD", 4);
4494 else
4495 string_copy_rev(ref_commit, commit->id);
4498 static struct view_ops blame_ops = {
4499 "line",
4500 NULL,
4501 blame_open,
4502 blame_read,
4503 blame_draw,
4504 blame_request,
4505 blame_grep,
4506 blame_select,
4510 * Status backend
4513 struct status {
4514 char status;
4515 struct {
4516 mode_t mode;
4517 char rev[SIZEOF_REV];
4518 char name[SIZEOF_STR];
4519 } old;
4520 struct {
4521 mode_t mode;
4522 char rev[SIZEOF_REV];
4523 char name[SIZEOF_STR];
4524 } new;
4527 static char status_onbranch[SIZEOF_STR];
4528 static struct status stage_status;
4529 static enum line_type stage_line_type;
4530 static size_t stage_chunks;
4531 static int *stage_chunk;
4533 /* This should work even for the "On branch" line. */
4534 static inline bool
4535 status_has_none(struct view *view, struct line *line)
4537 return line < view->line + view->lines && !line[1].data;
4540 /* Get fields from the diff line:
4541 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4543 static inline bool
4544 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4546 const char *old_mode = buf + 1;
4547 const char *new_mode = buf + 8;
4548 const char *old_rev = buf + 15;
4549 const char *new_rev = buf + 56;
4550 const char *status = buf + 97;
4552 if (bufsize < 98 ||
4553 old_mode[-1] != ':' ||
4554 new_mode[-1] != ' ' ||
4555 old_rev[-1] != ' ' ||
4556 new_rev[-1] != ' ' ||
4557 status[-1] != ' ')
4558 return FALSE;
4560 file->status = *status;
4562 string_copy_rev(file->old.rev, old_rev);
4563 string_copy_rev(file->new.rev, new_rev);
4565 file->old.mode = strtoul(old_mode, NULL, 8);
4566 file->new.mode = strtoul(new_mode, NULL, 8);
4568 file->old.name[0] = file->new.name[0] = 0;
4570 return TRUE;
4573 static bool
4574 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4576 struct status *file = NULL;
4577 struct status *unmerged = NULL;
4578 char *buf;
4579 struct io io = {};
4581 if (!run_io(&io, argv, NULL, IO_RD))
4582 return FALSE;
4584 add_line_data(view, NULL, type);
4586 while ((buf = io_get(&io, 0, TRUE))) {
4587 if (!file) {
4588 file = calloc(1, sizeof(*file));
4589 if (!file || !add_line_data(view, file, type))
4590 goto error_out;
4593 /* Parse diff info part. */
4594 if (status) {
4595 file->status = status;
4596 if (status == 'A')
4597 string_copy(file->old.rev, NULL_ID);
4599 } else if (!file->status) {
4600 if (!status_get_diff(file, buf, strlen(buf)))
4601 goto error_out;
4603 buf = io_get(&io, 0, TRUE);
4604 if (!buf)
4605 break;
4607 /* Collapse all 'M'odified entries that follow a
4608 * associated 'U'nmerged entry. */
4609 if (file->status == 'U') {
4610 unmerged = file;
4612 } else if (unmerged) {
4613 int collapse = !strcmp(buf, unmerged->new.name);
4615 unmerged = NULL;
4616 if (collapse) {
4617 free(file);
4618 file = NULL;
4619 view->lines--;
4620 continue;
4625 /* Grab the old name for rename/copy. */
4626 if (!*file->old.name &&
4627 (file->status == 'R' || file->status == 'C')) {
4628 string_ncopy(file->old.name, buf, strlen(buf));
4630 buf = io_get(&io, 0, TRUE);
4631 if (!buf)
4632 break;
4635 /* git-ls-files just delivers a NUL separated list of
4636 * file names similar to the second half of the
4637 * git-diff-* output. */
4638 string_ncopy(file->new.name, buf, strlen(buf));
4639 if (!*file->old.name)
4640 string_copy(file->old.name, file->new.name);
4641 file = NULL;
4644 if (io_error(&io)) {
4645 error_out:
4646 done_io(&io);
4647 return FALSE;
4650 if (!view->line[view->lines - 1].data)
4651 add_line_data(view, NULL, LINE_STAT_NONE);
4653 done_io(&io);
4654 return TRUE;
4657 /* Don't show unmerged entries in the staged section. */
4658 static const char *status_diff_index_argv[] = {
4659 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4660 "--cached", "-M", "HEAD", NULL
4663 static const char *status_diff_files_argv[] = {
4664 "git", "diff-files", "-z", NULL
4667 static const char *status_list_other_argv[] = {
4668 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4671 static const char *status_list_no_head_argv[] = {
4672 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4675 static const char *update_index_argv[] = {
4676 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4679 /* Restore the previous line number to stay in the context or select a
4680 * line with something that can be updated. */
4681 static void
4682 status_restore(struct view *view)
4684 if (view->p_lineno >= view->lines)
4685 view->p_lineno = view->lines - 1;
4686 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4687 view->p_lineno++;
4688 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4689 view->p_lineno--;
4691 /* If the above fails, always skip the "On branch" line. */
4692 if (view->p_lineno < view->lines)
4693 view->lineno = view->p_lineno;
4694 else
4695 view->lineno = 1;
4697 if (view->lineno < view->offset)
4698 view->offset = view->lineno;
4699 else if (view->offset + view->height <= view->lineno)
4700 view->offset = view->lineno - view->height + 1;
4702 view->p_restore = FALSE;
4705 /* First parse staged info using git-diff-index(1), then parse unstaged
4706 * info using git-diff-files(1), and finally untracked files using
4707 * git-ls-files(1). */
4708 static bool
4709 status_open(struct view *view)
4711 reset_view(view);
4713 add_line_data(view, NULL, LINE_STAT_HEAD);
4714 if (is_initial_commit())
4715 string_copy(status_onbranch, "Initial commit");
4716 else if (!*opt_head)
4717 string_copy(status_onbranch, "Not currently on any branch");
4718 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4719 return FALSE;
4721 run_io_bg(update_index_argv);
4723 if (is_initial_commit()) {
4724 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4725 return FALSE;
4726 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4727 return FALSE;
4730 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4731 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4732 return FALSE;
4734 /* Restore the exact position or use the specialized restore
4735 * mode? */
4736 if (!view->p_restore)
4737 status_restore(view);
4738 return TRUE;
4741 static bool
4742 status_draw(struct view *view, struct line *line, unsigned int lineno)
4744 struct status *status = line->data;
4745 enum line_type type;
4746 const char *text;
4748 if (!status) {
4749 switch (line->type) {
4750 case LINE_STAT_STAGED:
4751 type = LINE_STAT_SECTION;
4752 text = "Changes to be committed:";
4753 break;
4755 case LINE_STAT_UNSTAGED:
4756 type = LINE_STAT_SECTION;
4757 text = "Changed but not updated:";
4758 break;
4760 case LINE_STAT_UNTRACKED:
4761 type = LINE_STAT_SECTION;
4762 text = "Untracked files:";
4763 break;
4765 case LINE_STAT_NONE:
4766 type = LINE_DEFAULT;
4767 text = " (no files)";
4768 break;
4770 case LINE_STAT_HEAD:
4771 type = LINE_STAT_HEAD;
4772 text = status_onbranch;
4773 break;
4775 default:
4776 return FALSE;
4778 } else {
4779 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4781 buf[0] = status->status;
4782 if (draw_text(view, line->type, buf, TRUE))
4783 return TRUE;
4784 type = LINE_DEFAULT;
4785 text = status->new.name;
4788 draw_text(view, type, text, TRUE);
4789 return TRUE;
4792 static enum request
4793 status_enter(struct view *view, struct line *line)
4795 struct status *status = line->data;
4796 const char *oldpath = status ? status->old.name : NULL;
4797 /* Diffs for unmerged entries are empty when passing the new
4798 * path, so leave it empty. */
4799 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4800 const char *info;
4801 enum open_flags split;
4802 struct view *stage = VIEW(REQ_VIEW_STAGE);
4804 if (line->type == LINE_STAT_NONE ||
4805 (!status && line[1].type == LINE_STAT_NONE)) {
4806 report("No file to diff");
4807 return REQ_NONE;
4810 switch (line->type) {
4811 case LINE_STAT_STAGED:
4812 if (is_initial_commit()) {
4813 const char *no_head_diff_argv[] = {
4814 "git", "diff", "--no-color", "--patch-with-stat",
4815 "--", "/dev/null", newpath, NULL
4818 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4819 return REQ_QUIT;
4820 } else {
4821 const char *index_show_argv[] = {
4822 "git", "diff-index", "--root", "--patch-with-stat",
4823 "-C", "-M", "--cached", "HEAD", "--",
4824 oldpath, newpath, NULL
4827 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4828 return REQ_QUIT;
4831 if (status)
4832 info = "Staged changes to %s";
4833 else
4834 info = "Staged changes";
4835 break;
4837 case LINE_STAT_UNSTAGED:
4839 const char *files_show_argv[] = {
4840 "git", "diff-files", "--root", "--patch-with-stat",
4841 "-C", "-M", "--", oldpath, newpath, NULL
4844 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4845 return REQ_QUIT;
4846 if (status)
4847 info = "Unstaged changes to %s";
4848 else
4849 info = "Unstaged changes";
4850 break;
4852 case LINE_STAT_UNTRACKED:
4853 if (!newpath) {
4854 report("No file to show");
4855 return REQ_NONE;
4858 if (!suffixcmp(status->new.name, -1, "/")) {
4859 report("Cannot display a directory");
4860 return REQ_NONE;
4863 if (!prepare_update_file(stage, newpath))
4864 return REQ_QUIT;
4865 info = "Untracked file %s";
4866 break;
4868 case LINE_STAT_HEAD:
4869 return REQ_NONE;
4871 default:
4872 die("line type %d not handled in switch", line->type);
4875 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4876 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4877 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4878 if (status) {
4879 stage_status = *status;
4880 } else {
4881 memset(&stage_status, 0, sizeof(stage_status));
4884 stage_line_type = line->type;
4885 stage_chunks = 0;
4886 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4889 return REQ_NONE;
4892 static bool
4893 status_exists(struct status *status, enum line_type type)
4895 struct view *view = VIEW(REQ_VIEW_STATUS);
4896 unsigned long lineno;
4898 for (lineno = 0; lineno < view->lines; lineno++) {
4899 struct line *line = &view->line[lineno];
4900 struct status *pos = line->data;
4902 if (line->type != type)
4903 continue;
4904 if (!pos && (!status || !status->status) && line[1].data) {
4905 select_view_line(view, lineno);
4906 return TRUE;
4908 if (pos && !strcmp(status->new.name, pos->new.name)) {
4909 select_view_line(view, lineno);
4910 return TRUE;
4914 return FALSE;
4918 static bool
4919 status_update_prepare(struct io *io, enum line_type type)
4921 const char *staged_argv[] = {
4922 "git", "update-index", "-z", "--index-info", NULL
4924 const char *others_argv[] = {
4925 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4928 switch (type) {
4929 case LINE_STAT_STAGED:
4930 return run_io(io, staged_argv, opt_cdup, IO_WR);
4932 case LINE_STAT_UNSTAGED:
4933 return run_io(io, others_argv, opt_cdup, IO_WR);
4935 case LINE_STAT_UNTRACKED:
4936 return run_io(io, others_argv, NULL, IO_WR);
4938 default:
4939 die("line type %d not handled in switch", type);
4940 return FALSE;
4944 static bool
4945 status_update_write(struct io *io, struct status *status, enum line_type type)
4947 char buf[SIZEOF_STR];
4948 size_t bufsize = 0;
4950 switch (type) {
4951 case LINE_STAT_STAGED:
4952 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4953 status->old.mode,
4954 status->old.rev,
4955 status->old.name, 0))
4956 return FALSE;
4957 break;
4959 case LINE_STAT_UNSTAGED:
4960 case LINE_STAT_UNTRACKED:
4961 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4962 return FALSE;
4963 break;
4965 default:
4966 die("line type %d not handled in switch", type);
4969 return io_write(io, buf, bufsize);
4972 static bool
4973 status_update_file(struct status *status, enum line_type type)
4975 struct io io = {};
4976 bool result;
4978 if (!status_update_prepare(&io, type))
4979 return FALSE;
4981 result = status_update_write(&io, status, type);
4982 done_io(&io);
4983 return result;
4986 static bool
4987 status_update_files(struct view *view, struct line *line)
4989 struct io io = {};
4990 bool result = TRUE;
4991 struct line *pos = view->line + view->lines;
4992 int files = 0;
4993 int file, done;
4995 if (!status_update_prepare(&io, line->type))
4996 return FALSE;
4998 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4999 files++;
5001 for (file = 0, done = 0; result && file < files; line++, file++) {
5002 int almost_done = file * 100 / files;
5004 if (almost_done > done) {
5005 done = almost_done;
5006 string_format(view->ref, "updating file %u of %u (%d%% done)",
5007 file, files, done);
5008 update_view_title(view);
5010 result = status_update_write(&io, line->data, line->type);
5013 done_io(&io);
5014 return result;
5017 static bool
5018 status_update(struct view *view)
5020 struct line *line = &view->line[view->lineno];
5022 assert(view->lines);
5024 if (!line->data) {
5025 /* This should work even for the "On branch" line. */
5026 if (line < view->line + view->lines && !line[1].data) {
5027 report("Nothing to update");
5028 return FALSE;
5031 if (!status_update_files(view, line + 1)) {
5032 report("Failed to update file status");
5033 return FALSE;
5036 } else if (!status_update_file(line->data, line->type)) {
5037 report("Failed to update file status");
5038 return FALSE;
5041 return TRUE;
5044 static bool
5045 status_revert(struct status *status, enum line_type type, bool has_none)
5047 if (!status || type != LINE_STAT_UNSTAGED) {
5048 if (type == LINE_STAT_STAGED) {
5049 report("Cannot revert changes to staged files");
5050 } else if (type == LINE_STAT_UNTRACKED) {
5051 report("Cannot revert changes to untracked files");
5052 } else if (has_none) {
5053 report("Nothing to revert");
5054 } else {
5055 report("Cannot revert changes to multiple files");
5057 return FALSE;
5059 } else {
5060 const char *checkout_argv[] = {
5061 "git", "checkout", "--", status->old.name, NULL
5064 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5065 return FALSE;
5066 return run_io_fg(checkout_argv, opt_cdup);
5070 static enum request
5071 status_request(struct view *view, enum request request, struct line *line)
5073 struct status *status = line->data;
5075 switch (request) {
5076 case REQ_STATUS_UPDATE:
5077 if (!status_update(view))
5078 return REQ_NONE;
5079 break;
5081 case REQ_STATUS_REVERT:
5082 if (!status_revert(status, line->type, status_has_none(view, line)))
5083 return REQ_NONE;
5084 break;
5086 case REQ_STATUS_MERGE:
5087 if (!status || status->status != 'U') {
5088 report("Merging only possible for files with unmerged status ('U').");
5089 return REQ_NONE;
5091 open_mergetool(status->new.name);
5092 break;
5094 case REQ_EDIT:
5095 if (!status)
5096 return request;
5097 if (status->status == 'D') {
5098 report("File has been deleted.");
5099 return REQ_NONE;
5102 open_editor(status->status != '?', status->new.name);
5103 break;
5105 case REQ_VIEW_BLAME:
5106 if (status) {
5107 string_copy(opt_file, status->new.name);
5108 opt_ref[0] = 0;
5110 return request;
5112 case REQ_ENTER:
5113 /* After returning the status view has been split to
5114 * show the stage view. No further reloading is
5115 * necessary. */
5116 status_enter(view, line);
5117 return REQ_NONE;
5119 case REQ_REFRESH:
5120 /* Simply reload the view. */
5121 break;
5123 default:
5124 return request;
5127 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5129 return REQ_NONE;
5132 static void
5133 status_select(struct view *view, struct line *line)
5135 struct status *status = line->data;
5136 char file[SIZEOF_STR] = "all files";
5137 const char *text;
5138 const char *key;
5140 if (status && !string_format(file, "'%s'", status->new.name))
5141 return;
5143 if (!status && line[1].type == LINE_STAT_NONE)
5144 line++;
5146 switch (line->type) {
5147 case LINE_STAT_STAGED:
5148 text = "Press %s to unstage %s for commit";
5149 break;
5151 case LINE_STAT_UNSTAGED:
5152 text = "Press %s to stage %s for commit";
5153 break;
5155 case LINE_STAT_UNTRACKED:
5156 text = "Press %s to stage %s for addition";
5157 break;
5159 case LINE_STAT_HEAD:
5160 case LINE_STAT_NONE:
5161 text = "Nothing to update";
5162 break;
5164 default:
5165 die("line type %d not handled in switch", line->type);
5168 if (status && status->status == 'U') {
5169 text = "Press %s to resolve conflict in %s";
5170 key = get_key(REQ_STATUS_MERGE);
5172 } else {
5173 key = get_key(REQ_STATUS_UPDATE);
5176 string_format(view->ref, text, key, file);
5179 static bool
5180 status_grep(struct view *view, struct line *line)
5182 struct status *status = line->data;
5183 enum { S_STATUS, S_NAME, S_END } state;
5184 char buf[2] = "?";
5185 regmatch_t pmatch;
5187 if (!status)
5188 return FALSE;
5190 for (state = S_STATUS; state < S_END; state++) {
5191 const char *text;
5193 switch (state) {
5194 case S_NAME: text = status->new.name; break;
5195 case S_STATUS:
5196 buf[0] = status->status;
5197 text = buf;
5198 break;
5200 default:
5201 return FALSE;
5204 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5205 return TRUE;
5208 return FALSE;
5211 static struct view_ops status_ops = {
5212 "file",
5213 NULL,
5214 status_open,
5215 NULL,
5216 status_draw,
5217 status_request,
5218 status_grep,
5219 status_select,
5223 static bool
5224 stage_diff_write(struct io *io, struct line *line, struct line *end)
5226 while (line < end) {
5227 if (!io_write(io, line->data, strlen(line->data)) ||
5228 !io_write(io, "\n", 1))
5229 return FALSE;
5230 line++;
5231 if (line->type == LINE_DIFF_CHUNK ||
5232 line->type == LINE_DIFF_HEADER)
5233 break;
5236 return TRUE;
5239 static struct line *
5240 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5242 for (; view->line < line; line--)
5243 if (line->type == type)
5244 return line;
5246 return NULL;
5249 static bool
5250 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5252 const char *apply_argv[SIZEOF_ARG] = {
5253 "git", "apply", "--whitespace=nowarn", NULL
5255 struct line *diff_hdr;
5256 struct io io = {};
5257 int argc = 3;
5259 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5260 if (!diff_hdr)
5261 return FALSE;
5263 if (!revert)
5264 apply_argv[argc++] = "--cached";
5265 if (revert || stage_line_type == LINE_STAT_STAGED)
5266 apply_argv[argc++] = "-R";
5267 apply_argv[argc++] = "-";
5268 apply_argv[argc++] = NULL;
5269 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5270 return FALSE;
5272 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5273 !stage_diff_write(&io, chunk, view->line + view->lines))
5274 chunk = NULL;
5276 done_io(&io);
5277 run_io_bg(update_index_argv);
5279 return chunk ? TRUE : FALSE;
5282 static bool
5283 stage_update(struct view *view, struct line *line)
5285 struct line *chunk = NULL;
5287 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5288 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5290 if (chunk) {
5291 if (!stage_apply_chunk(view, chunk, FALSE)) {
5292 report("Failed to apply chunk");
5293 return FALSE;
5296 } else if (!stage_status.status) {
5297 view = VIEW(REQ_VIEW_STATUS);
5299 for (line = view->line; line < view->line + view->lines; line++)
5300 if (line->type == stage_line_type)
5301 break;
5303 if (!status_update_files(view, line + 1)) {
5304 report("Failed to update files");
5305 return FALSE;
5308 } else if (!status_update_file(&stage_status, stage_line_type)) {
5309 report("Failed to update file");
5310 return FALSE;
5313 return TRUE;
5316 static bool
5317 stage_revert(struct view *view, struct line *line)
5319 struct line *chunk = NULL;
5321 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5322 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5324 if (chunk) {
5325 if (!prompt_yesno("Are you sure you want to revert changes?"))
5326 return FALSE;
5328 if (!stage_apply_chunk(view, chunk, TRUE)) {
5329 report("Failed to revert chunk");
5330 return FALSE;
5332 return TRUE;
5334 } else {
5335 return status_revert(stage_status.status ? &stage_status : NULL,
5336 stage_line_type, FALSE);
5341 static void
5342 stage_next(struct view *view, struct line *line)
5344 int i;
5346 if (!stage_chunks) {
5347 static size_t alloc = 0;
5348 int *tmp;
5350 for (line = view->line; line < view->line + view->lines; line++) {
5351 if (line->type != LINE_DIFF_CHUNK)
5352 continue;
5354 tmp = realloc_items(stage_chunk, &alloc,
5355 stage_chunks, sizeof(*tmp));
5356 if (!tmp) {
5357 report("Allocation failure");
5358 return;
5361 stage_chunk = tmp;
5362 stage_chunk[stage_chunks++] = line - view->line;
5366 for (i = 0; i < stage_chunks; i++) {
5367 if (stage_chunk[i] > view->lineno) {
5368 do_scroll_view(view, stage_chunk[i] - view->lineno);
5369 report("Chunk %d of %d", i + 1, stage_chunks);
5370 return;
5374 report("No next chunk found");
5377 static enum request
5378 stage_request(struct view *view, enum request request, struct line *line)
5380 switch (request) {
5381 case REQ_STATUS_UPDATE:
5382 if (!stage_update(view, line))
5383 return REQ_NONE;
5384 break;
5386 case REQ_STATUS_REVERT:
5387 if (!stage_revert(view, line))
5388 return REQ_NONE;
5389 break;
5391 case REQ_STAGE_NEXT:
5392 if (stage_line_type == LINE_STAT_UNTRACKED) {
5393 report("File is untracked; press %s to add",
5394 get_key(REQ_STATUS_UPDATE));
5395 return REQ_NONE;
5397 stage_next(view, line);
5398 return REQ_NONE;
5400 case REQ_EDIT:
5401 if (!stage_status.new.name[0])
5402 return request;
5403 if (stage_status.status == 'D') {
5404 report("File has been deleted.");
5405 return REQ_NONE;
5408 open_editor(stage_status.status != '?', stage_status.new.name);
5409 break;
5411 case REQ_REFRESH:
5412 /* Reload everything ... */
5413 break;
5415 case REQ_VIEW_BLAME:
5416 if (stage_status.new.name[0]) {
5417 string_copy(opt_file, stage_status.new.name);
5418 opt_ref[0] = 0;
5420 return request;
5422 case REQ_ENTER:
5423 return pager_request(view, request, line);
5425 default:
5426 return request;
5429 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5430 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5432 /* Check whether the staged entry still exists, and close the
5433 * stage view if it doesn't. */
5434 if (!status_exists(&stage_status, stage_line_type)) {
5435 status_restore(VIEW(REQ_VIEW_STATUS));
5436 return REQ_VIEW_CLOSE;
5439 if (stage_line_type == LINE_STAT_UNTRACKED) {
5440 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5441 report("Cannot display a directory");
5442 return REQ_NONE;
5445 if (!prepare_update_file(view, stage_status.new.name)) {
5446 report("Failed to open file: %s", strerror(errno));
5447 return REQ_NONE;
5450 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5452 return REQ_NONE;
5455 static struct view_ops stage_ops = {
5456 "line",
5457 NULL,
5458 NULL,
5459 pager_read,
5460 pager_draw,
5461 stage_request,
5462 pager_grep,
5463 pager_select,
5468 * Revision graph
5471 struct commit {
5472 char id[SIZEOF_REV]; /* SHA1 ID. */
5473 char title[128]; /* First line of the commit message. */
5474 char author[75]; /* Author of the commit. */
5475 struct tm time; /* Date from the author ident. */
5476 struct ref **refs; /* Repository references. */
5477 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5478 size_t graph_size; /* The width of the graph array. */
5479 bool has_parents; /* Rewritten --parents seen. */
5482 /* Size of rev graph with no "padding" columns */
5483 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5485 struct rev_graph {
5486 struct rev_graph *prev, *next, *parents;
5487 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5488 size_t size;
5489 struct commit *commit;
5490 size_t pos;
5491 unsigned int boundary:1;
5494 /* Parents of the commit being visualized. */
5495 static struct rev_graph graph_parents[4];
5497 /* The current stack of revisions on the graph. */
5498 static struct rev_graph graph_stacks[4] = {
5499 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5500 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5501 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5502 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5505 static inline bool
5506 graph_parent_is_merge(struct rev_graph *graph)
5508 return graph->parents->size > 1;
5511 static inline void
5512 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5514 struct commit *commit = graph->commit;
5516 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5517 commit->graph[commit->graph_size++] = symbol;
5520 static void
5521 clear_rev_graph(struct rev_graph *graph)
5523 graph->boundary = 0;
5524 graph->size = graph->pos = 0;
5525 graph->commit = NULL;
5526 memset(graph->parents, 0, sizeof(*graph->parents));
5529 static void
5530 done_rev_graph(struct rev_graph *graph)
5532 if (graph_parent_is_merge(graph) &&
5533 graph->pos < graph->size - 1 &&
5534 graph->next->size == graph->size + graph->parents->size - 1) {
5535 size_t i = graph->pos + graph->parents->size - 1;
5537 graph->commit->graph_size = i * 2;
5538 while (i < graph->next->size - 1) {
5539 append_to_rev_graph(graph, ' ');
5540 append_to_rev_graph(graph, '\\');
5541 i++;
5545 clear_rev_graph(graph);
5548 static void
5549 push_rev_graph(struct rev_graph *graph, const char *parent)
5551 int i;
5553 /* "Collapse" duplicate parents lines.
5555 * FIXME: This needs to also update update the drawn graph but
5556 * for now it just serves as a method for pruning graph lines. */
5557 for (i = 0; i < graph->size; i++)
5558 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5559 return;
5561 if (graph->size < SIZEOF_REVITEMS) {
5562 string_copy_rev(graph->rev[graph->size++], parent);
5566 static chtype
5567 get_rev_graph_symbol(struct rev_graph *graph)
5569 chtype symbol;
5571 if (graph->boundary)
5572 symbol = REVGRAPH_BOUND;
5573 else if (graph->parents->size == 0)
5574 symbol = REVGRAPH_INIT;
5575 else if (graph_parent_is_merge(graph))
5576 symbol = REVGRAPH_MERGE;
5577 else if (graph->pos >= graph->size)
5578 symbol = REVGRAPH_BRANCH;
5579 else
5580 symbol = REVGRAPH_COMMIT;
5582 return symbol;
5585 static void
5586 draw_rev_graph(struct rev_graph *graph)
5588 struct rev_filler {
5589 chtype separator, line;
5591 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5592 static struct rev_filler fillers[] = {
5593 { ' ', '|' },
5594 { '`', '.' },
5595 { '\'', ' ' },
5596 { '/', ' ' },
5598 chtype symbol = get_rev_graph_symbol(graph);
5599 struct rev_filler *filler;
5600 size_t i;
5602 if (opt_line_graphics)
5603 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5605 filler = &fillers[DEFAULT];
5607 for (i = 0; i < graph->pos; i++) {
5608 append_to_rev_graph(graph, filler->line);
5609 if (graph_parent_is_merge(graph->prev) &&
5610 graph->prev->pos == i)
5611 filler = &fillers[RSHARP];
5613 append_to_rev_graph(graph, filler->separator);
5616 /* Place the symbol for this revision. */
5617 append_to_rev_graph(graph, symbol);
5619 if (graph->prev->size > graph->size)
5620 filler = &fillers[RDIAG];
5621 else
5622 filler = &fillers[DEFAULT];
5624 i++;
5626 for (; i < graph->size; i++) {
5627 append_to_rev_graph(graph, filler->separator);
5628 append_to_rev_graph(graph, filler->line);
5629 if (graph_parent_is_merge(graph->prev) &&
5630 i < graph->prev->pos + graph->parents->size)
5631 filler = &fillers[RSHARP];
5632 if (graph->prev->size > graph->size)
5633 filler = &fillers[LDIAG];
5636 if (graph->prev->size > graph->size) {
5637 append_to_rev_graph(graph, filler->separator);
5638 if (filler->line != ' ')
5639 append_to_rev_graph(graph, filler->line);
5643 /* Prepare the next rev graph */
5644 static void
5645 prepare_rev_graph(struct rev_graph *graph)
5647 size_t i;
5649 /* First, traverse all lines of revisions up to the active one. */
5650 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5651 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5652 break;
5654 push_rev_graph(graph->next, graph->rev[graph->pos]);
5657 /* Interleave the new revision parent(s). */
5658 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5659 push_rev_graph(graph->next, graph->parents->rev[i]);
5661 /* Lastly, put any remaining revisions. */
5662 for (i = graph->pos + 1; i < graph->size; i++)
5663 push_rev_graph(graph->next, graph->rev[i]);
5666 static void
5667 update_rev_graph(struct view *view, struct rev_graph *graph)
5669 /* If this is the finalizing update ... */
5670 if (graph->commit)
5671 prepare_rev_graph(graph);
5673 /* Graph visualization needs a one rev look-ahead,
5674 * so the first update doesn't visualize anything. */
5675 if (!graph->prev->commit)
5676 return;
5678 if (view->lines > 2)
5679 view->line[view->lines - 3].dirty = 1;
5680 if (view->lines > 1)
5681 view->line[view->lines - 2].dirty = 1;
5682 draw_rev_graph(graph->prev);
5683 done_rev_graph(graph->prev->prev);
5688 * Main view backend
5691 static const char *main_argv[SIZEOF_ARG] = {
5692 "git", "log", "--no-color", "--pretty=raw", "--parents",
5693 "--topo-order", "%(head)", NULL
5696 static bool
5697 main_draw(struct view *view, struct line *line, unsigned int lineno)
5699 struct commit *commit = line->data;
5701 if (!*commit->author)
5702 return FALSE;
5704 if (opt_date && draw_date(view, &commit->time))
5705 return TRUE;
5707 if (opt_author && draw_author(view, commit->author))
5708 return TRUE;
5710 if (opt_rev_graph && commit->graph_size &&
5711 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5712 return TRUE;
5714 if (opt_show_refs && commit->refs) {
5715 size_t i = 0;
5717 do {
5718 enum line_type type;
5720 if (commit->refs[i]->head)
5721 type = LINE_MAIN_HEAD;
5722 else if (commit->refs[i]->ltag)
5723 type = LINE_MAIN_LOCAL_TAG;
5724 else if (commit->refs[i]->tag)
5725 type = LINE_MAIN_TAG;
5726 else if (commit->refs[i]->tracked)
5727 type = LINE_MAIN_TRACKED;
5728 else if (commit->refs[i]->remote)
5729 type = LINE_MAIN_REMOTE;
5730 else
5731 type = LINE_MAIN_REF;
5733 if (draw_text(view, type, "[", TRUE) ||
5734 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5735 draw_text(view, type, "]", TRUE))
5736 return TRUE;
5738 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5739 return TRUE;
5740 } while (commit->refs[i++]->next);
5743 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5744 return TRUE;
5747 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5748 static bool
5749 main_read(struct view *view, char *line)
5751 static struct rev_graph *graph = graph_stacks;
5752 enum line_type type;
5753 struct commit *commit;
5755 if (!line) {
5756 int i;
5758 if (!view->lines && !view->parent)
5759 die("No revisions match the given arguments.");
5760 if (view->lines > 0) {
5761 commit = view->line[view->lines - 1].data;
5762 view->line[view->lines - 1].dirty = 1;
5763 if (!*commit->author) {
5764 view->lines--;
5765 free(commit);
5766 graph->commit = NULL;
5769 update_rev_graph(view, graph);
5771 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5772 clear_rev_graph(&graph_stacks[i]);
5773 return TRUE;
5776 type = get_line_type(line);
5777 if (type == LINE_COMMIT) {
5778 commit = calloc(1, sizeof(struct commit));
5779 if (!commit)
5780 return FALSE;
5782 line += STRING_SIZE("commit ");
5783 if (*line == '-') {
5784 graph->boundary = 1;
5785 line++;
5788 string_copy_rev(commit->id, line);
5789 commit->refs = get_refs(commit->id);
5790 graph->commit = commit;
5791 add_line_data(view, commit, LINE_MAIN_COMMIT);
5793 while ((line = strchr(line, ' '))) {
5794 line++;
5795 push_rev_graph(graph->parents, line);
5796 commit->has_parents = TRUE;
5798 return TRUE;
5801 if (!view->lines)
5802 return TRUE;
5803 commit = view->line[view->lines - 1].data;
5805 switch (type) {
5806 case LINE_PARENT:
5807 if (commit->has_parents)
5808 break;
5809 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5810 break;
5812 case LINE_AUTHOR:
5813 parse_author_line(line + STRING_SIZE("author "),
5814 commit->author, sizeof(commit->author),
5815 &commit->time);
5816 update_rev_graph(view, graph);
5817 graph = graph->next;
5818 break;
5820 default:
5821 /* Fill in the commit title if it has not already been set. */
5822 if (commit->title[0])
5823 break;
5825 /* Require titles to start with a non-space character at the
5826 * offset used by git log. */
5827 if (strncmp(line, " ", 4))
5828 break;
5829 line += 4;
5830 /* Well, if the title starts with a whitespace character,
5831 * try to be forgiving. Otherwise we end up with no title. */
5832 while (isspace(*line))
5833 line++;
5834 if (*line == '\0')
5835 break;
5836 /* FIXME: More graceful handling of titles; append "..." to
5837 * shortened titles, etc. */
5839 string_ncopy(commit->title, line, strlen(line));
5840 view->line[view->lines - 1].dirty = 1;
5843 return TRUE;
5846 static enum request
5847 main_request(struct view *view, enum request request, struct line *line)
5849 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5851 switch (request) {
5852 case REQ_ENTER:
5853 open_view(view, REQ_VIEW_DIFF, flags);
5854 break;
5855 case REQ_REFRESH:
5856 load_refs();
5857 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5858 break;
5859 default:
5860 return request;
5863 return REQ_NONE;
5866 static bool
5867 grep_refs(struct ref **refs, regex_t *regex)
5869 regmatch_t pmatch;
5870 size_t i = 0;
5872 if (!refs)
5873 return FALSE;
5874 do {
5875 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5876 return TRUE;
5877 } while (refs[i++]->next);
5879 return FALSE;
5882 static bool
5883 main_grep(struct view *view, struct line *line)
5885 struct commit *commit = line->data;
5886 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5887 char buf[DATE_COLS + 1];
5888 regmatch_t pmatch;
5890 for (state = S_TITLE; state < S_END; state++) {
5891 char *text;
5893 switch (state) {
5894 case S_TITLE: text = commit->title; break;
5895 case S_AUTHOR:
5896 if (!opt_author)
5897 continue;
5898 text = commit->author;
5899 break;
5900 case S_DATE:
5901 if (!opt_date)
5902 continue;
5903 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5904 continue;
5905 text = buf;
5906 break;
5907 case S_REFS:
5908 if (!opt_show_refs)
5909 continue;
5910 if (grep_refs(commit->refs, view->regex) == TRUE)
5911 return TRUE;
5912 continue;
5913 default:
5914 return FALSE;
5917 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5918 return TRUE;
5921 return FALSE;
5924 static void
5925 main_select(struct view *view, struct line *line)
5927 struct commit *commit = line->data;
5929 string_copy_rev(view->ref, commit->id);
5930 string_copy_rev(ref_commit, view->ref);
5933 static struct view_ops main_ops = {
5934 "commit",
5935 main_argv,
5936 NULL,
5937 main_read,
5938 main_draw,
5939 main_request,
5940 main_grep,
5941 main_select,
5946 * Unicode / UTF-8 handling
5948 * NOTE: Much of the following code for dealing with unicode is derived from
5949 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5950 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5953 /* I've (over)annotated a lot of code snippets because I am not entirely
5954 * confident that the approach taken by this small UTF-8 interface is correct.
5955 * --jonas */
5957 static inline int
5958 unicode_width(unsigned long c)
5960 if (c >= 0x1100 &&
5961 (c <= 0x115f /* Hangul Jamo */
5962 || c == 0x2329
5963 || c == 0x232a
5964 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5965 /* CJK ... Yi */
5966 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5967 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5968 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5969 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5970 || (c >= 0xffe0 && c <= 0xffe6)
5971 || (c >= 0x20000 && c <= 0x2fffd)
5972 || (c >= 0x30000 && c <= 0x3fffd)))
5973 return 2;
5975 if (c == '\t')
5976 return opt_tab_size;
5978 return 1;
5981 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5982 * Illegal bytes are set one. */
5983 static const unsigned char utf8_bytes[256] = {
5984 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,
5985 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,
5986 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,
5987 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,
5988 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,
5989 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,
5990 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,
5991 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,
5994 /* Decode UTF-8 multi-byte representation into a unicode character. */
5995 static inline unsigned long
5996 utf8_to_unicode(const char *string, size_t length)
5998 unsigned long unicode;
6000 switch (length) {
6001 case 1:
6002 unicode = string[0];
6003 break;
6004 case 2:
6005 unicode = (string[0] & 0x1f) << 6;
6006 unicode += (string[1] & 0x3f);
6007 break;
6008 case 3:
6009 unicode = (string[0] & 0x0f) << 12;
6010 unicode += ((string[1] & 0x3f) << 6);
6011 unicode += (string[2] & 0x3f);
6012 break;
6013 case 4:
6014 unicode = (string[0] & 0x0f) << 18;
6015 unicode += ((string[1] & 0x3f) << 12);
6016 unicode += ((string[2] & 0x3f) << 6);
6017 unicode += (string[3] & 0x3f);
6018 break;
6019 case 5:
6020 unicode = (string[0] & 0x0f) << 24;
6021 unicode += ((string[1] & 0x3f) << 18);
6022 unicode += ((string[2] & 0x3f) << 12);
6023 unicode += ((string[3] & 0x3f) << 6);
6024 unicode += (string[4] & 0x3f);
6025 break;
6026 case 6:
6027 unicode = (string[0] & 0x01) << 30;
6028 unicode += ((string[1] & 0x3f) << 24);
6029 unicode += ((string[2] & 0x3f) << 18);
6030 unicode += ((string[3] & 0x3f) << 12);
6031 unicode += ((string[4] & 0x3f) << 6);
6032 unicode += (string[5] & 0x3f);
6033 break;
6034 default:
6035 die("Invalid unicode length");
6038 /* Invalid characters could return the special 0xfffd value but NUL
6039 * should be just as good. */
6040 return unicode > 0xffff ? 0 : unicode;
6043 /* Calculates how much of string can be shown within the given maximum width
6044 * and sets trimmed parameter to non-zero value if all of string could not be
6045 * shown. If the reserve flag is TRUE, it will reserve at least one
6046 * trailing character, which can be useful when drawing a delimiter.
6048 * Returns the number of bytes to output from string to satisfy max_width. */
6049 static size_t
6050 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6052 const char *start = string;
6053 const char *end = strchr(string, '\0');
6054 unsigned char last_bytes = 0;
6055 size_t last_ucwidth = 0;
6057 *width = 0;
6058 *trimmed = 0;
6060 while (string < end) {
6061 int c = *(unsigned char *) string;
6062 unsigned char bytes = utf8_bytes[c];
6063 size_t ucwidth;
6064 unsigned long unicode;
6066 if (string + bytes > end)
6067 break;
6069 /* Change representation to figure out whether
6070 * it is a single- or double-width character. */
6072 unicode = utf8_to_unicode(string, bytes);
6073 /* FIXME: Graceful handling of invalid unicode character. */
6074 if (!unicode)
6075 break;
6077 ucwidth = unicode_width(unicode);
6078 *width += ucwidth;
6079 if (*width > max_width) {
6080 *trimmed = 1;
6081 *width -= ucwidth;
6082 if (reserve && *width == max_width) {
6083 string -= last_bytes;
6084 *width -= last_ucwidth;
6086 break;
6089 string += bytes;
6090 last_bytes = bytes;
6091 last_ucwidth = ucwidth;
6094 return string - start;
6099 * Status management
6102 /* Whether or not the curses interface has been initialized. */
6103 static bool cursed = FALSE;
6105 /* Terminal hacks and workarounds. */
6106 static bool use_scroll_redrawwin;
6107 static bool use_scroll_status_wclear;
6109 /* The status window is used for polling keystrokes. */
6110 static WINDOW *status_win;
6112 /* Reading from the prompt? */
6113 static bool input_mode = FALSE;
6115 static bool status_empty = FALSE;
6117 /* Update status and title window. */
6118 static void
6119 report(const char *msg, ...)
6121 struct view *view = display[current_view];
6123 if (input_mode)
6124 return;
6126 if (!view) {
6127 char buf[SIZEOF_STR];
6128 va_list args;
6130 va_start(args, msg);
6131 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6132 buf[sizeof(buf) - 1] = 0;
6133 buf[sizeof(buf) - 2] = '.';
6134 buf[sizeof(buf) - 3] = '.';
6135 buf[sizeof(buf) - 4] = '.';
6137 va_end(args);
6138 die("%s", buf);
6141 if (!status_empty || *msg) {
6142 va_list args;
6144 va_start(args, msg);
6146 wmove(status_win, 0, 0);
6147 if (view->has_scrolled && use_scroll_status_wclear)
6148 wclear(status_win);
6149 if (*msg) {
6150 vwprintw(status_win, msg, args);
6151 status_empty = FALSE;
6152 } else {
6153 status_empty = TRUE;
6155 wclrtoeol(status_win);
6156 wnoutrefresh(status_win);
6158 va_end(args);
6161 update_view_title(view);
6164 /* Controls when nodelay should be in effect when polling user input. */
6165 static void
6166 set_nonblocking_input(bool loading)
6168 static unsigned int loading_views;
6170 if ((loading == FALSE && loading_views-- == 1) ||
6171 (loading == TRUE && loading_views++ == 0))
6172 nodelay(status_win, loading);
6175 static void
6176 init_display(void)
6178 const char *term;
6179 int x, y;
6181 /* Initialize the curses library */
6182 if (isatty(STDIN_FILENO)) {
6183 cursed = !!initscr();
6184 opt_tty = stdin;
6185 } else {
6186 /* Leave stdin and stdout alone when acting as a pager. */
6187 opt_tty = fopen("/dev/tty", "r+");
6188 if (!opt_tty)
6189 die("Failed to open /dev/tty");
6190 cursed = !!newterm(NULL, opt_tty, opt_tty);
6193 if (!cursed)
6194 die("Failed to initialize curses");
6196 nonl(); /* Tell curses not to do NL->CR/NL on output */
6197 cbreak(); /* Take input chars one at a time, no wait for \n */
6198 noecho(); /* Don't echo input */
6199 leaveok(stdscr, FALSE);
6201 if (has_colors())
6202 init_colors();
6204 getmaxyx(stdscr, y, x);
6205 status_win = newwin(1, 0, y - 1, 0);
6206 if (!status_win)
6207 die("Failed to create status window");
6209 /* Enable keyboard mapping */
6210 keypad(status_win, TRUE);
6211 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6213 TABSIZE = opt_tab_size;
6214 if (opt_line_graphics) {
6215 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6218 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6219 if (term && !strcmp(term, "gnome-terminal")) {
6220 /* In the gnome-terminal-emulator, the message from
6221 * scrolling up one line when impossible followed by
6222 * scrolling down one line causes corruption of the
6223 * status line. This is fixed by calling wclear. */
6224 use_scroll_status_wclear = TRUE;
6225 use_scroll_redrawwin = FALSE;
6227 } else if (term && !strcmp(term, "xrvt-xpm")) {
6228 /* No problems with full optimizations in xrvt-(unicode)
6229 * and aterm. */
6230 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6232 } else {
6233 /* When scrolling in (u)xterm the last line in the
6234 * scrolling direction will update slowly. */
6235 use_scroll_redrawwin = TRUE;
6236 use_scroll_status_wclear = FALSE;
6240 static int
6241 get_input(int prompt_position)
6243 struct view *view;
6244 int i, key, cursor_y, cursor_x;
6246 if (prompt_position)
6247 input_mode = TRUE;
6249 while (TRUE) {
6250 foreach_view (view, i) {
6251 update_view(view);
6252 if (view_is_displayed(view) && view->has_scrolled &&
6253 use_scroll_redrawwin)
6254 redrawwin(view->win);
6255 view->has_scrolled = FALSE;
6258 /* Update the cursor position. */
6259 if (prompt_position) {
6260 getbegyx(status_win, cursor_y, cursor_x);
6261 cursor_x = prompt_position;
6262 } else {
6263 view = display[current_view];
6264 getbegyx(view->win, cursor_y, cursor_x);
6265 cursor_x = view->width - 1;
6266 cursor_y += view->lineno - view->offset;
6268 setsyx(cursor_y, cursor_x);
6270 /* Refresh, accept single keystroke of input */
6271 doupdate();
6272 key = wgetch(status_win);
6274 /* wgetch() with nodelay() enabled returns ERR when
6275 * there's no input. */
6276 if (key == ERR) {
6278 } else if (key == KEY_RESIZE) {
6279 int height, width;
6281 getmaxyx(stdscr, height, width);
6283 wresize(status_win, 1, width);
6284 mvwin(status_win, height - 1, 0);
6285 wnoutrefresh(status_win);
6286 resize_display();
6287 redraw_display(TRUE);
6289 } else {
6290 input_mode = FALSE;
6291 return key;
6296 static char *
6297 prompt_input(const char *prompt, input_handler handler, void *data)
6299 enum input_status status = INPUT_OK;
6300 static char buf[SIZEOF_STR];
6301 size_t pos = 0;
6303 buf[pos] = 0;
6305 while (status == INPUT_OK || status == INPUT_SKIP) {
6306 int key;
6308 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6309 wclrtoeol(status_win);
6311 key = get_input(pos + 1);
6312 switch (key) {
6313 case KEY_RETURN:
6314 case KEY_ENTER:
6315 case '\n':
6316 status = pos ? INPUT_STOP : INPUT_CANCEL;
6317 break;
6319 case KEY_BACKSPACE:
6320 if (pos > 0)
6321 buf[--pos] = 0;
6322 else
6323 status = INPUT_CANCEL;
6324 break;
6326 case KEY_ESC:
6327 status = INPUT_CANCEL;
6328 break;
6330 default:
6331 if (pos >= sizeof(buf)) {
6332 report("Input string too long");
6333 return NULL;
6336 status = handler(data, buf, key);
6337 if (status == INPUT_OK)
6338 buf[pos++] = (char) key;
6342 /* Clear the status window */
6343 status_empty = FALSE;
6344 report("");
6346 if (status == INPUT_CANCEL)
6347 return NULL;
6349 buf[pos++] = 0;
6351 return buf;
6354 static enum input_status
6355 prompt_yesno_handler(void *data, char *buf, int c)
6357 if (c == 'y' || c == 'Y')
6358 return INPUT_STOP;
6359 if (c == 'n' || c == 'N')
6360 return INPUT_CANCEL;
6361 return INPUT_SKIP;
6364 static bool
6365 prompt_yesno(const char *prompt)
6367 char prompt2[SIZEOF_STR];
6369 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6370 return FALSE;
6372 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6375 static enum input_status
6376 read_prompt_handler(void *data, char *buf, int c)
6378 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6381 static char *
6382 read_prompt(const char *prompt)
6384 return prompt_input(prompt, read_prompt_handler, NULL);
6388 * Repository properties
6391 static int
6392 git_properties(const char **argv, const char *separators,
6393 int (*read_property)(char *, size_t, char *, size_t))
6395 struct io io = {};
6397 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6398 return read_properties(&io, separators, read_property);
6399 return ERR;
6402 static struct ref *refs = NULL;
6403 static size_t refs_alloc = 0;
6404 static size_t refs_size = 0;
6406 /* Id <-> ref store */
6407 static struct ref ***id_refs = NULL;
6408 static size_t id_refs_alloc = 0;
6409 static size_t id_refs_size = 0;
6411 static int
6412 compare_refs(const void *ref1_, const void *ref2_)
6414 const struct ref *ref1 = *(const struct ref **)ref1_;
6415 const struct ref *ref2 = *(const struct ref **)ref2_;
6417 if (ref1->tag != ref2->tag)
6418 return ref2->tag - ref1->tag;
6419 if (ref1->ltag != ref2->ltag)
6420 return ref2->ltag - ref2->ltag;
6421 if (ref1->head != ref2->head)
6422 return ref2->head - ref1->head;
6423 if (ref1->tracked != ref2->tracked)
6424 return ref2->tracked - ref1->tracked;
6425 if (ref1->remote != ref2->remote)
6426 return ref2->remote - ref1->remote;
6427 return strcmp(ref1->name, ref2->name);
6430 static struct ref **
6431 get_refs(const char *id)
6433 struct ref ***tmp_id_refs;
6434 struct ref **ref_list = NULL;
6435 size_t ref_list_alloc = 0;
6436 size_t ref_list_size = 0;
6437 size_t i;
6439 for (i = 0; i < id_refs_size; i++)
6440 if (!strcmp(id, id_refs[i][0]->id))
6441 return id_refs[i];
6443 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6444 sizeof(*id_refs));
6445 if (!tmp_id_refs)
6446 return NULL;
6448 id_refs = tmp_id_refs;
6450 for (i = 0; i < refs_size; i++) {
6451 struct ref **tmp;
6453 if (strcmp(id, refs[i].id))
6454 continue;
6456 tmp = realloc_items(ref_list, &ref_list_alloc,
6457 ref_list_size + 1, sizeof(*ref_list));
6458 if (!tmp) {
6459 if (ref_list)
6460 free(ref_list);
6461 return NULL;
6464 ref_list = tmp;
6465 ref_list[ref_list_size] = &refs[i];
6466 /* XXX: The properties of the commit chains ensures that we can
6467 * safely modify the shared ref. The repo references will
6468 * always be similar for the same id. */
6469 ref_list[ref_list_size]->next = 1;
6471 ref_list_size++;
6474 if (ref_list) {
6475 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6476 ref_list[ref_list_size - 1]->next = 0;
6477 id_refs[id_refs_size++] = ref_list;
6480 return ref_list;
6483 static int
6484 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6486 struct ref *ref;
6487 bool tag = FALSE;
6488 bool ltag = FALSE;
6489 bool remote = FALSE;
6490 bool tracked = FALSE;
6491 bool check_replace = FALSE;
6492 bool head = FALSE;
6494 if (!prefixcmp(name, "refs/tags/")) {
6495 if (!suffixcmp(name, namelen, "^{}")) {
6496 namelen -= 3;
6497 name[namelen] = 0;
6498 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6499 check_replace = TRUE;
6500 } else {
6501 ltag = TRUE;
6504 tag = TRUE;
6505 namelen -= STRING_SIZE("refs/tags/");
6506 name += STRING_SIZE("refs/tags/");
6508 } else if (!prefixcmp(name, "refs/remotes/")) {
6509 remote = TRUE;
6510 namelen -= STRING_SIZE("refs/remotes/");
6511 name += STRING_SIZE("refs/remotes/");
6512 tracked = !strcmp(opt_remote, name);
6514 } else if (!prefixcmp(name, "refs/heads/")) {
6515 namelen -= STRING_SIZE("refs/heads/");
6516 name += STRING_SIZE("refs/heads/");
6517 head = !strncmp(opt_head, name, namelen);
6519 } else if (!strcmp(name, "HEAD")) {
6520 string_ncopy(opt_head_rev, id, idlen);
6521 return OK;
6524 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6525 /* it's an annotated tag, replace the previous sha1 with the
6526 * resolved commit id; relies on the fact git-ls-remote lists
6527 * the commit id of an annotated tag right before the commit id
6528 * it points to. */
6529 refs[refs_size - 1].ltag = ltag;
6530 string_copy_rev(refs[refs_size - 1].id, id);
6532 return OK;
6534 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6535 if (!refs)
6536 return ERR;
6538 ref = &refs[refs_size++];
6539 ref->name = malloc(namelen + 1);
6540 if (!ref->name)
6541 return ERR;
6543 strncpy(ref->name, name, namelen);
6544 ref->name[namelen] = 0;
6545 ref->head = head;
6546 ref->tag = tag;
6547 ref->ltag = ltag;
6548 ref->remote = remote;
6549 ref->tracked = tracked;
6550 string_copy_rev(ref->id, id);
6552 return OK;
6555 static int
6556 load_refs(void)
6558 static const char *ls_remote_argv[SIZEOF_ARG] = {
6559 "git", "ls-remote", ".", NULL
6561 static bool init = FALSE;
6563 if (!init) {
6564 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6565 init = TRUE;
6568 if (!*opt_git_dir)
6569 return OK;
6571 while (refs_size > 0)
6572 free(refs[--refs_size].name);
6573 while (id_refs_size > 0)
6574 free(id_refs[--id_refs_size]);
6576 return git_properties(ls_remote_argv, "\t", read_ref);
6579 static int
6580 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6582 if (!strcmp(name, "i18n.commitencoding"))
6583 string_ncopy(opt_encoding, value, valuelen);
6585 if (!strcmp(name, "core.editor"))
6586 string_ncopy(opt_editor, value, valuelen);
6588 /* branch.<head>.remote */
6589 if (*opt_head &&
6590 !strncmp(name, "branch.", 7) &&
6591 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6592 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6593 string_ncopy(opt_remote, value, valuelen);
6595 if (*opt_head && *opt_remote &&
6596 !strncmp(name, "branch.", 7) &&
6597 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6598 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6599 size_t from = strlen(opt_remote);
6601 if (!prefixcmp(value, "refs/heads/")) {
6602 value += STRING_SIZE("refs/heads/");
6603 valuelen -= STRING_SIZE("refs/heads/");
6606 if (!string_format_from(opt_remote, &from, "/%s", value))
6607 opt_remote[0] = 0;
6610 return OK;
6613 static int
6614 load_git_config(void)
6616 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6618 return git_properties(config_list_argv, "=", read_repo_config_option);
6621 static int
6622 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6624 if (!opt_git_dir[0]) {
6625 string_ncopy(opt_git_dir, name, namelen);
6627 } else if (opt_is_inside_work_tree == -1) {
6628 /* This can be 3 different values depending on the
6629 * version of git being used. If git-rev-parse does not
6630 * understand --is-inside-work-tree it will simply echo
6631 * the option else either "true" or "false" is printed.
6632 * Default to true for the unknown case. */
6633 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6635 } else if (*name == '.') {
6636 string_ncopy(opt_cdup, name, namelen);
6638 } else {
6639 string_ncopy(opt_prefix, name, namelen);
6642 return OK;
6645 static int
6646 load_repo_info(void)
6648 const char *head_argv[] = {
6649 "git", "symbolic-ref", "HEAD", NULL
6651 const char *rev_parse_argv[] = {
6652 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6653 "--show-cdup", "--show-prefix", NULL
6656 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6657 chomp_string(opt_head);
6658 if (!prefixcmp(opt_head, "refs/heads/")) {
6659 char *offset = opt_head + STRING_SIZE("refs/heads/");
6661 memmove(opt_head, offset, strlen(offset) + 1);
6665 return git_properties(rev_parse_argv, "=", read_repo_info);
6668 static int
6669 read_properties(struct io *io, const char *separators,
6670 int (*read_property)(char *, size_t, char *, size_t))
6672 char *name;
6673 int state = OK;
6675 if (!start_io(io))
6676 return ERR;
6678 while (state == OK && (name = io_get(io, '\n', TRUE))) {
6679 char *value;
6680 size_t namelen;
6681 size_t valuelen;
6683 name = chomp_string(name);
6684 namelen = strcspn(name, separators);
6686 if (name[namelen]) {
6687 name[namelen] = 0;
6688 value = chomp_string(name + namelen + 1);
6689 valuelen = strlen(value);
6691 } else {
6692 value = "";
6693 valuelen = 0;
6696 state = read_property(name, namelen, value, valuelen);
6699 if (state != ERR && io_error(io))
6700 state = ERR;
6701 done_io(io);
6703 return state;
6708 * Main
6711 static void __NORETURN
6712 quit(int sig)
6714 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6715 if (cursed)
6716 endwin();
6717 exit(0);
6720 static void __NORETURN
6721 die(const char *err, ...)
6723 va_list args;
6725 endwin();
6727 va_start(args, err);
6728 fputs("tig: ", stderr);
6729 vfprintf(stderr, err, args);
6730 fputs("\n", stderr);
6731 va_end(args);
6733 exit(1);
6736 static void
6737 warn(const char *msg, ...)
6739 va_list args;
6741 va_start(args, msg);
6742 fputs("tig warning: ", stderr);
6743 vfprintf(stderr, msg, args);
6744 fputs("\n", stderr);
6745 va_end(args);
6749 main(int argc, const char *argv[])
6751 const char **run_argv = NULL;
6752 struct view *view;
6753 enum request request;
6754 size_t i;
6756 signal(SIGINT, quit);
6758 if (setlocale(LC_ALL, "")) {
6759 char *codeset = nl_langinfo(CODESET);
6761 string_ncopy(opt_codeset, codeset, strlen(codeset));
6764 if (load_repo_info() == ERR)
6765 die("Failed to load repo info.");
6767 if (load_options() == ERR)
6768 die("Failed to load user config.");
6770 if (load_git_config() == ERR)
6771 die("Failed to load repo config.");
6773 request = parse_options(argc, argv, &run_argv);
6774 if (request == REQ_NONE)
6775 return 0;
6777 /* Require a git repository unless when running in pager mode. */
6778 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6779 die("Not a git repository");
6781 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6782 opt_utf8 = FALSE;
6784 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6785 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6786 if (opt_iconv == ICONV_NONE)
6787 die("Failed to initialize character set conversion");
6790 if (load_refs() == ERR)
6791 die("Failed to load refs.");
6793 foreach_view (view, i)
6794 argv_from_env(view->ops->argv, view->cmd_env);
6796 init_display();
6798 if (request == REQ_VIEW_PAGER || run_argv) {
6799 if (request == REQ_VIEW_PAGER)
6800 io_open(&VIEW(request)->io, "");
6801 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6802 die("Failed to format arguments");
6803 open_view(NULL, request, OPEN_PREPARED);
6804 request = REQ_NONE;
6807 while (view_driver(display[current_view], request)) {
6808 int key = get_input(0);
6810 view = display[current_view];
6811 request = get_keybinding(view->keymap, key);
6813 /* Some low-level request handling. This keeps access to
6814 * status_win restricted. */
6815 switch (request) {
6816 case REQ_PROMPT:
6818 char *cmd = read_prompt(":");
6820 if (cmd) {
6821 struct view *next = VIEW(REQ_VIEW_PAGER);
6822 const char *argv[SIZEOF_ARG] = { "git" };
6823 int argc = 1;
6825 /* When running random commands, initially show the
6826 * command in the title. However, it maybe later be
6827 * overwritten if a commit line is selected. */
6828 string_ncopy(next->ref, cmd, strlen(cmd));
6830 if (!argv_from_string(argv, &argc, cmd)) {
6831 report("Too many arguments");
6832 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6833 report("Failed to format command");
6834 } else {
6835 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6839 request = REQ_NONE;
6840 break;
6842 case REQ_SEARCH:
6843 case REQ_SEARCH_BACK:
6845 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6846 char *search = read_prompt(prompt);
6848 if (search)
6849 string_ncopy(opt_search, search, strlen(search));
6850 else
6851 request = REQ_NONE;
6852 break;
6854 default:
6855 break;
6859 quit(0);
6861 return 0;