Fix handling of quoted strings in the config file
[tig.git] / tig.c
blob2b2607bdc9828f6ba4a13db944d78c50e50214c0
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 int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
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
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE 8
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
120 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
122 #ifndef GIT_CONFIG
123 #define GIT_CONFIG "config"
124 #endif
126 /* Some ASCII-shorthands fitted into the ncurses namespace. */
127 #define KEY_TAB '\t'
128 #define KEY_RETURN '\r'
129 #define KEY_ESC 27
132 struct ref {
133 char *name; /* Ref name; tag or head names are shortened. */
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 unsigned int head:1; /* Is it the current HEAD? */
136 unsigned int tag:1; /* Is it a tag? */
137 unsigned int ltag:1; /* If so, is the tag local? */
138 unsigned int remote:1; /* Is it a remote ref? */
139 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
140 unsigned int next:1; /* For ref lists: are there more refs? */
143 static struct ref **get_refs(const char *id);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
166 * String helpers
169 static inline void
170 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
172 if (srclen > dstlen - 1)
173 srclen = dstlen - 1;
175 strncpy(dst, src, srclen);
176 dst[srclen] = 0;
179 /* Shorthands for safely copying into a fixed buffer. */
181 #define string_copy(dst, src) \
182 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
184 #define string_ncopy(dst, src, srclen) \
185 string_ncopy_do(dst, sizeof(dst), src, srclen)
187 #define string_copy_rev(dst, src) \
188 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
190 #define string_add(dst, from, src) \
191 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
193 static void
194 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
196 size_t size, pos;
198 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
199 if (src[pos] == '\t') {
200 size_t expanded = tabsize - (size % tabsize);
202 if (expanded + size >= dstlen - 1)
203 expanded = dstlen - size - 1;
204 memcpy(dst + size, " ", expanded);
205 size += expanded;
206 } else {
207 dst[size++] = src[pos];
211 dst[size] = 0;
214 static char *
215 chomp_string(char *name)
217 int namelen;
219 while (isspace(*name))
220 name++;
222 namelen = strlen(name) - 1;
223 while (namelen > 0 && isspace(name[namelen]))
224 name[namelen--] = 0;
226 return name;
229 static bool
230 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
232 va_list args;
233 size_t pos = bufpos ? *bufpos : 0;
235 va_start(args, fmt);
236 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
237 va_end(args);
239 if (bufpos)
240 *bufpos = pos;
242 return pos >= bufsize ? FALSE : TRUE;
245 #define string_format(buf, fmt, args...) \
246 string_nformat(buf, sizeof(buf), NULL, fmt, args)
248 #define string_format_from(buf, from, fmt, args...) \
249 string_nformat(buf, sizeof(buf), from, fmt, args)
251 static int
252 string_enum_compare(const char *str1, const char *str2, int len)
254 size_t i;
256 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
258 /* Diff-Header == DIFF_HEADER */
259 for (i = 0; i < len; i++) {
260 if (toupper(str1[i]) == toupper(str2[i]))
261 continue;
263 if (string_enum_sep(str1[i]) &&
264 string_enum_sep(str2[i]))
265 continue;
267 return str1[i] - str2[i];
270 return 0;
273 struct enum_map {
274 const char *name;
275 int namelen;
276 int value;
279 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
281 static bool
282 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
284 size_t namelen = strlen(name);
285 int i;
287 for (i = 0; i < map_size; i++)
288 if (namelen == map[i].namelen &&
289 !string_enum_compare(name, map[i].name, namelen)) {
290 *value = map[i].value;
291 return TRUE;
294 return FALSE;
297 #define map_enum(attr, map, name) \
298 map_enum_do(map, ARRAY_SIZE(map), attr, name)
300 #define prefixcmp(str1, str2) \
301 strncmp(str1, str2, STRING_SIZE(str2))
303 static inline int
304 suffixcmp(const char *str, int slen, const char *suffix)
306 size_t len = slen >= 0 ? slen : strlen(str);
307 size_t suffixlen = strlen(suffix);
309 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
313 static bool
314 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
316 int valuelen;
318 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
319 bool advance = cmd[valuelen] != 0;
321 cmd[valuelen] = 0;
322 argv[(*argc)++] = chomp_string(cmd);
323 cmd = chomp_string(cmd + valuelen + advance);
326 if (*argc < SIZEOF_ARG)
327 argv[*argc] = NULL;
328 return *argc < SIZEOF_ARG;
331 static void
332 argv_from_env(const char **argv, const char *name)
334 char *env = argv ? getenv(name) : NULL;
335 int argc = 0;
337 if (env && *env)
338 env = strdup(env);
339 if (env && !argv_from_string(argv, &argc, env))
340 die("Too many arguments in the `%s` environment variable", name);
345 * Executing external commands.
348 enum io_type {
349 IO_FD, /* File descriptor based IO. */
350 IO_BG, /* Execute command in the background. */
351 IO_FG, /* Execute command with same std{in,out,err}. */
352 IO_RD, /* Read only fork+exec IO. */
353 IO_WR, /* Write only fork+exec IO. */
354 IO_AP, /* Append fork+exec output to file. */
357 struct io {
358 enum io_type type; /* The requested type of pipe. */
359 const char *dir; /* Directory from which to execute. */
360 pid_t pid; /* Pipe for reading or writing. */
361 int pipe; /* Pipe end for reading or writing. */
362 int error; /* Error status. */
363 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
364 char *buf; /* Read buffer. */
365 size_t bufalloc; /* Allocated buffer size. */
366 size_t bufsize; /* Buffer content size. */
367 char *bufpos; /* Current buffer position. */
368 unsigned int eof:1; /* Has end of file been reached. */
371 static void
372 reset_io(struct io *io)
374 io->pipe = -1;
375 io->pid = 0;
376 io->buf = io->bufpos = NULL;
377 io->bufalloc = io->bufsize = 0;
378 io->error = 0;
379 io->eof = 0;
382 static void
383 init_io(struct io *io, const char *dir, enum io_type type)
385 reset_io(io);
386 io->type = type;
387 io->dir = dir;
390 static bool
391 init_io_rd(struct io *io, const char *argv[], const char *dir,
392 enum format_flags flags)
394 init_io(io, dir, IO_RD);
395 return format_argv(io->argv, argv, flags);
398 static bool
399 io_open(struct io *io, const char *name)
401 init_io(io, NULL, IO_FD);
402 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
403 if (io->pipe == -1)
404 io->error = errno;
405 return io->pipe != -1;
408 static bool
409 kill_io(struct io *io)
411 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
414 static bool
415 done_io(struct io *io)
417 pid_t pid = io->pid;
419 if (io->pipe != -1)
420 close(io->pipe);
421 free(io->buf);
422 reset_io(io);
424 while (pid > 0) {
425 int status;
426 pid_t waiting = waitpid(pid, &status, 0);
428 if (waiting < 0) {
429 if (errno == EINTR)
430 continue;
431 report("waitpid failed (%s)", strerror(errno));
432 return FALSE;
435 return waiting == pid &&
436 !WIFSIGNALED(status) &&
437 WIFEXITED(status) &&
438 !WEXITSTATUS(status);
441 return TRUE;
444 static bool
445 start_io(struct io *io)
447 int pipefds[2] = { -1, -1 };
449 if (io->type == IO_FD)
450 return TRUE;
452 if ((io->type == IO_RD || io->type == IO_WR) &&
453 pipe(pipefds) < 0)
454 return FALSE;
455 else if (io->type == IO_AP)
456 pipefds[1] = io->pipe;
458 if ((io->pid = fork())) {
459 if (pipefds[!(io->type == IO_WR)] != -1)
460 close(pipefds[!(io->type == IO_WR)]);
461 if (io->pid != -1) {
462 io->pipe = pipefds[!!(io->type == IO_WR)];
463 return TRUE;
466 } else {
467 if (io->type != IO_FG) {
468 int devnull = open("/dev/null", O_RDWR);
469 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
470 int writefd = (io->type == IO_RD || io->type == IO_AP)
471 ? pipefds[1] : devnull;
473 dup2(readfd, STDIN_FILENO);
474 dup2(writefd, STDOUT_FILENO);
475 dup2(devnull, STDERR_FILENO);
477 close(devnull);
478 if (pipefds[0] != -1)
479 close(pipefds[0]);
480 if (pipefds[1] != -1)
481 close(pipefds[1]);
484 if (io->dir && *io->dir && chdir(io->dir) == -1)
485 die("Failed to change directory: %s", strerror(errno));
487 execvp(io->argv[0], (char *const*) io->argv);
488 die("Failed to execute program: %s", strerror(errno));
491 if (pipefds[!!(io->type == IO_WR)] != -1)
492 close(pipefds[!!(io->type == IO_WR)]);
493 return FALSE;
496 static bool
497 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
499 init_io(io, dir, type);
500 if (!format_argv(io->argv, argv, FORMAT_NONE))
501 return FALSE;
502 return start_io(io);
505 static int
506 run_io_do(struct io *io)
508 return start_io(io) && done_io(io);
511 static int
512 run_io_bg(const char **argv)
514 struct io io = {};
516 init_io(&io, NULL, IO_BG);
517 if (!format_argv(io.argv, argv, FORMAT_NONE))
518 return FALSE;
519 return run_io_do(&io);
522 static bool
523 run_io_fg(const char **argv, const char *dir)
525 struct io io = {};
527 init_io(&io, dir, IO_FG);
528 if (!format_argv(io.argv, argv, FORMAT_NONE))
529 return FALSE;
530 return run_io_do(&io);
533 static bool
534 run_io_append(const char **argv, enum format_flags flags, int fd)
536 struct io io = {};
538 init_io(&io, NULL, IO_AP);
539 io.pipe = fd;
540 if (format_argv(io.argv, argv, flags))
541 return run_io_do(&io);
542 close(fd);
543 return FALSE;
546 static bool
547 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
549 return init_io_rd(io, argv, NULL, flags) && start_io(io);
552 static bool
553 io_eof(struct io *io)
555 return io->eof;
558 static int
559 io_error(struct io *io)
561 return io->error;
564 static char *
565 io_strerror(struct io *io)
567 return strerror(io->error);
570 static bool
571 io_can_read(struct io *io)
573 struct timeval tv = { 0, 500 };
574 fd_set fds;
576 FD_ZERO(&fds);
577 FD_SET(io->pipe, &fds);
579 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
582 static ssize_t
583 io_read(struct io *io, void *buf, size_t bufsize)
585 do {
586 ssize_t readsize = read(io->pipe, buf, bufsize);
588 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
589 continue;
590 else if (readsize == -1)
591 io->error = errno;
592 else if (readsize == 0)
593 io->eof = 1;
594 return readsize;
595 } while (1);
598 static char *
599 io_get(struct io *io, int c, bool can_read)
601 char *eol;
602 ssize_t readsize;
604 if (!io->buf) {
605 io->buf = io->bufpos = malloc(BUFSIZ);
606 if (!io->buf)
607 return NULL;
608 io->bufalloc = BUFSIZ;
609 io->bufsize = 0;
612 while (TRUE) {
613 if (io->bufsize > 0) {
614 eol = memchr(io->bufpos, c, io->bufsize);
615 if (eol) {
616 char *line = io->bufpos;
618 *eol = 0;
619 io->bufpos = eol + 1;
620 io->bufsize -= io->bufpos - line;
621 return line;
625 if (io_eof(io)) {
626 if (io->bufsize) {
627 io->bufpos[io->bufsize] = 0;
628 io->bufsize = 0;
629 return io->bufpos;
631 return NULL;
634 if (!can_read)
635 return NULL;
637 if (io->bufsize > 0 && io->bufpos > io->buf)
638 memmove(io->buf, io->bufpos, io->bufsize);
640 io->bufpos = io->buf;
641 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
642 if (io_error(io))
643 return NULL;
644 io->bufsize += readsize;
648 static bool
649 io_write(struct io *io, const void *buf, size_t bufsize)
651 size_t written = 0;
653 while (!io_error(io) && written < bufsize) {
654 ssize_t size;
656 size = write(io->pipe, buf + written, bufsize - written);
657 if (size < 0 && (errno == EAGAIN || errno == EINTR))
658 continue;
659 else if (size == -1)
660 io->error = errno;
661 else
662 written += size;
665 return written == bufsize;
668 static bool
669 io_read_buf(struct io *io, char buf[], size_t bufsize)
671 bool error;
673 io->buf = io->bufpos = buf;
674 io->bufalloc = bufsize;
675 error = !io_get(io, '\n', TRUE) && io_error(io);
676 io->buf = NULL;
678 return done_io(io) || error;
681 static bool
682 run_io_buf(const char **argv, char buf[], size_t bufsize)
684 struct io io = {};
686 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
689 static int
690 io_load(struct io *io, const char *separators,
691 int (*read_property)(char *, size_t, char *, size_t))
693 char *name;
694 int state = OK;
696 if (!start_io(io))
697 return ERR;
699 while (state == OK && (name = io_get(io, '\n', TRUE))) {
700 char *value;
701 size_t namelen;
702 size_t valuelen;
704 name = chomp_string(name);
705 namelen = strcspn(name, separators);
707 if (name[namelen]) {
708 name[namelen] = 0;
709 value = chomp_string(name + namelen + 1);
710 valuelen = strlen(value);
712 } else {
713 value = "";
714 valuelen = 0;
717 state = read_property(name, namelen, value, valuelen);
720 if (state != ERR && io_error(io))
721 state = ERR;
722 done_io(io);
724 return state;
727 static int
728 run_io_load(const char **argv, const char *separators,
729 int (*read_property)(char *, size_t, char *, size_t))
731 struct io io = {};
733 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
734 ? io_load(&io, separators, read_property) : ERR;
739 * User requests
742 #define REQ_INFO \
743 /* XXX: Keep the view request first and in sync with views[]. */ \
744 REQ_GROUP("View switching") \
745 REQ_(VIEW_MAIN, "Show main view"), \
746 REQ_(VIEW_DIFF, "Show diff view"), \
747 REQ_(VIEW_LOG, "Show log view"), \
748 REQ_(VIEW_TREE, "Show tree view"), \
749 REQ_(VIEW_BLOB, "Show blob view"), \
750 REQ_(VIEW_BLAME, "Show blame view"), \
751 REQ_(VIEW_HELP, "Show help page"), \
752 REQ_(VIEW_PAGER, "Show pager view"), \
753 REQ_(VIEW_STATUS, "Show status view"), \
754 REQ_(VIEW_STAGE, "Show stage view"), \
756 REQ_GROUP("View manipulation") \
757 REQ_(ENTER, "Enter current line and scroll"), \
758 REQ_(NEXT, "Move to next"), \
759 REQ_(PREVIOUS, "Move to previous"), \
760 REQ_(PARENT, "Move to parent"), \
761 REQ_(VIEW_NEXT, "Move focus to next view"), \
762 REQ_(REFRESH, "Reload and refresh"), \
763 REQ_(MAXIMIZE, "Maximize the current view"), \
764 REQ_(VIEW_CLOSE, "Close the current view"), \
765 REQ_(QUIT, "Close all views and quit"), \
767 REQ_GROUP("View specific requests") \
768 REQ_(STATUS_UPDATE, "Update file status"), \
769 REQ_(STATUS_REVERT, "Revert file changes"), \
770 REQ_(STATUS_MERGE, "Merge file using external tool"), \
771 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
773 REQ_GROUP("Cursor navigation") \
774 REQ_(MOVE_UP, "Move cursor one line up"), \
775 REQ_(MOVE_DOWN, "Move cursor one line down"), \
776 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
777 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
778 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
779 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
781 REQ_GROUP("Scrolling") \
782 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
783 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
784 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
785 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
786 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
787 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
789 REQ_GROUP("Searching") \
790 REQ_(SEARCH, "Search the view"), \
791 REQ_(SEARCH_BACK, "Search backwards in the view"), \
792 REQ_(FIND_NEXT, "Find next search match"), \
793 REQ_(FIND_PREV, "Find previous search match"), \
795 REQ_GROUP("Option manipulation") \
796 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
797 REQ_(TOGGLE_DATE, "Toggle date display"), \
798 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
799 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
800 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
802 REQ_GROUP("Misc") \
803 REQ_(PROMPT, "Bring up the prompt"), \
804 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
805 REQ_(SHOW_VERSION, "Show version information"), \
806 REQ_(STOP_LOADING, "Stop all loading views"), \
807 REQ_(EDIT, "Open in editor"), \
808 REQ_(NONE, "Do nothing")
811 /* User action requests. */
812 enum request {
813 #define REQ_GROUP(help)
814 #define REQ_(req, help) REQ_##req
816 /* Offset all requests to avoid conflicts with ncurses getch values. */
817 REQ_OFFSET = KEY_MAX + 1,
818 REQ_INFO
820 #undef REQ_GROUP
821 #undef REQ_
824 struct request_info {
825 enum request request;
826 const char *name;
827 int namelen;
828 const char *help;
831 static const struct request_info req_info[] = {
832 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
833 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
834 REQ_INFO
835 #undef REQ_GROUP
836 #undef REQ_
839 static enum request
840 get_request(const char *name)
842 int namelen = strlen(name);
843 int i;
845 for (i = 0; i < ARRAY_SIZE(req_info); i++)
846 if (req_info[i].namelen == namelen &&
847 !string_enum_compare(req_info[i].name, name, namelen))
848 return req_info[i].request;
850 return REQ_NONE;
855 * Options
858 /* Option and state variables. */
859 static bool opt_date = TRUE;
860 static bool opt_author = TRUE;
861 static bool opt_line_number = FALSE;
862 static bool opt_line_graphics = TRUE;
863 static bool opt_rev_graph = FALSE;
864 static bool opt_show_refs = TRUE;
865 static int opt_num_interval = NUMBER_INTERVAL;
866 static int opt_tab_size = TAB_SIZE;
867 static int opt_author_cols = AUTHOR_COLS-1;
868 static char opt_path[SIZEOF_STR] = "";
869 static char opt_file[SIZEOF_STR] = "";
870 static char opt_ref[SIZEOF_REF] = "";
871 static char opt_head[SIZEOF_REF] = "";
872 static char opt_head_rev[SIZEOF_REV] = "";
873 static char opt_remote[SIZEOF_REF] = "";
874 static char opt_encoding[20] = "UTF-8";
875 static bool opt_utf8 = TRUE;
876 static char opt_codeset[20] = "UTF-8";
877 static iconv_t opt_iconv = ICONV_NONE;
878 static char opt_search[SIZEOF_STR] = "";
879 static char opt_cdup[SIZEOF_STR] = "";
880 static char opt_prefix[SIZEOF_STR] = "";
881 static char opt_git_dir[SIZEOF_STR] = "";
882 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
883 static char opt_editor[SIZEOF_STR] = "";
884 static FILE *opt_tty = NULL;
886 #define is_initial_commit() (!*opt_head_rev)
887 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
891 * Line-oriented content detection.
894 #define LINE_INFO \
895 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
896 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
897 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
898 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
899 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
900 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
901 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
909 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
910 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
911 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
912 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
916 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
917 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
918 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
919 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
920 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
921 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
922 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
923 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
924 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
925 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
926 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
927 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
928 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
929 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
930 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
931 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
932 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
933 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
934 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
935 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
937 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
938 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
939 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
940 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
941 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
942 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
943 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
944 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
945 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
946 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
947 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
948 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
949 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
951 enum line_type {
952 #define LINE(type, line, fg, bg, attr) \
953 LINE_##type
954 LINE_INFO,
955 LINE_NONE
956 #undef LINE
959 struct line_info {
960 const char *name; /* Option name. */
961 int namelen; /* Size of option name. */
962 const char *line; /* The start of line to match. */
963 int linelen; /* Size of string to match. */
964 int fg, bg, attr; /* Color and text attributes for the lines. */
967 static struct line_info line_info[] = {
968 #define LINE(type, line, fg, bg, attr) \
969 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
970 LINE_INFO
971 #undef LINE
974 static enum line_type
975 get_line_type(const char *line)
977 int linelen = strlen(line);
978 enum line_type type;
980 for (type = 0; type < ARRAY_SIZE(line_info); type++)
981 /* Case insensitive search matches Signed-off-by lines better. */
982 if (linelen >= line_info[type].linelen &&
983 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
984 return type;
986 return LINE_DEFAULT;
989 static inline int
990 get_line_attr(enum line_type type)
992 assert(type < ARRAY_SIZE(line_info));
993 return COLOR_PAIR(type) | line_info[type].attr;
996 static struct line_info *
997 get_line_info(const char *name)
999 size_t namelen = strlen(name);
1000 enum line_type type;
1002 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1003 if (namelen == line_info[type].namelen &&
1004 !string_enum_compare(line_info[type].name, name, namelen))
1005 return &line_info[type];
1007 return NULL;
1010 static void
1011 init_colors(void)
1013 int default_bg = line_info[LINE_DEFAULT].bg;
1014 int default_fg = line_info[LINE_DEFAULT].fg;
1015 enum line_type type;
1017 start_color();
1019 if (assume_default_colors(default_fg, default_bg) == ERR) {
1020 default_bg = COLOR_BLACK;
1021 default_fg = COLOR_WHITE;
1024 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1025 struct line_info *info = &line_info[type];
1026 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1027 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1029 init_pair(type, fg, bg);
1033 struct line {
1034 enum line_type type;
1036 /* State flags */
1037 unsigned int selected:1;
1038 unsigned int dirty:1;
1039 unsigned int cleareol:1;
1041 void *data; /* User data */
1046 * Keys
1049 struct keybinding {
1050 int alias;
1051 enum request request;
1054 static const struct keybinding default_keybindings[] = {
1055 /* View switching */
1056 { 'm', REQ_VIEW_MAIN },
1057 { 'd', REQ_VIEW_DIFF },
1058 { 'l', REQ_VIEW_LOG },
1059 { 't', REQ_VIEW_TREE },
1060 { 'f', REQ_VIEW_BLOB },
1061 { 'B', REQ_VIEW_BLAME },
1062 { 'p', REQ_VIEW_PAGER },
1063 { 'h', REQ_VIEW_HELP },
1064 { 'S', REQ_VIEW_STATUS },
1065 { 'c', REQ_VIEW_STAGE },
1067 /* View manipulation */
1068 { 'q', REQ_VIEW_CLOSE },
1069 { KEY_TAB, REQ_VIEW_NEXT },
1070 { KEY_RETURN, REQ_ENTER },
1071 { KEY_UP, REQ_PREVIOUS },
1072 { KEY_DOWN, REQ_NEXT },
1073 { 'R', REQ_REFRESH },
1074 { KEY_F(5), REQ_REFRESH },
1075 { 'O', REQ_MAXIMIZE },
1077 /* Cursor navigation */
1078 { 'k', REQ_MOVE_UP },
1079 { 'j', REQ_MOVE_DOWN },
1080 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1081 { KEY_END, REQ_MOVE_LAST_LINE },
1082 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1083 { ' ', REQ_MOVE_PAGE_DOWN },
1084 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1085 { 'b', REQ_MOVE_PAGE_UP },
1086 { '-', REQ_MOVE_PAGE_UP },
1088 /* Scrolling */
1089 { KEY_LEFT, REQ_SCROLL_LEFT },
1090 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1091 { KEY_IC, REQ_SCROLL_LINE_UP },
1092 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1093 { 'w', REQ_SCROLL_PAGE_UP },
1094 { 's', REQ_SCROLL_PAGE_DOWN },
1096 /* Searching */
1097 { '/', REQ_SEARCH },
1098 { '?', REQ_SEARCH_BACK },
1099 { 'n', REQ_FIND_NEXT },
1100 { 'N', REQ_FIND_PREV },
1102 /* Misc */
1103 { 'Q', REQ_QUIT },
1104 { 'z', REQ_STOP_LOADING },
1105 { 'v', REQ_SHOW_VERSION },
1106 { 'r', REQ_SCREEN_REDRAW },
1107 { '.', REQ_TOGGLE_LINENO },
1108 { 'D', REQ_TOGGLE_DATE },
1109 { 'A', REQ_TOGGLE_AUTHOR },
1110 { 'g', REQ_TOGGLE_REV_GRAPH },
1111 { 'F', REQ_TOGGLE_REFS },
1112 { ':', REQ_PROMPT },
1113 { 'u', REQ_STATUS_UPDATE },
1114 { '!', REQ_STATUS_REVERT },
1115 { 'M', REQ_STATUS_MERGE },
1116 { '@', REQ_STAGE_NEXT },
1117 { ',', REQ_PARENT },
1118 { 'e', REQ_EDIT },
1121 #define KEYMAP_INFO \
1122 KEYMAP_(GENERIC), \
1123 KEYMAP_(MAIN), \
1124 KEYMAP_(DIFF), \
1125 KEYMAP_(LOG), \
1126 KEYMAP_(TREE), \
1127 KEYMAP_(BLOB), \
1128 KEYMAP_(BLAME), \
1129 KEYMAP_(PAGER), \
1130 KEYMAP_(HELP), \
1131 KEYMAP_(STATUS), \
1132 KEYMAP_(STAGE)
1134 enum keymap {
1135 #define KEYMAP_(name) KEYMAP_##name
1136 KEYMAP_INFO
1137 #undef KEYMAP_
1140 static const struct enum_map keymap_table[] = {
1141 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1142 KEYMAP_INFO
1143 #undef KEYMAP_
1146 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1148 struct keybinding_table {
1149 struct keybinding *data;
1150 size_t size;
1153 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1155 static void
1156 add_keybinding(enum keymap keymap, enum request request, int key)
1158 struct keybinding_table *table = &keybindings[keymap];
1160 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1161 if (!table->data)
1162 die("Failed to allocate keybinding");
1163 table->data[table->size].alias = key;
1164 table->data[table->size++].request = request;
1167 /* Looks for a key binding first in the given map, then in the generic map, and
1168 * lastly in the default keybindings. */
1169 static enum request
1170 get_keybinding(enum keymap keymap, int key)
1172 size_t i;
1174 for (i = 0; i < keybindings[keymap].size; i++)
1175 if (keybindings[keymap].data[i].alias == key)
1176 return keybindings[keymap].data[i].request;
1178 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1179 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1180 return keybindings[KEYMAP_GENERIC].data[i].request;
1182 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1183 if (default_keybindings[i].alias == key)
1184 return default_keybindings[i].request;
1186 return (enum request) key;
1190 struct key {
1191 const char *name;
1192 int value;
1195 static const struct key key_table[] = {
1196 { "Enter", KEY_RETURN },
1197 { "Space", ' ' },
1198 { "Backspace", KEY_BACKSPACE },
1199 { "Tab", KEY_TAB },
1200 { "Escape", KEY_ESC },
1201 { "Left", KEY_LEFT },
1202 { "Right", KEY_RIGHT },
1203 { "Up", KEY_UP },
1204 { "Down", KEY_DOWN },
1205 { "Insert", KEY_IC },
1206 { "Delete", KEY_DC },
1207 { "Hash", '#' },
1208 { "Home", KEY_HOME },
1209 { "End", KEY_END },
1210 { "PageUp", KEY_PPAGE },
1211 { "PageDown", KEY_NPAGE },
1212 { "F1", KEY_F(1) },
1213 { "F2", KEY_F(2) },
1214 { "F3", KEY_F(3) },
1215 { "F4", KEY_F(4) },
1216 { "F5", KEY_F(5) },
1217 { "F6", KEY_F(6) },
1218 { "F7", KEY_F(7) },
1219 { "F8", KEY_F(8) },
1220 { "F9", KEY_F(9) },
1221 { "F10", KEY_F(10) },
1222 { "F11", KEY_F(11) },
1223 { "F12", KEY_F(12) },
1226 static int
1227 get_key_value(const char *name)
1229 int i;
1231 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1232 if (!strcasecmp(key_table[i].name, name))
1233 return key_table[i].value;
1235 if (strlen(name) == 1 && isprint(*name))
1236 return (int) *name;
1238 return ERR;
1241 static const char *
1242 get_key_name(int key_value)
1244 static char key_char[] = "'X'";
1245 const char *seq = NULL;
1246 int key;
1248 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1249 if (key_table[key].value == key_value)
1250 seq = key_table[key].name;
1252 if (seq == NULL &&
1253 key_value < 127 &&
1254 isprint(key_value)) {
1255 key_char[1] = (char) key_value;
1256 seq = key_char;
1259 return seq ? seq : "(no key)";
1262 static const char *
1263 get_key(enum request request)
1265 static char buf[BUFSIZ];
1266 size_t pos = 0;
1267 char *sep = "";
1268 int i;
1270 buf[pos] = 0;
1272 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1273 const struct keybinding *keybinding = &default_keybindings[i];
1275 if (keybinding->request != request)
1276 continue;
1278 if (!string_format_from(buf, &pos, "%s%s", sep,
1279 get_key_name(keybinding->alias)))
1280 return "Too many keybindings!";
1281 sep = ", ";
1284 return buf;
1287 struct run_request {
1288 enum keymap keymap;
1289 int key;
1290 const char *argv[SIZEOF_ARG];
1293 static struct run_request *run_request;
1294 static size_t run_requests;
1296 static enum request
1297 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1299 struct run_request *req;
1301 if (argc >= ARRAY_SIZE(req->argv) - 1)
1302 return REQ_NONE;
1304 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1305 if (!req)
1306 return REQ_NONE;
1308 run_request = req;
1309 req = &run_request[run_requests];
1310 req->keymap = keymap;
1311 req->key = key;
1312 req->argv[0] = NULL;
1314 if (!format_argv(req->argv, argv, FORMAT_NONE))
1315 return REQ_NONE;
1317 return REQ_NONE + ++run_requests;
1320 static struct run_request *
1321 get_run_request(enum request request)
1323 if (request <= REQ_NONE)
1324 return NULL;
1325 return &run_request[request - REQ_NONE - 1];
1328 static void
1329 add_builtin_run_requests(void)
1331 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1332 const char *gc[] = { "git", "gc", NULL };
1333 struct {
1334 enum keymap keymap;
1335 int key;
1336 int argc;
1337 const char **argv;
1338 } reqs[] = {
1339 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1340 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1342 int i;
1344 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1345 enum request req;
1347 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1348 if (req != REQ_NONE)
1349 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1354 * User config file handling.
1357 static int config_lineno;
1358 static bool config_errors;
1359 static const char *config_msg;
1361 static const struct enum_map color_map[] = {
1362 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1363 COLOR_MAP(DEFAULT),
1364 COLOR_MAP(BLACK),
1365 COLOR_MAP(BLUE),
1366 COLOR_MAP(CYAN),
1367 COLOR_MAP(GREEN),
1368 COLOR_MAP(MAGENTA),
1369 COLOR_MAP(RED),
1370 COLOR_MAP(WHITE),
1371 COLOR_MAP(YELLOW),
1374 static const struct enum_map attr_map[] = {
1375 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1376 ATTR_MAP(NORMAL),
1377 ATTR_MAP(BLINK),
1378 ATTR_MAP(BOLD),
1379 ATTR_MAP(DIM),
1380 ATTR_MAP(REVERSE),
1381 ATTR_MAP(STANDOUT),
1382 ATTR_MAP(UNDERLINE),
1385 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1387 static int
1388 parse_int(int *opt, const char *arg, int min, int max)
1390 int value = atoi(arg);
1392 if (min <= value && value <= max) {
1393 *opt = value;
1394 return OK;
1397 config_msg = "Integer value out of bound";
1398 return ERR;
1401 static bool
1402 set_color(int *color, const char *name)
1404 if (map_enum(color, color_map, name))
1405 return TRUE;
1406 if (!prefixcmp(name, "color"))
1407 return parse_int(color, name + 5, 0, 255) == OK;
1408 return FALSE;
1411 /* Wants: object fgcolor bgcolor [attribute] */
1412 static int
1413 option_color_command(int argc, const char *argv[])
1415 struct line_info *info;
1417 if (argc != 3 && argc != 4) {
1418 config_msg = "Wrong number of arguments given to color command";
1419 return ERR;
1422 info = get_line_info(argv[0]);
1423 if (!info) {
1424 static const struct enum_map obsolete[] = {
1425 ENUM_MAP("main-delim", LINE_DELIMITER),
1426 ENUM_MAP("main-date", LINE_DATE),
1427 ENUM_MAP("main-author", LINE_AUTHOR),
1429 int index;
1431 if (!map_enum(&index, obsolete, argv[0])) {
1432 config_msg = "Unknown color name";
1433 return ERR;
1435 info = &line_info[index];
1438 if (!set_color(&info->fg, argv[1]) ||
1439 !set_color(&info->bg, argv[2])) {
1440 config_msg = "Unknown color";
1441 return ERR;
1444 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1445 config_msg = "Unknown attribute";
1446 return ERR;
1449 return OK;
1452 static int parse_bool(bool *opt, const char *arg)
1454 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1455 ? TRUE : FALSE;
1456 return OK;
1459 static int
1460 parse_string(char *opt, const char *arg, size_t optsize)
1462 int arglen = strlen(arg);
1464 switch (arg[0]) {
1465 case '\"':
1466 case '\'':
1467 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1468 config_msg = "Unmatched quotation";
1469 return ERR;
1471 arg += 1; arglen -= 2;
1472 default:
1473 string_ncopy_do(opt, optsize, arg, arglen);
1474 return OK;
1478 /* Wants: name = value */
1479 static int
1480 option_set_command(int argc, const char *argv[])
1482 if (argc != 3) {
1483 config_msg = "Wrong number of arguments given to set command";
1484 return ERR;
1487 if (strcmp(argv[1], "=")) {
1488 config_msg = "No value assigned";
1489 return ERR;
1492 if (!strcmp(argv[0], "show-author"))
1493 return parse_bool(&opt_author, argv[2]);
1495 if (!strcmp(argv[0], "show-date"))
1496 return parse_bool(&opt_date, argv[2]);
1498 if (!strcmp(argv[0], "show-rev-graph"))
1499 return parse_bool(&opt_rev_graph, argv[2]);
1501 if (!strcmp(argv[0], "show-refs"))
1502 return parse_bool(&opt_show_refs, argv[2]);
1504 if (!strcmp(argv[0], "show-line-numbers"))
1505 return parse_bool(&opt_line_number, argv[2]);
1507 if (!strcmp(argv[0], "line-graphics"))
1508 return parse_bool(&opt_line_graphics, argv[2]);
1510 if (!strcmp(argv[0], "line-number-interval"))
1511 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1513 if (!strcmp(argv[0], "author-width"))
1514 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1516 if (!strcmp(argv[0], "tab-size"))
1517 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1519 if (!strcmp(argv[0], "commit-encoding"))
1520 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1522 config_msg = "Unknown variable name";
1523 return ERR;
1526 /* Wants: mode request key */
1527 static int
1528 option_bind_command(int argc, const char *argv[])
1530 enum request request;
1531 int keymap;
1532 int key;
1534 if (argc < 3) {
1535 config_msg = "Wrong number of arguments given to bind command";
1536 return ERR;
1539 if (set_keymap(&keymap, argv[0]) == ERR) {
1540 config_msg = "Unknown key map";
1541 return ERR;
1544 key = get_key_value(argv[1]);
1545 if (key == ERR) {
1546 config_msg = "Unknown key";
1547 return ERR;
1550 request = get_request(argv[2]);
1551 if (request == REQ_NONE) {
1552 static const struct enum_map obsolete[] = {
1553 ENUM_MAP("cherry-pick", REQ_NONE),
1554 ENUM_MAP("screen-resize", REQ_NONE),
1555 ENUM_MAP("tree-parent", REQ_PARENT),
1557 int alias;
1559 if (map_enum(&alias, obsolete, argv[2])) {
1560 if (alias != REQ_NONE)
1561 add_keybinding(keymap, alias, key);
1562 config_msg = "Obsolete request name";
1563 return ERR;
1566 if (request == REQ_NONE && *argv[2]++ == '!')
1567 request = add_run_request(keymap, key, argc - 2, argv + 2);
1568 if (request == REQ_NONE) {
1569 config_msg = "Unknown request name";
1570 return ERR;
1573 add_keybinding(keymap, request, key);
1575 return OK;
1578 static int
1579 set_option(const char *opt, char *value)
1581 const char *argv[SIZEOF_ARG];
1582 int argc = 0;
1584 if (!argv_from_string(argv, &argc, value)) {
1585 config_msg = "Too many option arguments";
1586 return ERR;
1589 if (!strcmp(opt, "color"))
1590 return option_color_command(argc, argv);
1592 if (!strcmp(opt, "set"))
1593 return option_set_command(argc, argv);
1595 if (!strcmp(opt, "bind"))
1596 return option_bind_command(argc, argv);
1598 config_msg = "Unknown option command";
1599 return ERR;
1602 static int
1603 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1605 int status = OK;
1607 config_lineno++;
1608 config_msg = "Internal error";
1610 /* Check for comment markers, since read_properties() will
1611 * only ensure opt and value are split at first " \t". */
1612 optlen = strcspn(opt, "#");
1613 if (optlen == 0)
1614 return OK;
1616 if (opt[optlen] != 0) {
1617 config_msg = "No option value";
1618 status = ERR;
1620 } else {
1621 /* Look for comment endings in the value. */
1622 size_t len = strcspn(value, "#");
1624 if (len < valuelen) {
1625 valuelen = len;
1626 value[valuelen] = 0;
1629 status = set_option(opt, value);
1632 if (status == ERR) {
1633 warn("Error on line %d, near '%.*s': %s",
1634 config_lineno, (int) optlen, opt, config_msg);
1635 config_errors = TRUE;
1638 /* Always keep going if errors are encountered. */
1639 return OK;
1642 static void
1643 load_option_file(const char *path)
1645 struct io io = {};
1647 /* It's OK that the file doesn't exist. */
1648 if (!io_open(&io, path))
1649 return;
1651 config_lineno = 0;
1652 config_errors = FALSE;
1654 if (io_load(&io, " \t", read_option) == ERR ||
1655 config_errors == TRUE)
1656 warn("Errors while loading %s.", path);
1659 static int
1660 load_options(void)
1662 const char *home = getenv("HOME");
1663 const char *tigrc_user = getenv("TIGRC_USER");
1664 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1665 char buf[SIZEOF_STR];
1667 add_builtin_run_requests();
1669 if (!tigrc_system)
1670 tigrc_system = SYSCONFDIR "/tigrc";
1671 load_option_file(tigrc_system);
1673 if (!tigrc_user) {
1674 if (!home || !string_format(buf, "%s/.tigrc", home))
1675 return ERR;
1676 tigrc_user = buf;
1678 load_option_file(tigrc_user);
1680 return OK;
1685 * The viewer
1688 struct view;
1689 struct view_ops;
1691 /* The display array of active views and the index of the current view. */
1692 static struct view *display[2];
1693 static unsigned int current_view;
1695 #define foreach_displayed_view(view, i) \
1696 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1698 #define displayed_views() (display[1] != NULL ? 2 : 1)
1700 /* Current head and commit ID */
1701 static char ref_blob[SIZEOF_REF] = "";
1702 static char ref_commit[SIZEOF_REF] = "HEAD";
1703 static char ref_head[SIZEOF_REF] = "HEAD";
1705 struct view {
1706 const char *name; /* View name */
1707 const char *cmd_env; /* Command line set via environment */
1708 const char *id; /* Points to either of ref_{head,commit,blob} */
1710 struct view_ops *ops; /* View operations */
1712 enum keymap keymap; /* What keymap does this view have */
1713 bool git_dir; /* Whether the view requires a git directory. */
1715 char ref[SIZEOF_REF]; /* Hovered commit reference */
1716 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1718 int height, width; /* The width and height of the main window */
1719 WINDOW *win; /* The main window */
1720 WINDOW *title; /* The title window living below the main window */
1722 /* Navigation */
1723 unsigned long offset; /* Offset of the window top */
1724 unsigned long yoffset; /* Offset from the window side. */
1725 unsigned long lineno; /* Current line number */
1726 unsigned long p_offset; /* Previous offset of the window top */
1727 unsigned long p_yoffset;/* Previous offset from the window side */
1728 unsigned long p_lineno; /* Previous current line number */
1729 bool p_restore; /* Should the previous position be restored. */
1731 /* Searching */
1732 char grep[SIZEOF_STR]; /* Search string */
1733 regex_t *regex; /* Pre-compiled regexp */
1735 /* If non-NULL, points to the view that opened this view. If this view
1736 * is closed tig will switch back to the parent view. */
1737 struct view *parent;
1739 /* Buffering */
1740 size_t lines; /* Total number of lines */
1741 struct line *line; /* Line index */
1742 size_t line_alloc; /* Total number of allocated lines */
1743 unsigned int digits; /* Number of digits in the lines member. */
1745 /* Drawing */
1746 struct line *curline; /* Line currently being drawn. */
1747 enum line_type curtype; /* Attribute currently used for drawing. */
1748 unsigned long col; /* Column when drawing. */
1749 bool has_scrolled; /* View was scrolled. */
1750 bool can_hscroll; /* View can be scrolled horizontally. */
1752 /* Loading */
1753 struct io io;
1754 struct io *pipe;
1755 time_t start_time;
1756 time_t update_secs;
1759 struct view_ops {
1760 /* What type of content being displayed. Used in the title bar. */
1761 const char *type;
1762 /* Default command arguments. */
1763 const char **argv;
1764 /* Open and reads in all view content. */
1765 bool (*open)(struct view *view);
1766 /* Read one line; updates view->line. */
1767 bool (*read)(struct view *view, char *data);
1768 /* Draw one line; @lineno must be < view->height. */
1769 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1770 /* Depending on view handle a special requests. */
1771 enum request (*request)(struct view *view, enum request request, struct line *line);
1772 /* Search for regexp in a line. */
1773 bool (*grep)(struct view *view, struct line *line);
1774 /* Select line */
1775 void (*select)(struct view *view, struct line *line);
1778 static struct view_ops blame_ops;
1779 static struct view_ops blob_ops;
1780 static struct view_ops diff_ops;
1781 static struct view_ops help_ops;
1782 static struct view_ops log_ops;
1783 static struct view_ops main_ops;
1784 static struct view_ops pager_ops;
1785 static struct view_ops stage_ops;
1786 static struct view_ops status_ops;
1787 static struct view_ops tree_ops;
1789 #define VIEW_STR(name, env, ref, ops, map, git) \
1790 { name, #env, ref, ops, map, git }
1792 #define VIEW_(id, name, ops, git, ref) \
1793 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1796 static struct view views[] = {
1797 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1798 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1799 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1800 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1801 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1802 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1803 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1804 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1805 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1806 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1809 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1810 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1812 #define foreach_view(view, i) \
1813 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1815 #define view_is_displayed(view) \
1816 (view == display[0] || view == display[1])
1819 enum line_graphic {
1820 LINE_GRAPHIC_VLINE
1823 static int line_graphics[] = {
1824 /* LINE_GRAPHIC_VLINE: */ '|'
1827 static inline void
1828 set_view_attr(struct view *view, enum line_type type)
1830 if (!view->curline->selected && view->curtype != type) {
1831 wattrset(view->win, get_line_attr(type));
1832 wchgat(view->win, -1, 0, type, NULL);
1833 view->curtype = type;
1837 static int
1838 draw_chars(struct view *view, enum line_type type, const char *string,
1839 int max_len, bool use_tilde)
1841 int len = 0;
1842 int col = 0;
1843 int trimmed = FALSE;
1844 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1846 if (max_len <= 0)
1847 return 0;
1849 if (opt_utf8) {
1850 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1851 } else {
1852 col = len = strlen(string);
1853 if (len > max_len) {
1854 if (use_tilde) {
1855 max_len -= 1;
1857 col = len = max_len;
1858 trimmed = TRUE;
1862 set_view_attr(view, type);
1863 if (len > 0)
1864 waddnstr(view->win, string, len);
1865 if (trimmed && use_tilde) {
1866 set_view_attr(view, LINE_DELIMITER);
1867 waddch(view->win, '~');
1868 col++;
1871 if (view->col + col >= view->width + view->yoffset)
1872 view->can_hscroll = TRUE;
1874 return col;
1877 static int
1878 draw_space(struct view *view, enum line_type type, int max, int spaces)
1880 static char space[] = " ";
1881 int col = 0;
1883 spaces = MIN(max, spaces);
1885 while (spaces > 0) {
1886 int len = MIN(spaces, sizeof(space) - 1);
1888 col += draw_chars(view, type, space, spaces, FALSE);
1889 spaces -= len;
1892 return col;
1895 static bool
1896 draw_lineno(struct view *view, unsigned int lineno)
1898 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1899 char number[10];
1900 int digits3 = view->digits < 3 ? 3 : view->digits;
1901 int max_number = MIN(digits3, STRING_SIZE(number));
1902 int max = view->width - view->col;
1903 int col;
1905 if (max < max_number)
1906 max_number = max;
1908 lineno += view->offset + 1;
1909 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1910 static char fmt[] = "%1ld";
1912 if (view->digits <= 9)
1913 fmt[1] = '0' + digits3;
1915 if (!string_format(number, fmt, lineno))
1916 number[0] = 0;
1917 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1918 } else {
1919 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1922 if (col < max && skip <= col) {
1923 set_view_attr(view, LINE_DEFAULT);
1924 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1926 col++;
1928 view->col += col;
1929 if (col < max && skip <= col)
1930 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1931 view->col++;
1933 return view->width + view->yoffset <= view->col;
1936 static bool
1937 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1939 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1940 return view->width - view->col <= 0;
1943 static bool
1944 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1946 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1947 int max = view->width - view->col;
1948 int i;
1950 if (max < size)
1951 size = max;
1953 set_view_attr(view, type);
1954 /* Using waddch() instead of waddnstr() ensures that
1955 * they'll be rendered correctly for the cursor line. */
1956 for (i = skip; i < size; i++)
1957 waddch(view->win, graphic[i]);
1959 view->col += size;
1960 if (size < max && skip <= size)
1961 waddch(view->win, ' ');
1962 view->col++;
1964 return view->width - view->col <= 0;
1967 static bool
1968 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1970 int max = MIN(view->width - view->col, len);
1971 int col;
1973 if (text)
1974 col = draw_chars(view, type, text, max - 1, trim);
1975 else
1976 col = draw_space(view, type, max - 1, max - 1);
1978 view->col += col;
1979 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1980 return view->width + view->yoffset <= view->col;
1983 static bool
1984 draw_date(struct view *view, struct tm *time)
1986 char buf[DATE_COLS];
1987 char *date;
1988 int timelen = 0;
1990 if (time)
1991 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1992 date = timelen ? buf : NULL;
1994 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1997 static bool
1998 draw_author(struct view *view, const char *author)
2000 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2002 if (!trim) {
2003 static char initials[10];
2004 size_t pos;
2006 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2008 memset(initials, 0, sizeof(initials));
2009 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2010 while (is_initial_sep(*author))
2011 author++;
2012 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2013 while (*author && !is_initial_sep(author[1]))
2014 author++;
2017 author = initials;
2020 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2023 static bool
2024 draw_mode(struct view *view, mode_t mode)
2026 const char *str;
2028 if (S_ISDIR(mode))
2029 str = "drwxr-xr-x";
2030 else if (S_ISLNK(mode))
2031 str = "lrwxrwxrwx";
2032 else if (S_ISGITLINK(mode))
2033 str = "m---------";
2034 else if (S_ISREG(mode) && mode & S_IXUSR)
2035 str = "-rwxr-xr-x";
2036 else if (S_ISREG(mode))
2037 str = "-rw-r--r--";
2038 else
2039 str = "----------";
2041 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2044 static bool
2045 draw_view_line(struct view *view, unsigned int lineno)
2047 struct line *line;
2048 bool selected = (view->offset + lineno == view->lineno);
2050 assert(view_is_displayed(view));
2052 if (view->offset + lineno >= view->lines)
2053 return FALSE;
2055 line = &view->line[view->offset + lineno];
2057 wmove(view->win, lineno, 0);
2058 if (line->cleareol)
2059 wclrtoeol(view->win);
2060 view->col = 0;
2061 view->curline = line;
2062 view->curtype = LINE_NONE;
2063 line->selected = FALSE;
2064 line->dirty = line->cleareol = 0;
2066 if (selected) {
2067 set_view_attr(view, LINE_CURSOR);
2068 line->selected = TRUE;
2069 view->ops->select(view, line);
2072 return view->ops->draw(view, line, lineno);
2075 static void
2076 redraw_view_dirty(struct view *view)
2078 bool dirty = FALSE;
2079 int lineno;
2081 for (lineno = 0; lineno < view->height; lineno++) {
2082 if (view->offset + lineno >= view->lines)
2083 break;
2084 if (!view->line[view->offset + lineno].dirty)
2085 continue;
2086 dirty = TRUE;
2087 if (!draw_view_line(view, lineno))
2088 break;
2091 if (!dirty)
2092 return;
2093 wnoutrefresh(view->win);
2096 static void
2097 redraw_view_from(struct view *view, int lineno)
2099 assert(0 <= lineno && lineno < view->height);
2101 if (lineno == 0)
2102 view->can_hscroll = FALSE;
2104 for (; lineno < view->height; lineno++) {
2105 if (!draw_view_line(view, lineno))
2106 break;
2109 wnoutrefresh(view->win);
2112 static void
2113 redraw_view(struct view *view)
2115 werase(view->win);
2116 redraw_view_from(view, 0);
2120 static void
2121 update_view_title(struct view *view)
2123 char buf[SIZEOF_STR];
2124 char state[SIZEOF_STR];
2125 size_t bufpos = 0, statelen = 0;
2127 assert(view_is_displayed(view));
2129 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2130 unsigned int view_lines = view->offset + view->height;
2131 unsigned int lines = view->lines
2132 ? MIN(view_lines, view->lines) * 100 / view->lines
2133 : 0;
2135 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2136 view->ops->type,
2137 view->lineno + 1,
2138 view->lines,
2139 lines);
2143 if (view->pipe) {
2144 time_t secs = time(NULL) - view->start_time;
2146 /* Three git seconds are a long time ... */
2147 if (secs > 2)
2148 string_format_from(state, &statelen, " loading %lds", secs);
2151 string_format_from(buf, &bufpos, "[%s]", view->name);
2152 if (*view->ref && bufpos < view->width) {
2153 size_t refsize = strlen(view->ref);
2154 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2156 if (minsize < view->width)
2157 refsize = view->width - minsize + 7;
2158 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2161 if (statelen && bufpos < view->width) {
2162 string_format_from(buf, &bufpos, "%s", state);
2165 if (view == display[current_view])
2166 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2167 else
2168 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2170 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2171 wclrtoeol(view->title);
2172 wnoutrefresh(view->title);
2175 static void
2176 resize_display(void)
2178 int offset, i;
2179 struct view *base = display[0];
2180 struct view *view = display[1] ? display[1] : display[0];
2182 /* Setup window dimensions */
2184 getmaxyx(stdscr, base->height, base->width);
2186 /* Make room for the status window. */
2187 base->height -= 1;
2189 if (view != base) {
2190 /* Horizontal split. */
2191 view->width = base->width;
2192 view->height = SCALE_SPLIT_VIEW(base->height);
2193 base->height -= view->height;
2195 /* Make room for the title bar. */
2196 view->height -= 1;
2199 /* Make room for the title bar. */
2200 base->height -= 1;
2202 offset = 0;
2204 foreach_displayed_view (view, i) {
2205 if (!view->win) {
2206 view->win = newwin(view->height, 0, offset, 0);
2207 if (!view->win)
2208 die("Failed to create %s view", view->name);
2210 scrollok(view->win, FALSE);
2212 view->title = newwin(1, 0, offset + view->height, 0);
2213 if (!view->title)
2214 die("Failed to create title window");
2216 } else {
2217 wresize(view->win, view->height, view->width);
2218 mvwin(view->win, offset, 0);
2219 mvwin(view->title, offset + view->height, 0);
2222 offset += view->height + 1;
2226 static void
2227 redraw_display(bool clear)
2229 struct view *view;
2230 int i;
2232 foreach_displayed_view (view, i) {
2233 if (clear)
2234 wclear(view->win);
2235 redraw_view(view);
2236 update_view_title(view);
2240 static void
2241 toggle_view_option(bool *option, const char *help)
2243 *option = !*option;
2244 redraw_display(FALSE);
2245 report("%sabling %s", *option ? "En" : "Dis", help);
2248 static void
2249 maximize_view(struct view *view)
2251 memset(display, 0, sizeof(display));
2252 current_view = 0;
2253 display[current_view] = view;
2254 resize_display();
2255 redraw_display(FALSE);
2256 report("");
2261 * Navigation
2264 static bool
2265 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2267 if (lineno >= view->lines)
2268 lineno = view->lines > 0 ? view->lines - 1 : 0;
2270 if (offset > lineno || offset + view->height <= lineno) {
2271 unsigned long half = view->height / 2;
2273 if (lineno > half)
2274 offset = lineno - half;
2275 else
2276 offset = 0;
2279 if (offset != view->offset || lineno != view->lineno) {
2280 view->offset = offset;
2281 view->lineno = lineno;
2282 return TRUE;
2285 return FALSE;
2288 /* Scrolling backend */
2289 static void
2290 do_scroll_view(struct view *view, int lines)
2292 bool redraw_current_line = FALSE;
2294 /* The rendering expects the new offset. */
2295 view->offset += lines;
2297 assert(0 <= view->offset && view->offset < view->lines);
2298 assert(lines);
2300 /* Move current line into the view. */
2301 if (view->lineno < view->offset) {
2302 view->lineno = view->offset;
2303 redraw_current_line = TRUE;
2304 } else if (view->lineno >= view->offset + view->height) {
2305 view->lineno = view->offset + view->height - 1;
2306 redraw_current_line = TRUE;
2309 assert(view->offset <= view->lineno && view->lineno < view->lines);
2311 /* Redraw the whole screen if scrolling is pointless. */
2312 if (view->height < ABS(lines)) {
2313 redraw_view(view);
2315 } else {
2316 int line = lines > 0 ? view->height - lines : 0;
2317 int end = line + ABS(lines);
2319 scrollok(view->win, TRUE);
2320 wscrl(view->win, lines);
2321 scrollok(view->win, FALSE);
2323 while (line < end && draw_view_line(view, line))
2324 line++;
2326 if (redraw_current_line)
2327 draw_view_line(view, view->lineno - view->offset);
2328 wnoutrefresh(view->win);
2331 view->has_scrolled = TRUE;
2332 report("");
2335 /* Scroll frontend */
2336 static void
2337 scroll_view(struct view *view, enum request request)
2339 int lines = 1;
2341 assert(view_is_displayed(view));
2343 switch (request) {
2344 case REQ_SCROLL_LEFT:
2345 if (view->yoffset == 0) {
2346 report("Cannot scroll beyond the first column");
2347 return;
2349 if (view->yoffset <= SCROLL_INTERVAL)
2350 view->yoffset = 0;
2351 else
2352 view->yoffset -= SCROLL_INTERVAL;
2353 redraw_view_from(view, 0);
2354 report("");
2355 return;
2356 case REQ_SCROLL_RIGHT:
2357 if (!view->can_hscroll) {
2358 report("Cannot scroll beyond the last column");
2359 return;
2361 view->yoffset += SCROLL_INTERVAL;
2362 redraw_view(view);
2363 report("");
2364 return;
2365 case REQ_SCROLL_PAGE_DOWN:
2366 lines = view->height;
2367 case REQ_SCROLL_LINE_DOWN:
2368 if (view->offset + lines > view->lines)
2369 lines = view->lines - view->offset;
2371 if (lines == 0 || view->offset + view->height >= view->lines) {
2372 report("Cannot scroll beyond the last line");
2373 return;
2375 break;
2377 case REQ_SCROLL_PAGE_UP:
2378 lines = view->height;
2379 case REQ_SCROLL_LINE_UP:
2380 if (lines > view->offset)
2381 lines = view->offset;
2383 if (lines == 0) {
2384 report("Cannot scroll beyond the first line");
2385 return;
2388 lines = -lines;
2389 break;
2391 default:
2392 die("request %d not handled in switch", request);
2395 do_scroll_view(view, lines);
2398 /* Cursor moving */
2399 static void
2400 move_view(struct view *view, enum request request)
2402 int scroll_steps = 0;
2403 int steps;
2405 switch (request) {
2406 case REQ_MOVE_FIRST_LINE:
2407 steps = -view->lineno;
2408 break;
2410 case REQ_MOVE_LAST_LINE:
2411 steps = view->lines - view->lineno - 1;
2412 break;
2414 case REQ_MOVE_PAGE_UP:
2415 steps = view->height > view->lineno
2416 ? -view->lineno : -view->height;
2417 break;
2419 case REQ_MOVE_PAGE_DOWN:
2420 steps = view->lineno + view->height >= view->lines
2421 ? view->lines - view->lineno - 1 : view->height;
2422 break;
2424 case REQ_MOVE_UP:
2425 steps = -1;
2426 break;
2428 case REQ_MOVE_DOWN:
2429 steps = 1;
2430 break;
2432 default:
2433 die("request %d not handled in switch", request);
2436 if (steps <= 0 && view->lineno == 0) {
2437 report("Cannot move beyond the first line");
2438 return;
2440 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2441 report("Cannot move beyond the last line");
2442 return;
2445 /* Move the current line */
2446 view->lineno += steps;
2447 assert(0 <= view->lineno && view->lineno < view->lines);
2449 /* Check whether the view needs to be scrolled */
2450 if (view->lineno < view->offset ||
2451 view->lineno >= view->offset + view->height) {
2452 scroll_steps = steps;
2453 if (steps < 0 && -steps > view->offset) {
2454 scroll_steps = -view->offset;
2456 } else if (steps > 0) {
2457 if (view->lineno == view->lines - 1 &&
2458 view->lines > view->height) {
2459 scroll_steps = view->lines - view->offset - 1;
2460 if (scroll_steps >= view->height)
2461 scroll_steps -= view->height - 1;
2466 if (!view_is_displayed(view)) {
2467 view->offset += scroll_steps;
2468 assert(0 <= view->offset && view->offset < view->lines);
2469 view->ops->select(view, &view->line[view->lineno]);
2470 return;
2473 /* Repaint the old "current" line if we be scrolling */
2474 if (ABS(steps) < view->height)
2475 draw_view_line(view, view->lineno - steps - view->offset);
2477 if (scroll_steps) {
2478 do_scroll_view(view, scroll_steps);
2479 return;
2482 /* Draw the current line */
2483 draw_view_line(view, view->lineno - view->offset);
2485 wnoutrefresh(view->win);
2486 report("");
2491 * Searching
2494 static void search_view(struct view *view, enum request request);
2496 static void
2497 select_view_line(struct view *view, unsigned long lineno)
2499 unsigned long old_lineno = view->lineno;
2500 unsigned long old_offset = view->offset;
2502 if (goto_view_line(view, view->offset, lineno)) {
2503 if (view_is_displayed(view)) {
2504 if (old_offset != view->offset) {
2505 redraw_view(view);
2506 } else {
2507 draw_view_line(view, old_lineno - view->offset);
2508 draw_view_line(view, view->lineno - view->offset);
2509 wnoutrefresh(view->win);
2511 } else {
2512 view->ops->select(view, &view->line[view->lineno]);
2517 static void
2518 find_next(struct view *view, enum request request)
2520 unsigned long lineno = view->lineno;
2521 int direction;
2523 if (!*view->grep) {
2524 if (!*opt_search)
2525 report("No previous search");
2526 else
2527 search_view(view, request);
2528 return;
2531 switch (request) {
2532 case REQ_SEARCH:
2533 case REQ_FIND_NEXT:
2534 direction = 1;
2535 break;
2537 case REQ_SEARCH_BACK:
2538 case REQ_FIND_PREV:
2539 direction = -1;
2540 break;
2542 default:
2543 return;
2546 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2547 lineno += direction;
2549 /* Note, lineno is unsigned long so will wrap around in which case it
2550 * will become bigger than view->lines. */
2551 for (; lineno < view->lines; lineno += direction) {
2552 if (view->ops->grep(view, &view->line[lineno])) {
2553 select_view_line(view, lineno);
2554 report("Line %ld matches '%s'", lineno + 1, view->grep);
2555 return;
2559 report("No match found for '%s'", view->grep);
2562 static void
2563 search_view(struct view *view, enum request request)
2565 int regex_err;
2567 if (view->regex) {
2568 regfree(view->regex);
2569 *view->grep = 0;
2570 } else {
2571 view->regex = calloc(1, sizeof(*view->regex));
2572 if (!view->regex)
2573 return;
2576 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2577 if (regex_err != 0) {
2578 char buf[SIZEOF_STR] = "unknown error";
2580 regerror(regex_err, view->regex, buf, sizeof(buf));
2581 report("Search failed: %s", buf);
2582 return;
2585 string_copy(view->grep, opt_search);
2587 find_next(view, request);
2591 * Incremental updating
2594 static void
2595 reset_view(struct view *view)
2597 int i;
2599 for (i = 0; i < view->lines; i++)
2600 free(view->line[i].data);
2601 free(view->line);
2603 view->p_offset = view->offset;
2604 view->p_yoffset = view->yoffset;
2605 view->p_lineno = view->lineno;
2607 view->line = NULL;
2608 view->offset = 0;
2609 view->yoffset = 0;
2610 view->lines = 0;
2611 view->lineno = 0;
2612 view->line_alloc = 0;
2613 view->vid[0] = 0;
2614 view->update_secs = 0;
2617 static void
2618 free_argv(const char *argv[])
2620 int argc;
2622 for (argc = 0; argv[argc]; argc++)
2623 free((void *) argv[argc]);
2626 static bool
2627 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2629 char buf[SIZEOF_STR];
2630 int argc;
2631 bool noreplace = flags == FORMAT_NONE;
2633 free_argv(dst_argv);
2635 for (argc = 0; src_argv[argc]; argc++) {
2636 const char *arg = src_argv[argc];
2637 size_t bufpos = 0;
2639 while (arg) {
2640 char *next = strstr(arg, "%(");
2641 int len = next - arg;
2642 const char *value;
2644 if (!next || noreplace) {
2645 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2646 noreplace = TRUE;
2647 len = strlen(arg);
2648 value = "";
2650 } else if (!prefixcmp(next, "%(directory)")) {
2651 value = opt_path;
2653 } else if (!prefixcmp(next, "%(file)")) {
2654 value = opt_file;
2656 } else if (!prefixcmp(next, "%(ref)")) {
2657 value = *opt_ref ? opt_ref : "HEAD";
2659 } else if (!prefixcmp(next, "%(head)")) {
2660 value = ref_head;
2662 } else if (!prefixcmp(next, "%(commit)")) {
2663 value = ref_commit;
2665 } else if (!prefixcmp(next, "%(blob)")) {
2666 value = ref_blob;
2668 } else {
2669 report("Unknown replacement: `%s`", next);
2670 return FALSE;
2673 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2674 return FALSE;
2676 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2679 dst_argv[argc] = strdup(buf);
2680 if (!dst_argv[argc])
2681 break;
2684 dst_argv[argc] = NULL;
2686 return src_argv[argc] == NULL;
2689 static bool
2690 restore_view_position(struct view *view)
2692 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2693 return FALSE;
2695 /* Changing the view position cancels the restoring. */
2696 /* FIXME: Changing back to the first line is not detected. */
2697 if (view->offset != 0 || view->lineno != 0) {
2698 view->p_restore = FALSE;
2699 return FALSE;
2702 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2703 view_is_displayed(view))
2704 werase(view->win);
2706 view->yoffset = view->p_yoffset;
2707 view->p_restore = FALSE;
2709 return TRUE;
2712 static void
2713 end_update(struct view *view, bool force)
2715 if (!view->pipe)
2716 return;
2717 while (!view->ops->read(view, NULL))
2718 if (!force)
2719 return;
2720 set_nonblocking_input(FALSE);
2721 if (force)
2722 kill_io(view->pipe);
2723 done_io(view->pipe);
2724 view->pipe = NULL;
2727 static void
2728 setup_update(struct view *view, const char *vid)
2730 set_nonblocking_input(TRUE);
2731 reset_view(view);
2732 string_copy_rev(view->vid, vid);
2733 view->pipe = &view->io;
2734 view->start_time = time(NULL);
2737 static bool
2738 prepare_update(struct view *view, const char *argv[], const char *dir,
2739 enum format_flags flags)
2741 if (view->pipe)
2742 end_update(view, TRUE);
2743 return init_io_rd(&view->io, argv, dir, flags);
2746 static bool
2747 prepare_update_file(struct view *view, const char *name)
2749 if (view->pipe)
2750 end_update(view, TRUE);
2751 return io_open(&view->io, name);
2754 static bool
2755 begin_update(struct view *view, bool refresh)
2757 if (view->pipe)
2758 end_update(view, TRUE);
2760 if (refresh) {
2761 if (!start_io(&view->io))
2762 return FALSE;
2764 } else {
2765 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2766 opt_path[0] = 0;
2768 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2769 return FALSE;
2771 /* Put the current ref_* value to the view title ref
2772 * member. This is needed by the blob view. Most other
2773 * views sets it automatically after loading because the
2774 * first line is a commit line. */
2775 string_copy_rev(view->ref, view->id);
2778 setup_update(view, view->id);
2780 return TRUE;
2783 #define ITEM_CHUNK_SIZE 256
2784 static void *
2785 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2787 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2788 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2790 if (mem == NULL || num_chunks != num_chunks_new) {
2791 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2792 mem = realloc(mem, *size * item_size);
2795 return mem;
2798 static struct line *
2799 realloc_lines(struct view *view, size_t line_size)
2801 size_t alloc = view->line_alloc;
2802 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2803 sizeof(*view->line));
2805 if (!tmp)
2806 return NULL;
2808 view->line = tmp;
2809 view->line_alloc = alloc;
2810 return view->line;
2813 static bool
2814 update_view(struct view *view)
2816 char out_buffer[BUFSIZ * 2];
2817 char *line;
2818 /* Clear the view and redraw everything since the tree sorting
2819 * might have rearranged things. */
2820 bool redraw = view->lines == 0;
2821 bool can_read = TRUE;
2823 if (!view->pipe)
2824 return TRUE;
2826 if (!io_can_read(view->pipe)) {
2827 if (view->lines == 0) {
2828 time_t secs = time(NULL) - view->start_time;
2830 if (secs > 1 && secs > view->update_secs) {
2831 if (view->update_secs == 0)
2832 redraw_view(view);
2833 update_view_title(view);
2834 view->update_secs = secs;
2837 return TRUE;
2840 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2841 if (opt_iconv != ICONV_NONE) {
2842 ICONV_CONST char *inbuf = line;
2843 size_t inlen = strlen(line) + 1;
2845 char *outbuf = out_buffer;
2846 size_t outlen = sizeof(out_buffer);
2848 size_t ret;
2850 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2851 if (ret != (size_t) -1)
2852 line = out_buffer;
2855 if (!view->ops->read(view, line)) {
2856 report("Allocation failure");
2857 end_update(view, TRUE);
2858 return FALSE;
2863 unsigned long lines = view->lines;
2864 int digits;
2866 for (digits = 0; lines; digits++)
2867 lines /= 10;
2869 /* Keep the displayed view in sync with line number scaling. */
2870 if (digits != view->digits) {
2871 view->digits = digits;
2872 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2873 redraw = TRUE;
2877 if (io_error(view->pipe)) {
2878 report("Failed to read: %s", io_strerror(view->pipe));
2879 end_update(view, TRUE);
2881 } else if (io_eof(view->pipe)) {
2882 report("");
2883 end_update(view, FALSE);
2886 if (restore_view_position(view))
2887 redraw = TRUE;
2889 if (!view_is_displayed(view))
2890 return TRUE;
2892 if (redraw)
2893 redraw_view_from(view, 0);
2894 else
2895 redraw_view_dirty(view);
2897 /* Update the title _after_ the redraw so that if the redraw picks up a
2898 * commit reference in view->ref it'll be available here. */
2899 update_view_title(view);
2900 return TRUE;
2903 static struct line *
2904 add_line_data(struct view *view, void *data, enum line_type type)
2906 struct line *line;
2908 if (!realloc_lines(view, view->lines + 1))
2909 return NULL;
2911 line = &view->line[view->lines++];
2912 memset(line, 0, sizeof(*line));
2913 line->type = type;
2914 line->data = data;
2915 line->dirty = 1;
2917 return line;
2920 static struct line *
2921 add_line_text(struct view *view, const char *text, enum line_type type)
2923 char *data = text ? strdup(text) : NULL;
2925 return data ? add_line_data(view, data, type) : NULL;
2928 static struct line *
2929 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2931 char buf[SIZEOF_STR];
2932 va_list args;
2934 va_start(args, fmt);
2935 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2936 buf[0] = 0;
2937 va_end(args);
2939 return buf[0] ? add_line_text(view, buf, type) : NULL;
2943 * View opening
2946 enum open_flags {
2947 OPEN_DEFAULT = 0, /* Use default view switching. */
2948 OPEN_SPLIT = 1, /* Split current view. */
2949 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2950 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2951 OPEN_PREPARED = 32, /* Open already prepared command. */
2954 static void
2955 open_view(struct view *prev, enum request request, enum open_flags flags)
2957 bool split = !!(flags & OPEN_SPLIT);
2958 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2959 bool nomaximize = !!(flags & OPEN_REFRESH);
2960 struct view *view = VIEW(request);
2961 int nviews = displayed_views();
2962 struct view *base_view = display[0];
2964 if (view == prev && nviews == 1 && !reload) {
2965 report("Already in %s view", view->name);
2966 return;
2969 if (view->git_dir && !opt_git_dir[0]) {
2970 report("The %s view is disabled in pager view", view->name);
2971 return;
2974 if (split) {
2975 display[1] = view;
2976 current_view = 1;
2977 } else if (!nomaximize) {
2978 /* Maximize the current view. */
2979 memset(display, 0, sizeof(display));
2980 current_view = 0;
2981 display[current_view] = view;
2984 /* Resize the view when switching between split- and full-screen,
2985 * or when switching between two different full-screen views. */
2986 if (nviews != displayed_views() ||
2987 (nviews == 1 && base_view != display[0]))
2988 resize_display();
2990 if (view->ops->open) {
2991 if (view->pipe)
2992 end_update(view, TRUE);
2993 if (!view->ops->open(view)) {
2994 report("Failed to load %s view", view->name);
2995 return;
2997 restore_view_position(view);
2999 } else if ((reload || strcmp(view->vid, view->id)) &&
3000 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3001 report("Failed to load %s view", view->name);
3002 return;
3005 if (split && prev->lineno - prev->offset >= prev->height) {
3006 /* Take the title line into account. */
3007 int lines = prev->lineno - prev->offset - prev->height + 1;
3009 /* Scroll the view that was split if the current line is
3010 * outside the new limited view. */
3011 do_scroll_view(prev, lines);
3014 if (prev && view != prev) {
3015 if (split) {
3016 /* "Blur" the previous view. */
3017 update_view_title(prev);
3020 view->parent = prev;
3023 if (view->pipe && view->lines == 0) {
3024 /* Clear the old view and let the incremental updating refill
3025 * the screen. */
3026 werase(view->win);
3027 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3028 report("");
3029 } else if (view_is_displayed(view)) {
3030 redraw_view(view);
3031 report("");
3035 static void
3036 open_external_viewer(const char *argv[], const char *dir)
3038 def_prog_mode(); /* save current tty modes */
3039 endwin(); /* restore original tty modes */
3040 run_io_fg(argv, dir);
3041 fprintf(stderr, "Press Enter to continue");
3042 getc(opt_tty);
3043 reset_prog_mode();
3044 redraw_display(TRUE);
3047 static void
3048 open_mergetool(const char *file)
3050 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3052 open_external_viewer(mergetool_argv, opt_cdup);
3055 static void
3056 open_editor(bool from_root, const char *file)
3058 const char *editor_argv[] = { "vi", file, NULL };
3059 const char *editor;
3061 editor = getenv("GIT_EDITOR");
3062 if (!editor && *opt_editor)
3063 editor = opt_editor;
3064 if (!editor)
3065 editor = getenv("VISUAL");
3066 if (!editor)
3067 editor = getenv("EDITOR");
3068 if (!editor)
3069 editor = "vi";
3071 editor_argv[0] = editor;
3072 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3075 static void
3076 open_run_request(enum request request)
3078 struct run_request *req = get_run_request(request);
3079 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3081 if (!req) {
3082 report("Unknown run request");
3083 return;
3086 if (format_argv(argv, req->argv, FORMAT_ALL))
3087 open_external_viewer(argv, NULL);
3088 free_argv(argv);
3092 * User request switch noodle
3095 static int
3096 view_driver(struct view *view, enum request request)
3098 int i;
3100 if (request == REQ_NONE) {
3101 doupdate();
3102 return TRUE;
3105 if (request > REQ_NONE) {
3106 open_run_request(request);
3107 /* FIXME: When all views can refresh always do this. */
3108 if (view == VIEW(REQ_VIEW_STATUS) ||
3109 view == VIEW(REQ_VIEW_MAIN) ||
3110 view == VIEW(REQ_VIEW_LOG) ||
3111 view == VIEW(REQ_VIEW_STAGE))
3112 request = REQ_REFRESH;
3113 else
3114 return TRUE;
3117 if (view && view->lines) {
3118 request = view->ops->request(view, request, &view->line[view->lineno]);
3119 if (request == REQ_NONE)
3120 return TRUE;
3123 switch (request) {
3124 case REQ_MOVE_UP:
3125 case REQ_MOVE_DOWN:
3126 case REQ_MOVE_PAGE_UP:
3127 case REQ_MOVE_PAGE_DOWN:
3128 case REQ_MOVE_FIRST_LINE:
3129 case REQ_MOVE_LAST_LINE:
3130 move_view(view, request);
3131 break;
3133 case REQ_SCROLL_LEFT:
3134 case REQ_SCROLL_RIGHT:
3135 case REQ_SCROLL_LINE_DOWN:
3136 case REQ_SCROLL_LINE_UP:
3137 case REQ_SCROLL_PAGE_DOWN:
3138 case REQ_SCROLL_PAGE_UP:
3139 scroll_view(view, request);
3140 break;
3142 case REQ_VIEW_BLAME:
3143 if (!opt_file[0]) {
3144 report("No file chosen, press %s to open tree view",
3145 get_key(REQ_VIEW_TREE));
3146 break;
3148 open_view(view, request, OPEN_DEFAULT);
3149 break;
3151 case REQ_VIEW_BLOB:
3152 if (!ref_blob[0]) {
3153 report("No file chosen, press %s to open tree view",
3154 get_key(REQ_VIEW_TREE));
3155 break;
3157 open_view(view, request, OPEN_DEFAULT);
3158 break;
3160 case REQ_VIEW_PAGER:
3161 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3162 report("No pager content, press %s to run command from prompt",
3163 get_key(REQ_PROMPT));
3164 break;
3166 open_view(view, request, OPEN_DEFAULT);
3167 break;
3169 case REQ_VIEW_STAGE:
3170 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3171 report("No stage content, press %s to open the status view and choose file",
3172 get_key(REQ_VIEW_STATUS));
3173 break;
3175 open_view(view, request, OPEN_DEFAULT);
3176 break;
3178 case REQ_VIEW_STATUS:
3179 if (opt_is_inside_work_tree == FALSE) {
3180 report("The status view requires a working tree");
3181 break;
3183 open_view(view, request, OPEN_DEFAULT);
3184 break;
3186 case REQ_VIEW_MAIN:
3187 case REQ_VIEW_DIFF:
3188 case REQ_VIEW_LOG:
3189 case REQ_VIEW_TREE:
3190 case REQ_VIEW_HELP:
3191 open_view(view, request, OPEN_DEFAULT);
3192 break;
3194 case REQ_NEXT:
3195 case REQ_PREVIOUS:
3196 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3198 if ((view == VIEW(REQ_VIEW_DIFF) &&
3199 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3200 (view == VIEW(REQ_VIEW_DIFF) &&
3201 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3202 (view == VIEW(REQ_VIEW_STAGE) &&
3203 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3204 (view == VIEW(REQ_VIEW_BLOB) &&
3205 view->parent == VIEW(REQ_VIEW_TREE))) {
3206 int line;
3208 view = view->parent;
3209 line = view->lineno;
3210 move_view(view, request);
3211 if (view_is_displayed(view))
3212 update_view_title(view);
3213 if (line != view->lineno)
3214 view->ops->request(view, REQ_ENTER,
3215 &view->line[view->lineno]);
3217 } else {
3218 move_view(view, request);
3220 break;
3222 case REQ_VIEW_NEXT:
3224 int nviews = displayed_views();
3225 int next_view = (current_view + 1) % nviews;
3227 if (next_view == current_view) {
3228 report("Only one view is displayed");
3229 break;
3232 current_view = next_view;
3233 /* Blur out the title of the previous view. */
3234 update_view_title(view);
3235 report("");
3236 break;
3238 case REQ_REFRESH:
3239 report("Refreshing is not yet supported for the %s view", view->name);
3240 break;
3242 case REQ_MAXIMIZE:
3243 if (displayed_views() == 2)
3244 maximize_view(view);
3245 break;
3247 case REQ_TOGGLE_LINENO:
3248 toggle_view_option(&opt_line_number, "line numbers");
3249 break;
3251 case REQ_TOGGLE_DATE:
3252 toggle_view_option(&opt_date, "date display");
3253 break;
3255 case REQ_TOGGLE_AUTHOR:
3256 toggle_view_option(&opt_author, "author display");
3257 break;
3259 case REQ_TOGGLE_REV_GRAPH:
3260 toggle_view_option(&opt_rev_graph, "revision graph display");
3261 break;
3263 case REQ_TOGGLE_REFS:
3264 toggle_view_option(&opt_show_refs, "reference display");
3265 break;
3267 case REQ_SEARCH:
3268 case REQ_SEARCH_BACK:
3269 search_view(view, request);
3270 break;
3272 case REQ_FIND_NEXT:
3273 case REQ_FIND_PREV:
3274 find_next(view, request);
3275 break;
3277 case REQ_STOP_LOADING:
3278 for (i = 0; i < ARRAY_SIZE(views); i++) {
3279 view = &views[i];
3280 if (view->pipe)
3281 report("Stopped loading the %s view", view->name),
3282 end_update(view, TRUE);
3284 break;
3286 case REQ_SHOW_VERSION:
3287 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3288 return TRUE;
3290 case REQ_SCREEN_REDRAW:
3291 redraw_display(TRUE);
3292 break;
3294 case REQ_EDIT:
3295 report("Nothing to edit");
3296 break;
3298 case REQ_ENTER:
3299 report("Nothing to enter");
3300 break;
3302 case REQ_VIEW_CLOSE:
3303 /* XXX: Mark closed views by letting view->parent point to the
3304 * view itself. Parents to closed view should never be
3305 * followed. */
3306 if (view->parent &&
3307 view->parent->parent != view->parent) {
3308 maximize_view(view->parent);
3309 view->parent = view;
3310 break;
3312 /* Fall-through */
3313 case REQ_QUIT:
3314 return FALSE;
3316 default:
3317 report("Unknown key, press 'h' for help");
3318 return TRUE;
3321 return TRUE;
3326 * View backend utilities
3329 static void
3330 parse_timezone(time_t *time, const char *zone)
3332 long tz;
3334 tz = ('0' - zone[1]) * 60 * 60 * 10;
3335 tz += ('0' - zone[2]) * 60 * 60;
3336 tz += ('0' - zone[3]) * 60;
3337 tz += ('0' - zone[4]);
3339 if (zone[0] == '-')
3340 tz = -tz;
3342 *time -= tz;
3345 /* Parse author lines where the name may be empty:
3346 * author <email@address.tld> 1138474660 +0100
3348 static void
3349 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3351 char *nameend = strchr(ident, '<');
3352 char *emailend = strchr(ident, '>');
3354 if (nameend && emailend)
3355 *nameend = *emailend = 0;
3356 ident = chomp_string(ident);
3357 if (!*ident) {
3358 if (nameend)
3359 ident = chomp_string(nameend + 1);
3360 if (!*ident)
3361 ident = "Unknown";
3364 string_ncopy_do(author, authorsize, ident, strlen(ident));
3366 /* Parse epoch and timezone */
3367 if (emailend && emailend[1] == ' ') {
3368 char *secs = emailend + 2;
3369 char *zone = strchr(secs, ' ');
3370 time_t time = (time_t) atol(secs);
3372 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3373 parse_timezone(&time, zone + 1);
3375 gmtime_r(&time, tm);
3379 static enum input_status
3380 select_commit_parent_handler(void *data, char *buf, int c)
3382 size_t parents = *(size_t *) data;
3383 int parent = 0;
3385 if (!isdigit(c))
3386 return INPUT_SKIP;
3388 if (*buf)
3389 parent = atoi(buf) * 10;
3390 parent += c - '0';
3392 if (parent > parents)
3393 return INPUT_SKIP;
3394 return INPUT_OK;
3397 static bool
3398 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3400 char buf[SIZEOF_STR * 4];
3401 const char *revlist_argv[] = {
3402 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3404 int parents;
3406 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3407 !*chomp_string(buf) ||
3408 (parents = (strlen(buf) / 40) - 1) < 0) {
3409 report("Failed to get parent information");
3410 return FALSE;
3412 } else if (parents == 0) {
3413 if (path)
3414 report("Path '%s' does not exist in the parent", path);
3415 else
3416 report("The selected commit has no parents");
3417 return FALSE;
3420 if (parents > 1) {
3421 char prompt[SIZEOF_STR];
3422 char *result;
3424 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3425 return FALSE;
3426 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3427 if (!result)
3428 return FALSE;
3429 parents = atoi(result);
3432 string_copy_rev(rev, &buf[41 * parents]);
3433 return TRUE;
3437 * Pager backend
3440 static bool
3441 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3443 char text[SIZEOF_STR];
3445 if (opt_line_number && draw_lineno(view, lineno))
3446 return TRUE;
3448 string_expand(text, sizeof(text), line->data, opt_tab_size);
3449 draw_text(view, line->type, text, TRUE);
3450 return TRUE;
3453 static bool
3454 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3456 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3457 char refbuf[SIZEOF_STR];
3458 char *ref = NULL;
3460 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3461 ref = chomp_string(refbuf);
3463 if (!ref || !*ref)
3464 return TRUE;
3466 /* This is the only fatal call, since it can "corrupt" the buffer. */
3467 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3468 return FALSE;
3470 return TRUE;
3473 static void
3474 add_pager_refs(struct view *view, struct line *line)
3476 char buf[SIZEOF_STR];
3477 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3478 struct ref **refs;
3479 size_t bufpos = 0, refpos = 0;
3480 const char *sep = "Refs: ";
3481 bool is_tag = FALSE;
3483 assert(line->type == LINE_COMMIT);
3485 refs = get_refs(commit_id);
3486 if (!refs) {
3487 if (view == VIEW(REQ_VIEW_DIFF))
3488 goto try_add_describe_ref;
3489 return;
3492 do {
3493 struct ref *ref = refs[refpos];
3494 const char *fmt = ref->tag ? "%s[%s]" :
3495 ref->remote ? "%s<%s>" : "%s%s";
3497 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3498 return;
3499 sep = ", ";
3500 if (ref->tag)
3501 is_tag = TRUE;
3502 } while (refs[refpos++]->next);
3504 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3505 try_add_describe_ref:
3506 /* Add <tag>-g<commit_id> "fake" reference. */
3507 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3508 return;
3511 if (bufpos == 0)
3512 return;
3514 add_line_text(view, buf, LINE_PP_REFS);
3517 static bool
3518 pager_read(struct view *view, char *data)
3520 struct line *line;
3522 if (!data)
3523 return TRUE;
3525 line = add_line_text(view, data, get_line_type(data));
3526 if (!line)
3527 return FALSE;
3529 if (line->type == LINE_COMMIT &&
3530 (view == VIEW(REQ_VIEW_DIFF) ||
3531 view == VIEW(REQ_VIEW_LOG)))
3532 add_pager_refs(view, line);
3534 return TRUE;
3537 static enum request
3538 pager_request(struct view *view, enum request request, struct line *line)
3540 int split = 0;
3542 if (request != REQ_ENTER)
3543 return request;
3545 if (line->type == LINE_COMMIT &&
3546 (view == VIEW(REQ_VIEW_LOG) ||
3547 view == VIEW(REQ_VIEW_PAGER))) {
3548 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3549 split = 1;
3552 /* Always scroll the view even if it was split. That way
3553 * you can use Enter to scroll through the log view and
3554 * split open each commit diff. */
3555 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3557 /* FIXME: A minor workaround. Scrolling the view will call report("")
3558 * but if we are scrolling a non-current view this won't properly
3559 * update the view title. */
3560 if (split)
3561 update_view_title(view);
3563 return REQ_NONE;
3566 static bool
3567 pager_grep(struct view *view, struct line *line)
3569 regmatch_t pmatch;
3570 char *text = line->data;
3572 if (!*text)
3573 return FALSE;
3575 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3576 return FALSE;
3578 return TRUE;
3581 static void
3582 pager_select(struct view *view, struct line *line)
3584 if (line->type == LINE_COMMIT) {
3585 char *text = (char *)line->data + STRING_SIZE("commit ");
3587 if (view != VIEW(REQ_VIEW_PAGER))
3588 string_copy_rev(view->ref, text);
3589 string_copy_rev(ref_commit, text);
3593 static struct view_ops pager_ops = {
3594 "line",
3595 NULL,
3596 NULL,
3597 pager_read,
3598 pager_draw,
3599 pager_request,
3600 pager_grep,
3601 pager_select,
3604 static const char *log_argv[SIZEOF_ARG] = {
3605 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3608 static enum request
3609 log_request(struct view *view, enum request request, struct line *line)
3611 switch (request) {
3612 case REQ_REFRESH:
3613 load_refs();
3614 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3615 return REQ_NONE;
3616 default:
3617 return pager_request(view, request, line);
3621 static struct view_ops log_ops = {
3622 "line",
3623 log_argv,
3624 NULL,
3625 pager_read,
3626 pager_draw,
3627 log_request,
3628 pager_grep,
3629 pager_select,
3632 static const char *diff_argv[SIZEOF_ARG] = {
3633 "git", "show", "--pretty=fuller", "--no-color", "--root",
3634 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3637 static struct view_ops diff_ops = {
3638 "line",
3639 diff_argv,
3640 NULL,
3641 pager_read,
3642 pager_draw,
3643 pager_request,
3644 pager_grep,
3645 pager_select,
3649 * Help backend
3652 static bool
3653 help_open(struct view *view)
3655 char buf[SIZEOF_STR];
3656 size_t bufpos;
3657 int i;
3659 if (view->lines > 0)
3660 return TRUE;
3662 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3664 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3665 const char *key;
3667 if (req_info[i].request == REQ_NONE)
3668 continue;
3670 if (!req_info[i].request) {
3671 add_line_text(view, "", LINE_DEFAULT);
3672 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3673 continue;
3676 key = get_key(req_info[i].request);
3677 if (!*key)
3678 key = "(no key defined)";
3680 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3681 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3682 if (buf[bufpos] == '_')
3683 buf[bufpos] = '-';
3686 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3687 key, buf, req_info[i].help);
3690 if (run_requests) {
3691 add_line_text(view, "", LINE_DEFAULT);
3692 add_line_text(view, "External commands:", LINE_DEFAULT);
3695 for (i = 0; i < run_requests; i++) {
3696 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3697 const char *key;
3698 int argc;
3700 if (!req)
3701 continue;
3703 key = get_key_name(req->key);
3704 if (!*key)
3705 key = "(no key defined)";
3707 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3708 if (!string_format_from(buf, &bufpos, "%s%s",
3709 argc ? " " : "", req->argv[argc]))
3710 return REQ_NONE;
3712 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3713 keymap_table[req->keymap].name, key, buf);
3716 return TRUE;
3719 static struct view_ops help_ops = {
3720 "line",
3721 NULL,
3722 help_open,
3723 NULL,
3724 pager_draw,
3725 pager_request,
3726 pager_grep,
3727 pager_select,
3732 * Tree backend
3735 struct tree_stack_entry {
3736 struct tree_stack_entry *prev; /* Entry below this in the stack */
3737 unsigned long lineno; /* Line number to restore */
3738 char *name; /* Position of name in opt_path */
3741 /* The top of the path stack. */
3742 static struct tree_stack_entry *tree_stack = NULL;
3743 unsigned long tree_lineno = 0;
3745 static void
3746 pop_tree_stack_entry(void)
3748 struct tree_stack_entry *entry = tree_stack;
3750 tree_lineno = entry->lineno;
3751 entry->name[0] = 0;
3752 tree_stack = entry->prev;
3753 free(entry);
3756 static void
3757 push_tree_stack_entry(const char *name, unsigned long lineno)
3759 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3760 size_t pathlen = strlen(opt_path);
3762 if (!entry)
3763 return;
3765 entry->prev = tree_stack;
3766 entry->name = opt_path + pathlen;
3767 tree_stack = entry;
3769 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3770 pop_tree_stack_entry();
3771 return;
3774 /* Move the current line to the first tree entry. */
3775 tree_lineno = 1;
3776 entry->lineno = lineno;
3779 /* Parse output from git-ls-tree(1):
3781 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3784 #define SIZEOF_TREE_ATTR \
3785 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3787 #define SIZEOF_TREE_MODE \
3788 STRING_SIZE("100644 ")
3790 #define TREE_ID_OFFSET \
3791 STRING_SIZE("100644 blob ")
3793 struct tree_entry {
3794 char id[SIZEOF_REV];
3795 mode_t mode;
3796 struct tm time; /* Date from the author ident. */
3797 char author[75]; /* Author of the commit. */
3798 char name[1];
3801 static const char *
3802 tree_path(struct line *line)
3804 return ((struct tree_entry *) line->data)->name;
3808 static int
3809 tree_compare_entry(struct line *line1, struct line *line2)
3811 if (line1->type != line2->type)
3812 return line1->type == LINE_TREE_DIR ? -1 : 1;
3813 return strcmp(tree_path(line1), tree_path(line2));
3816 static struct line *
3817 tree_entry(struct view *view, enum line_type type, const char *path,
3818 const char *mode, const char *id)
3820 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3821 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3823 if (!entry || !line) {
3824 free(entry);
3825 return NULL;
3828 strncpy(entry->name, path, strlen(path));
3829 if (mode)
3830 entry->mode = strtoul(mode, NULL, 8);
3831 if (id)
3832 string_copy_rev(entry->id, id);
3834 return line;
3837 static bool
3838 tree_read_date(struct view *view, char *text, bool *read_date)
3840 static char author_name[SIZEOF_STR];
3841 static struct tm author_time;
3843 if (!text && *read_date) {
3844 *read_date = FALSE;
3845 return TRUE;
3847 } else if (!text) {
3848 char *path = *opt_path ? opt_path : ".";
3849 /* Find next entry to process */
3850 const char *log_file[] = {
3851 "git", "log", "--no-color", "--pretty=raw",
3852 "--cc", "--raw", view->id, "--", path, NULL
3854 struct io io = {};
3856 if (!view->lines) {
3857 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3858 report("Tree is empty");
3859 return TRUE;
3862 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3863 report("Failed to load tree data");
3864 return TRUE;
3867 done_io(view->pipe);
3868 view->io = io;
3869 *read_date = TRUE;
3870 return FALSE;
3872 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3873 parse_author_line(text + STRING_SIZE("author "),
3874 author_name, sizeof(author_name), &author_time);
3876 } else if (*text == ':') {
3877 char *pos;
3878 size_t annotated = 1;
3879 size_t i;
3881 pos = strchr(text, '\t');
3882 if (!pos)
3883 return TRUE;
3884 text = pos + 1;
3885 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3886 text += strlen(opt_prefix);
3887 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3888 text += strlen(opt_path);
3889 pos = strchr(text, '/');
3890 if (pos)
3891 *pos = 0;
3893 for (i = 1; i < view->lines; i++) {
3894 struct line *line = &view->line[i];
3895 struct tree_entry *entry = line->data;
3897 annotated += !!*entry->author;
3898 if (*entry->author || strcmp(entry->name, text))
3899 continue;
3901 string_copy(entry->author, author_name);
3902 memcpy(&entry->time, &author_time, sizeof(entry->time));
3903 line->dirty = 1;
3904 break;
3907 if (annotated == view->lines)
3908 kill_io(view->pipe);
3910 return TRUE;
3913 static bool
3914 tree_read(struct view *view, char *text)
3916 static bool read_date = FALSE;
3917 struct tree_entry *data;
3918 struct line *entry, *line;
3919 enum line_type type;
3920 size_t textlen = text ? strlen(text) : 0;
3921 char *path = text + SIZEOF_TREE_ATTR;
3923 if (read_date || !text)
3924 return tree_read_date(view, text, &read_date);
3926 if (textlen <= SIZEOF_TREE_ATTR)
3927 return FALSE;
3928 if (view->lines == 0 &&
3929 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3930 return FALSE;
3932 /* Strip the path part ... */
3933 if (*opt_path) {
3934 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3935 size_t striplen = strlen(opt_path);
3937 if (pathlen > striplen)
3938 memmove(path, path + striplen,
3939 pathlen - striplen + 1);
3941 /* Insert "link" to parent directory. */
3942 if (view->lines == 1 &&
3943 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3944 return FALSE;
3947 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3948 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3949 if (!entry)
3950 return FALSE;
3951 data = entry->data;
3953 /* Skip "Directory ..." and ".." line. */
3954 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3955 if (tree_compare_entry(line, entry) <= 0)
3956 continue;
3958 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3960 line->data = data;
3961 line->type = type;
3962 for (; line <= entry; line++)
3963 line->dirty = line->cleareol = 1;
3964 return TRUE;
3967 if (tree_lineno > view->lineno) {
3968 view->lineno = tree_lineno;
3969 tree_lineno = 0;
3972 return TRUE;
3975 static bool
3976 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3978 struct tree_entry *entry = line->data;
3980 if (line->type == LINE_TREE_HEAD) {
3981 if (draw_text(view, line->type, "Directory path /", TRUE))
3982 return TRUE;
3983 } else {
3984 if (draw_mode(view, entry->mode))
3985 return TRUE;
3987 if (opt_author && draw_author(view, entry->author))
3988 return TRUE;
3990 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3991 return TRUE;
3993 if (draw_text(view, line->type, entry->name, TRUE))
3994 return TRUE;
3995 return TRUE;
3998 static void
3999 open_blob_editor()
4001 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4002 int fd = mkstemp(file);
4004 if (fd == -1)
4005 report("Failed to create temporary file");
4006 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4007 report("Failed to save blob data to file");
4008 else
4009 open_editor(FALSE, file);
4010 if (fd != -1)
4011 unlink(file);
4014 static enum request
4015 tree_request(struct view *view, enum request request, struct line *line)
4017 enum open_flags flags;
4019 switch (request) {
4020 case REQ_VIEW_BLAME:
4021 if (line->type != LINE_TREE_FILE) {
4022 report("Blame only supported for files");
4023 return REQ_NONE;
4026 string_copy(opt_ref, view->vid);
4027 return request;
4029 case REQ_EDIT:
4030 if (line->type != LINE_TREE_FILE) {
4031 report("Edit only supported for files");
4032 } else if (!is_head_commit(view->vid)) {
4033 open_blob_editor();
4034 } else {
4035 open_editor(TRUE, opt_file);
4037 return REQ_NONE;
4039 case REQ_PARENT:
4040 if (!*opt_path) {
4041 /* quit view if at top of tree */
4042 return REQ_VIEW_CLOSE;
4044 /* fake 'cd ..' */
4045 line = &view->line[1];
4046 break;
4048 case REQ_ENTER:
4049 break;
4051 default:
4052 return request;
4055 /* Cleanup the stack if the tree view is at a different tree. */
4056 while (!*opt_path && tree_stack)
4057 pop_tree_stack_entry();
4059 switch (line->type) {
4060 case LINE_TREE_DIR:
4061 /* Depending on whether it is a subdirectory or parent link
4062 * mangle the path buffer. */
4063 if (line == &view->line[1] && *opt_path) {
4064 pop_tree_stack_entry();
4066 } else {
4067 const char *basename = tree_path(line);
4069 push_tree_stack_entry(basename, view->lineno);
4072 /* Trees and subtrees share the same ID, so they are not not
4073 * unique like blobs. */
4074 flags = OPEN_RELOAD;
4075 request = REQ_VIEW_TREE;
4076 break;
4078 case LINE_TREE_FILE:
4079 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4080 request = REQ_VIEW_BLOB;
4081 break;
4083 default:
4084 return REQ_NONE;
4087 open_view(view, request, flags);
4088 if (request == REQ_VIEW_TREE)
4089 view->lineno = tree_lineno;
4091 return REQ_NONE;
4094 static void
4095 tree_select(struct view *view, struct line *line)
4097 struct tree_entry *entry = line->data;
4099 if (line->type == LINE_TREE_FILE) {
4100 string_copy_rev(ref_blob, entry->id);
4101 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4103 } else if (line->type != LINE_TREE_DIR) {
4104 return;
4107 string_copy_rev(view->ref, entry->id);
4110 static const char *tree_argv[SIZEOF_ARG] = {
4111 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4114 static struct view_ops tree_ops = {
4115 "file",
4116 tree_argv,
4117 NULL,
4118 tree_read,
4119 tree_draw,
4120 tree_request,
4121 pager_grep,
4122 tree_select,
4125 static bool
4126 blob_read(struct view *view, char *line)
4128 if (!line)
4129 return TRUE;
4130 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4133 static enum request
4134 blob_request(struct view *view, enum request request, struct line *line)
4136 switch (request) {
4137 case REQ_EDIT:
4138 open_blob_editor();
4139 return REQ_NONE;
4140 default:
4141 return pager_request(view, request, line);
4145 static const char *blob_argv[SIZEOF_ARG] = {
4146 "git", "cat-file", "blob", "%(blob)", NULL
4149 static struct view_ops blob_ops = {
4150 "line",
4151 blob_argv,
4152 NULL,
4153 blob_read,
4154 pager_draw,
4155 blob_request,
4156 pager_grep,
4157 pager_select,
4161 * Blame backend
4163 * Loading the blame view is a two phase job:
4165 * 1. File content is read either using opt_file from the
4166 * filesystem or using git-cat-file.
4167 * 2. Then blame information is incrementally added by
4168 * reading output from git-blame.
4171 static const char *blame_head_argv[] = {
4172 "git", "blame", "--incremental", "--", "%(file)", NULL
4175 static const char *blame_ref_argv[] = {
4176 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4179 static const char *blame_cat_file_argv[] = {
4180 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4183 struct blame_commit {
4184 char id[SIZEOF_REV]; /* SHA1 ID. */
4185 char title[128]; /* First line of the commit message. */
4186 char author[75]; /* Author of the commit. */
4187 struct tm time; /* Date from the author ident. */
4188 char filename[128]; /* Name of file. */
4189 bool has_previous; /* Was a "previous" line detected. */
4192 struct blame {
4193 struct blame_commit *commit;
4194 unsigned long lineno;
4195 char text[1];
4198 static bool
4199 blame_open(struct view *view)
4201 if (*opt_ref || !io_open(&view->io, opt_file)) {
4202 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4203 return FALSE;
4206 setup_update(view, opt_file);
4207 string_format(view->ref, "%s ...", opt_file);
4209 return TRUE;
4212 static struct blame_commit *
4213 get_blame_commit(struct view *view, const char *id)
4215 size_t i;
4217 for (i = 0; i < view->lines; i++) {
4218 struct blame *blame = view->line[i].data;
4220 if (!blame->commit)
4221 continue;
4223 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4224 return blame->commit;
4228 struct blame_commit *commit = calloc(1, sizeof(*commit));
4230 if (commit)
4231 string_ncopy(commit->id, id, SIZEOF_REV);
4232 return commit;
4236 static bool
4237 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4239 const char *pos = *posref;
4241 *posref = NULL;
4242 pos = strchr(pos + 1, ' ');
4243 if (!pos || !isdigit(pos[1]))
4244 return FALSE;
4245 *number = atoi(pos + 1);
4246 if (*number < min || *number > max)
4247 return FALSE;
4249 *posref = pos;
4250 return TRUE;
4253 static struct blame_commit *
4254 parse_blame_commit(struct view *view, const char *text, int *blamed)
4256 struct blame_commit *commit;
4257 struct blame *blame;
4258 const char *pos = text + SIZEOF_REV - 2;
4259 size_t orig_lineno = 0;
4260 size_t lineno;
4261 size_t group;
4263 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4264 return NULL;
4266 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4267 !parse_number(&pos, &lineno, 1, view->lines) ||
4268 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4269 return NULL;
4271 commit = get_blame_commit(view, text);
4272 if (!commit)
4273 return NULL;
4275 *blamed += group;
4276 while (group--) {
4277 struct line *line = &view->line[lineno + group - 1];
4279 blame = line->data;
4280 blame->commit = commit;
4281 blame->lineno = orig_lineno + group - 1;
4282 line->dirty = 1;
4285 return commit;
4288 static bool
4289 blame_read_file(struct view *view, const char *line, bool *read_file)
4291 if (!line) {
4292 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4293 struct io io = {};
4295 if (view->lines == 0 && !view->parent)
4296 die("No blame exist for %s", view->vid);
4298 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4299 report("Failed to load blame data");
4300 return TRUE;
4303 done_io(view->pipe);
4304 view->io = io;
4305 *read_file = FALSE;
4306 return FALSE;
4308 } else {
4309 size_t linelen = strlen(line);
4310 struct blame *blame = malloc(sizeof(*blame) + linelen);
4312 if (!blame)
4313 return FALSE;
4315 blame->commit = NULL;
4316 strncpy(blame->text, line, linelen);
4317 blame->text[linelen] = 0;
4318 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4322 static bool
4323 match_blame_header(const char *name, char **line)
4325 size_t namelen = strlen(name);
4326 bool matched = !strncmp(name, *line, namelen);
4328 if (matched)
4329 *line += namelen;
4331 return matched;
4334 static bool
4335 blame_read(struct view *view, char *line)
4337 static struct blame_commit *commit = NULL;
4338 static int blamed = 0;
4339 static time_t author_time;
4340 static bool read_file = TRUE;
4342 if (read_file)
4343 return blame_read_file(view, line, &read_file);
4345 if (!line) {
4346 /* Reset all! */
4347 commit = NULL;
4348 blamed = 0;
4349 read_file = TRUE;
4350 string_format(view->ref, "%s", view->vid);
4351 if (view_is_displayed(view)) {
4352 update_view_title(view);
4353 redraw_view_from(view, 0);
4355 return TRUE;
4358 if (!commit) {
4359 commit = parse_blame_commit(view, line, &blamed);
4360 string_format(view->ref, "%s %2d%%", view->vid,
4361 view->lines ? blamed * 100 / view->lines : 0);
4363 } else if (match_blame_header("author ", &line)) {
4364 string_ncopy(commit->author, line, strlen(line));
4366 } else if (match_blame_header("author-time ", &line)) {
4367 author_time = (time_t) atol(line);
4369 } else if (match_blame_header("author-tz ", &line)) {
4370 parse_timezone(&author_time, line);
4371 gmtime_r(&author_time, &commit->time);
4373 } else if (match_blame_header("summary ", &line)) {
4374 string_ncopy(commit->title, line, strlen(line));
4376 } else if (match_blame_header("previous ", &line)) {
4377 commit->has_previous = TRUE;
4379 } else if (match_blame_header("filename ", &line)) {
4380 string_ncopy(commit->filename, line, strlen(line));
4381 commit = NULL;
4384 return TRUE;
4387 static bool
4388 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4390 struct blame *blame = line->data;
4391 struct tm *time = NULL;
4392 const char *id = NULL, *author = NULL;
4393 char text[SIZEOF_STR];
4395 if (blame->commit && *blame->commit->filename) {
4396 id = blame->commit->id;
4397 author = blame->commit->author;
4398 time = &blame->commit->time;
4401 if (opt_date && draw_date(view, time))
4402 return TRUE;
4404 if (opt_author && draw_author(view, author))
4405 return TRUE;
4407 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4408 return TRUE;
4410 if (draw_lineno(view, lineno))
4411 return TRUE;
4413 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4414 draw_text(view, LINE_DEFAULT, text, TRUE);
4415 return TRUE;
4418 static bool
4419 check_blame_commit(struct blame *blame, bool check_null_id)
4421 if (!blame->commit)
4422 report("Commit data not loaded yet");
4423 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4424 report("No commit exist for the selected line");
4425 else
4426 return TRUE;
4427 return FALSE;
4430 static void
4431 setup_blame_parent_line(struct view *view, struct blame *blame)
4433 const char *diff_tree_argv[] = {
4434 "git", "diff-tree", "-U0", blame->commit->id,
4435 "--", blame->commit->filename, NULL
4437 struct io io = {};
4438 int parent_lineno = -1;
4439 int blamed_lineno = -1;
4440 char *line;
4442 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4443 return;
4445 while ((line = io_get(&io, '\n', TRUE))) {
4446 if (*line == '@') {
4447 char *pos = strchr(line, '+');
4449 parent_lineno = atoi(line + 4);
4450 if (pos)
4451 blamed_lineno = atoi(pos + 1);
4453 } else if (*line == '+' && parent_lineno != -1) {
4454 if (blame->lineno == blamed_lineno - 1 &&
4455 !strcmp(blame->text, line + 1)) {
4456 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4457 break;
4459 blamed_lineno++;
4463 done_io(&io);
4466 static enum request
4467 blame_request(struct view *view, enum request request, struct line *line)
4469 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4470 struct blame *blame = line->data;
4472 switch (request) {
4473 case REQ_VIEW_BLAME:
4474 if (check_blame_commit(blame, TRUE)) {
4475 string_copy(opt_ref, blame->commit->id);
4476 string_copy(opt_file, blame->commit->filename);
4477 if (blame->lineno)
4478 view->lineno = blame->lineno;
4479 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4481 break;
4483 case REQ_PARENT:
4484 if (check_blame_commit(blame, TRUE) &&
4485 select_commit_parent(blame->commit->id, opt_ref,
4486 blame->commit->filename)) {
4487 string_copy(opt_file, blame->commit->filename);
4488 setup_blame_parent_line(view, blame);
4489 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4491 break;
4493 case REQ_ENTER:
4494 if (!check_blame_commit(blame, FALSE))
4495 break;
4497 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4498 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4499 break;
4501 if (!strcmp(blame->commit->id, NULL_ID)) {
4502 struct view *diff = VIEW(REQ_VIEW_DIFF);
4503 const char *diff_index_argv[] = {
4504 "git", "diff-index", "--root", "--patch-with-stat",
4505 "-C", "-M", "HEAD", "--", view->vid, NULL
4508 if (!blame->commit->has_previous) {
4509 diff_index_argv[1] = "diff";
4510 diff_index_argv[2] = "--no-color";
4511 diff_index_argv[6] = "--";
4512 diff_index_argv[7] = "/dev/null";
4515 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4516 report("Failed to allocate diff command");
4517 break;
4519 flags |= OPEN_PREPARED;
4522 open_view(view, REQ_VIEW_DIFF, flags);
4523 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4524 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4525 break;
4527 default:
4528 return request;
4531 return REQ_NONE;
4534 static bool
4535 blame_grep(struct view *view, struct line *line)
4537 struct blame *blame = line->data;
4538 struct blame_commit *commit = blame->commit;
4539 regmatch_t pmatch;
4541 #define MATCH(text, on) \
4542 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4544 if (commit) {
4545 char buf[DATE_COLS + 1];
4547 if (MATCH(commit->title, 1) ||
4548 MATCH(commit->author, opt_author) ||
4549 MATCH(commit->id, opt_date))
4550 return TRUE;
4552 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4553 MATCH(buf, 1))
4554 return TRUE;
4557 return MATCH(blame->text, 1);
4559 #undef MATCH
4562 static void
4563 blame_select(struct view *view, struct line *line)
4565 struct blame *blame = line->data;
4566 struct blame_commit *commit = blame->commit;
4568 if (!commit)
4569 return;
4571 if (!strcmp(commit->id, NULL_ID))
4572 string_ncopy(ref_commit, "HEAD", 4);
4573 else
4574 string_copy_rev(ref_commit, commit->id);
4577 static struct view_ops blame_ops = {
4578 "line",
4579 NULL,
4580 blame_open,
4581 blame_read,
4582 blame_draw,
4583 blame_request,
4584 blame_grep,
4585 blame_select,
4589 * Status backend
4592 struct status {
4593 char status;
4594 struct {
4595 mode_t mode;
4596 char rev[SIZEOF_REV];
4597 char name[SIZEOF_STR];
4598 } old;
4599 struct {
4600 mode_t mode;
4601 char rev[SIZEOF_REV];
4602 char name[SIZEOF_STR];
4603 } new;
4606 static char status_onbranch[SIZEOF_STR];
4607 static struct status stage_status;
4608 static enum line_type stage_line_type;
4609 static size_t stage_chunks;
4610 static int *stage_chunk;
4612 /* This should work even for the "On branch" line. */
4613 static inline bool
4614 status_has_none(struct view *view, struct line *line)
4616 return line < view->line + view->lines && !line[1].data;
4619 /* Get fields from the diff line:
4620 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4622 static inline bool
4623 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4625 const char *old_mode = buf + 1;
4626 const char *new_mode = buf + 8;
4627 const char *old_rev = buf + 15;
4628 const char *new_rev = buf + 56;
4629 const char *status = buf + 97;
4631 if (bufsize < 98 ||
4632 old_mode[-1] != ':' ||
4633 new_mode[-1] != ' ' ||
4634 old_rev[-1] != ' ' ||
4635 new_rev[-1] != ' ' ||
4636 status[-1] != ' ')
4637 return FALSE;
4639 file->status = *status;
4641 string_copy_rev(file->old.rev, old_rev);
4642 string_copy_rev(file->new.rev, new_rev);
4644 file->old.mode = strtoul(old_mode, NULL, 8);
4645 file->new.mode = strtoul(new_mode, NULL, 8);
4647 file->old.name[0] = file->new.name[0] = 0;
4649 return TRUE;
4652 static bool
4653 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4655 struct status *unmerged = NULL;
4656 char *buf;
4657 struct io io = {};
4659 if (!run_io(&io, argv, NULL, IO_RD))
4660 return FALSE;
4662 add_line_data(view, NULL, type);
4664 while ((buf = io_get(&io, 0, TRUE))) {
4665 struct status *file = unmerged;
4667 if (!file) {
4668 file = calloc(1, sizeof(*file));
4669 if (!file || !add_line_data(view, file, type))
4670 goto error_out;
4673 /* Parse diff info part. */
4674 if (status) {
4675 file->status = status;
4676 if (status == 'A')
4677 string_copy(file->old.rev, NULL_ID);
4679 } else if (!file->status || file == unmerged) {
4680 if (!status_get_diff(file, buf, strlen(buf)))
4681 goto error_out;
4683 buf = io_get(&io, 0, TRUE);
4684 if (!buf)
4685 break;
4687 /* Collapse all modified entries that follow an
4688 * associated unmerged entry. */
4689 if (unmerged == file) {
4690 unmerged->status = 'U';
4691 unmerged = NULL;
4692 } else if (file->status == 'U') {
4693 unmerged = file;
4697 /* Grab the old name for rename/copy. */
4698 if (!*file->old.name &&
4699 (file->status == 'R' || file->status == 'C')) {
4700 string_ncopy(file->old.name, buf, strlen(buf));
4702 buf = io_get(&io, 0, TRUE);
4703 if (!buf)
4704 break;
4707 /* git-ls-files just delivers a NUL separated list of
4708 * file names similar to the second half of the
4709 * git-diff-* output. */
4710 string_ncopy(file->new.name, buf, strlen(buf));
4711 if (!*file->old.name)
4712 string_copy(file->old.name, file->new.name);
4713 file = NULL;
4716 if (io_error(&io)) {
4717 error_out:
4718 done_io(&io);
4719 return FALSE;
4722 if (!view->line[view->lines - 1].data)
4723 add_line_data(view, NULL, LINE_STAT_NONE);
4725 done_io(&io);
4726 return TRUE;
4729 /* Don't show unmerged entries in the staged section. */
4730 static const char *status_diff_index_argv[] = {
4731 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4732 "--cached", "-M", "HEAD", NULL
4735 static const char *status_diff_files_argv[] = {
4736 "git", "diff-files", "-z", NULL
4739 static const char *status_list_other_argv[] = {
4740 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4743 static const char *status_list_no_head_argv[] = {
4744 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4747 static const char *update_index_argv[] = {
4748 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4751 /* Restore the previous line number to stay in the context or select a
4752 * line with something that can be updated. */
4753 static void
4754 status_restore(struct view *view)
4756 if (view->p_lineno >= view->lines)
4757 view->p_lineno = view->lines - 1;
4758 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4759 view->p_lineno++;
4760 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4761 view->p_lineno--;
4763 /* If the above fails, always skip the "On branch" line. */
4764 if (view->p_lineno < view->lines)
4765 view->lineno = view->p_lineno;
4766 else
4767 view->lineno = 1;
4769 if (view->lineno < view->offset)
4770 view->offset = view->lineno;
4771 else if (view->offset + view->height <= view->lineno)
4772 view->offset = view->lineno - view->height + 1;
4774 view->p_restore = FALSE;
4777 static void
4778 status_update_onbranch(void)
4780 static const char *paths[][2] = {
4781 { "rebase-apply/rebasing", "Rebasing" },
4782 { "rebase-apply/applying", "Applying mailbox" },
4783 { "rebase-apply/", "Rebasing mailbox" },
4784 { "rebase-merge/interactive", "Interactive rebase" },
4785 { "rebase-merge/", "Rebase merge" },
4786 { "MERGE_HEAD", "Merging" },
4787 { "BISECT_LOG", "Bisecting" },
4788 { "HEAD", "On branch" },
4790 char buf[SIZEOF_STR];
4791 struct stat stat;
4792 int i;
4794 if (is_initial_commit()) {
4795 string_copy(status_onbranch, "Initial commit");
4796 return;
4799 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4800 char *head = opt_head;
4802 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4803 lstat(buf, &stat) < 0)
4804 continue;
4806 if (!*opt_head) {
4807 struct io io = {};
4809 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4810 io_open(&io, buf) &&
4811 io_read_buf(&io, buf, sizeof(buf))) {
4812 head = chomp_string(buf);
4813 if (!prefixcmp(head, "refs/heads/"))
4814 head += STRING_SIZE("refs/heads/");
4818 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4819 string_copy(status_onbranch, opt_head);
4820 return;
4823 string_copy(status_onbranch, "Not currently on any branch");
4826 /* First parse staged info using git-diff-index(1), then parse unstaged
4827 * info using git-diff-files(1), and finally untracked files using
4828 * git-ls-files(1). */
4829 static bool
4830 status_open(struct view *view)
4832 reset_view(view);
4834 add_line_data(view, NULL, LINE_STAT_HEAD);
4835 status_update_onbranch();
4837 run_io_bg(update_index_argv);
4839 if (is_initial_commit()) {
4840 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4841 return FALSE;
4842 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4843 return FALSE;
4846 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4847 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4848 return FALSE;
4850 /* Restore the exact position or use the specialized restore
4851 * mode? */
4852 if (!view->p_restore)
4853 status_restore(view);
4854 return TRUE;
4857 static bool
4858 status_draw(struct view *view, struct line *line, unsigned int lineno)
4860 struct status *status = line->data;
4861 enum line_type type;
4862 const char *text;
4864 if (!status) {
4865 switch (line->type) {
4866 case LINE_STAT_STAGED:
4867 type = LINE_STAT_SECTION;
4868 text = "Changes to be committed:";
4869 break;
4871 case LINE_STAT_UNSTAGED:
4872 type = LINE_STAT_SECTION;
4873 text = "Changed but not updated:";
4874 break;
4876 case LINE_STAT_UNTRACKED:
4877 type = LINE_STAT_SECTION;
4878 text = "Untracked files:";
4879 break;
4881 case LINE_STAT_NONE:
4882 type = LINE_DEFAULT;
4883 text = " (no files)";
4884 break;
4886 case LINE_STAT_HEAD:
4887 type = LINE_STAT_HEAD;
4888 text = status_onbranch;
4889 break;
4891 default:
4892 return FALSE;
4894 } else {
4895 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4897 buf[0] = status->status;
4898 if (draw_text(view, line->type, buf, TRUE))
4899 return TRUE;
4900 type = LINE_DEFAULT;
4901 text = status->new.name;
4904 draw_text(view, type, text, TRUE);
4905 return TRUE;
4908 static enum request
4909 status_load_error(struct view *view, struct view *stage, const char *path)
4911 if (displayed_views() == 2 || display[current_view] != view)
4912 maximize_view(view);
4913 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4914 return REQ_NONE;
4917 static enum request
4918 status_enter(struct view *view, struct line *line)
4920 struct status *status = line->data;
4921 const char *oldpath = status ? status->old.name : NULL;
4922 /* Diffs for unmerged entries are empty when passing the new
4923 * path, so leave it empty. */
4924 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4925 const char *info;
4926 enum open_flags split;
4927 struct view *stage = VIEW(REQ_VIEW_STAGE);
4929 if (line->type == LINE_STAT_NONE ||
4930 (!status && line[1].type == LINE_STAT_NONE)) {
4931 report("No file to diff");
4932 return REQ_NONE;
4935 switch (line->type) {
4936 case LINE_STAT_STAGED:
4937 if (is_initial_commit()) {
4938 const char *no_head_diff_argv[] = {
4939 "git", "diff", "--no-color", "--patch-with-stat",
4940 "--", "/dev/null", newpath, NULL
4943 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4944 return status_load_error(view, stage, newpath);
4945 } else {
4946 const char *index_show_argv[] = {
4947 "git", "diff-index", "--root", "--patch-with-stat",
4948 "-C", "-M", "--cached", "HEAD", "--",
4949 oldpath, newpath, NULL
4952 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4953 return status_load_error(view, stage, newpath);
4956 if (status)
4957 info = "Staged changes to %s";
4958 else
4959 info = "Staged changes";
4960 break;
4962 case LINE_STAT_UNSTAGED:
4964 const char *files_show_argv[] = {
4965 "git", "diff-files", "--root", "--patch-with-stat",
4966 "-C", "-M", "--", oldpath, newpath, NULL
4969 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4970 return status_load_error(view, stage, newpath);
4971 if (status)
4972 info = "Unstaged changes to %s";
4973 else
4974 info = "Unstaged changes";
4975 break;
4977 case LINE_STAT_UNTRACKED:
4978 if (!newpath) {
4979 report("No file to show");
4980 return REQ_NONE;
4983 if (!suffixcmp(status->new.name, -1, "/")) {
4984 report("Cannot display a directory");
4985 return REQ_NONE;
4988 if (!prepare_update_file(stage, newpath))
4989 return status_load_error(view, stage, newpath);
4990 info = "Untracked file %s";
4991 break;
4993 case LINE_STAT_HEAD:
4994 return REQ_NONE;
4996 default:
4997 die("line type %d not handled in switch", line->type);
5000 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5001 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5002 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5003 if (status) {
5004 stage_status = *status;
5005 } else {
5006 memset(&stage_status, 0, sizeof(stage_status));
5009 stage_line_type = line->type;
5010 stage_chunks = 0;
5011 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5014 return REQ_NONE;
5017 static bool
5018 status_exists(struct status *status, enum line_type type)
5020 struct view *view = VIEW(REQ_VIEW_STATUS);
5021 unsigned long lineno;
5023 for (lineno = 0; lineno < view->lines; lineno++) {
5024 struct line *line = &view->line[lineno];
5025 struct status *pos = line->data;
5027 if (line->type != type)
5028 continue;
5029 if (!pos && (!status || !status->status) && line[1].data) {
5030 select_view_line(view, lineno);
5031 return TRUE;
5033 if (pos && !strcmp(status->new.name, pos->new.name)) {
5034 select_view_line(view, lineno);
5035 return TRUE;
5039 return FALSE;
5043 static bool
5044 status_update_prepare(struct io *io, enum line_type type)
5046 const char *staged_argv[] = {
5047 "git", "update-index", "-z", "--index-info", NULL
5049 const char *others_argv[] = {
5050 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5053 switch (type) {
5054 case LINE_STAT_STAGED:
5055 return run_io(io, staged_argv, opt_cdup, IO_WR);
5057 case LINE_STAT_UNSTAGED:
5058 return run_io(io, others_argv, opt_cdup, IO_WR);
5060 case LINE_STAT_UNTRACKED:
5061 return run_io(io, others_argv, NULL, IO_WR);
5063 default:
5064 die("line type %d not handled in switch", type);
5065 return FALSE;
5069 static bool
5070 status_update_write(struct io *io, struct status *status, enum line_type type)
5072 char buf[SIZEOF_STR];
5073 size_t bufsize = 0;
5075 switch (type) {
5076 case LINE_STAT_STAGED:
5077 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5078 status->old.mode,
5079 status->old.rev,
5080 status->old.name, 0))
5081 return FALSE;
5082 break;
5084 case LINE_STAT_UNSTAGED:
5085 case LINE_STAT_UNTRACKED:
5086 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5087 return FALSE;
5088 break;
5090 default:
5091 die("line type %d not handled in switch", type);
5094 return io_write(io, buf, bufsize);
5097 static bool
5098 status_update_file(struct status *status, enum line_type type)
5100 struct io io = {};
5101 bool result;
5103 if (!status_update_prepare(&io, type))
5104 return FALSE;
5106 result = status_update_write(&io, status, type);
5107 return done_io(&io) && result;
5110 static bool
5111 status_update_files(struct view *view, struct line *line)
5113 char buf[sizeof(view->ref)];
5114 struct io io = {};
5115 bool result = TRUE;
5116 struct line *pos = view->line + view->lines;
5117 int files = 0;
5118 int file, done;
5120 if (!status_update_prepare(&io, line->type))
5121 return FALSE;
5123 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5124 files++;
5126 string_copy(buf, view->ref);
5127 for (file = 0, done = 5; result && file < files; line++, file++) {
5128 int almost_done = file * 100 / files;
5130 if (almost_done > done) {
5131 done = almost_done;
5132 string_format(view->ref, "updating file %u of %u (%d%% done)",
5133 file, files, done);
5134 update_view_title(view);
5135 doupdate();
5137 result = status_update_write(&io, line->data, line->type);
5139 string_copy(view->ref, buf);
5141 return done_io(&io) && result;
5144 static bool
5145 status_update(struct view *view)
5147 struct line *line = &view->line[view->lineno];
5149 assert(view->lines);
5151 if (!line->data) {
5152 /* This should work even for the "On branch" line. */
5153 if (line < view->line + view->lines && !line[1].data) {
5154 report("Nothing to update");
5155 return FALSE;
5158 if (!status_update_files(view, line + 1)) {
5159 report("Failed to update file status");
5160 return FALSE;
5163 } else if (!status_update_file(line->data, line->type)) {
5164 report("Failed to update file status");
5165 return FALSE;
5168 return TRUE;
5171 static bool
5172 status_revert(struct status *status, enum line_type type, bool has_none)
5174 if (!status || type != LINE_STAT_UNSTAGED) {
5175 if (type == LINE_STAT_STAGED) {
5176 report("Cannot revert changes to staged files");
5177 } else if (type == LINE_STAT_UNTRACKED) {
5178 report("Cannot revert changes to untracked files");
5179 } else if (has_none) {
5180 report("Nothing to revert");
5181 } else {
5182 report("Cannot revert changes to multiple files");
5184 return FALSE;
5186 } else {
5187 char mode[10] = "100644";
5188 const char *reset_argv[] = {
5189 "git", "update-index", "--cacheinfo", mode,
5190 status->old.rev, status->old.name, NULL
5192 const char *checkout_argv[] = {
5193 "git", "checkout", "--", status->old.name, NULL
5196 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5197 return FALSE;
5198 string_format(mode, "%o", status->old.mode);
5199 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5200 run_io_fg(checkout_argv, opt_cdup);
5204 static enum request
5205 status_request(struct view *view, enum request request, struct line *line)
5207 struct status *status = line->data;
5209 switch (request) {
5210 case REQ_STATUS_UPDATE:
5211 if (!status_update(view))
5212 return REQ_NONE;
5213 break;
5215 case REQ_STATUS_REVERT:
5216 if (!status_revert(status, line->type, status_has_none(view, line)))
5217 return REQ_NONE;
5218 break;
5220 case REQ_STATUS_MERGE:
5221 if (!status || status->status != 'U') {
5222 report("Merging only possible for files with unmerged status ('U').");
5223 return REQ_NONE;
5225 open_mergetool(status->new.name);
5226 break;
5228 case REQ_EDIT:
5229 if (!status)
5230 return request;
5231 if (status->status == 'D') {
5232 report("File has been deleted.");
5233 return REQ_NONE;
5236 open_editor(status->status != '?', status->new.name);
5237 break;
5239 case REQ_VIEW_BLAME:
5240 if (status) {
5241 string_copy(opt_file, status->new.name);
5242 opt_ref[0] = 0;
5244 return request;
5246 case REQ_ENTER:
5247 /* After returning the status view has been split to
5248 * show the stage view. No further reloading is
5249 * necessary. */
5250 return status_enter(view, line);
5252 case REQ_REFRESH:
5253 /* Simply reload the view. */
5254 break;
5256 default:
5257 return request;
5260 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5262 return REQ_NONE;
5265 static void
5266 status_select(struct view *view, struct line *line)
5268 struct status *status = line->data;
5269 char file[SIZEOF_STR] = "all files";
5270 const char *text;
5271 const char *key;
5273 if (status && !string_format(file, "'%s'", status->new.name))
5274 return;
5276 if (!status && line[1].type == LINE_STAT_NONE)
5277 line++;
5279 switch (line->type) {
5280 case LINE_STAT_STAGED:
5281 text = "Press %s to unstage %s for commit";
5282 break;
5284 case LINE_STAT_UNSTAGED:
5285 text = "Press %s to stage %s for commit";
5286 break;
5288 case LINE_STAT_UNTRACKED:
5289 text = "Press %s to stage %s for addition";
5290 break;
5292 case LINE_STAT_HEAD:
5293 case LINE_STAT_NONE:
5294 text = "Nothing to update";
5295 break;
5297 default:
5298 die("line type %d not handled in switch", line->type);
5301 if (status && status->status == 'U') {
5302 text = "Press %s to resolve conflict in %s";
5303 key = get_key(REQ_STATUS_MERGE);
5305 } else {
5306 key = get_key(REQ_STATUS_UPDATE);
5309 string_format(view->ref, text, key, file);
5312 static bool
5313 status_grep(struct view *view, struct line *line)
5315 struct status *status = line->data;
5316 enum { S_STATUS, S_NAME, S_END } state;
5317 char buf[2] = "?";
5318 regmatch_t pmatch;
5320 if (!status)
5321 return FALSE;
5323 for (state = S_STATUS; state < S_END; state++) {
5324 const char *text;
5326 switch (state) {
5327 case S_NAME: text = status->new.name; break;
5328 case S_STATUS:
5329 buf[0] = status->status;
5330 text = buf;
5331 break;
5333 default:
5334 return FALSE;
5337 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5338 return TRUE;
5341 return FALSE;
5344 static struct view_ops status_ops = {
5345 "file",
5346 NULL,
5347 status_open,
5348 NULL,
5349 status_draw,
5350 status_request,
5351 status_grep,
5352 status_select,
5356 static bool
5357 stage_diff_write(struct io *io, struct line *line, struct line *end)
5359 while (line < end) {
5360 if (!io_write(io, line->data, strlen(line->data)) ||
5361 !io_write(io, "\n", 1))
5362 return FALSE;
5363 line++;
5364 if (line->type == LINE_DIFF_CHUNK ||
5365 line->type == LINE_DIFF_HEADER)
5366 break;
5369 return TRUE;
5372 static struct line *
5373 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5375 for (; view->line < line; line--)
5376 if (line->type == type)
5377 return line;
5379 return NULL;
5382 static bool
5383 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5385 const char *apply_argv[SIZEOF_ARG] = {
5386 "git", "apply", "--whitespace=nowarn", NULL
5388 struct line *diff_hdr;
5389 struct io io = {};
5390 int argc = 3;
5392 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5393 if (!diff_hdr)
5394 return FALSE;
5396 if (!revert)
5397 apply_argv[argc++] = "--cached";
5398 if (revert || stage_line_type == LINE_STAT_STAGED)
5399 apply_argv[argc++] = "-R";
5400 apply_argv[argc++] = "-";
5401 apply_argv[argc++] = NULL;
5402 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5403 return FALSE;
5405 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5406 !stage_diff_write(&io, chunk, view->line + view->lines))
5407 chunk = NULL;
5409 done_io(&io);
5410 run_io_bg(update_index_argv);
5412 return chunk ? TRUE : FALSE;
5415 static bool
5416 stage_update(struct view *view, struct line *line)
5418 struct line *chunk = NULL;
5420 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5421 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5423 if (chunk) {
5424 if (!stage_apply_chunk(view, chunk, FALSE)) {
5425 report("Failed to apply chunk");
5426 return FALSE;
5429 } else if (!stage_status.status) {
5430 view = VIEW(REQ_VIEW_STATUS);
5432 for (line = view->line; line < view->line + view->lines; line++)
5433 if (line->type == stage_line_type)
5434 break;
5436 if (!status_update_files(view, line + 1)) {
5437 report("Failed to update files");
5438 return FALSE;
5441 } else if (!status_update_file(&stage_status, stage_line_type)) {
5442 report("Failed to update file");
5443 return FALSE;
5446 return TRUE;
5449 static bool
5450 stage_revert(struct view *view, struct line *line)
5452 struct line *chunk = NULL;
5454 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5455 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5457 if (chunk) {
5458 if (!prompt_yesno("Are you sure you want to revert changes?"))
5459 return FALSE;
5461 if (!stage_apply_chunk(view, chunk, TRUE)) {
5462 report("Failed to revert chunk");
5463 return FALSE;
5465 return TRUE;
5467 } else {
5468 return status_revert(stage_status.status ? &stage_status : NULL,
5469 stage_line_type, FALSE);
5474 static void
5475 stage_next(struct view *view, struct line *line)
5477 int i;
5479 if (!stage_chunks) {
5480 static size_t alloc = 0;
5481 int *tmp;
5483 for (line = view->line; line < view->line + view->lines; line++) {
5484 if (line->type != LINE_DIFF_CHUNK)
5485 continue;
5487 tmp = realloc_items(stage_chunk, &alloc,
5488 stage_chunks, sizeof(*tmp));
5489 if (!tmp) {
5490 report("Allocation failure");
5491 return;
5494 stage_chunk = tmp;
5495 stage_chunk[stage_chunks++] = line - view->line;
5499 for (i = 0; i < stage_chunks; i++) {
5500 if (stage_chunk[i] > view->lineno) {
5501 do_scroll_view(view, stage_chunk[i] - view->lineno);
5502 report("Chunk %d of %d", i + 1, stage_chunks);
5503 return;
5507 report("No next chunk found");
5510 static enum request
5511 stage_request(struct view *view, enum request request, struct line *line)
5513 switch (request) {
5514 case REQ_STATUS_UPDATE:
5515 if (!stage_update(view, line))
5516 return REQ_NONE;
5517 break;
5519 case REQ_STATUS_REVERT:
5520 if (!stage_revert(view, line))
5521 return REQ_NONE;
5522 break;
5524 case REQ_STAGE_NEXT:
5525 if (stage_line_type == LINE_STAT_UNTRACKED) {
5526 report("File is untracked; press %s to add",
5527 get_key(REQ_STATUS_UPDATE));
5528 return REQ_NONE;
5530 stage_next(view, line);
5531 return REQ_NONE;
5533 case REQ_EDIT:
5534 if (!stage_status.new.name[0])
5535 return request;
5536 if (stage_status.status == 'D') {
5537 report("File has been deleted.");
5538 return REQ_NONE;
5541 open_editor(stage_status.status != '?', stage_status.new.name);
5542 break;
5544 case REQ_REFRESH:
5545 /* Reload everything ... */
5546 break;
5548 case REQ_VIEW_BLAME:
5549 if (stage_status.new.name[0]) {
5550 string_copy(opt_file, stage_status.new.name);
5551 opt_ref[0] = 0;
5553 return request;
5555 case REQ_ENTER:
5556 return pager_request(view, request, line);
5558 default:
5559 return request;
5562 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5563 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5565 /* Check whether the staged entry still exists, and close the
5566 * stage view if it doesn't. */
5567 if (!status_exists(&stage_status, stage_line_type)) {
5568 status_restore(VIEW(REQ_VIEW_STATUS));
5569 return REQ_VIEW_CLOSE;
5572 if (stage_line_type == LINE_STAT_UNTRACKED) {
5573 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5574 report("Cannot display a directory");
5575 return REQ_NONE;
5578 if (!prepare_update_file(view, stage_status.new.name)) {
5579 report("Failed to open file: %s", strerror(errno));
5580 return REQ_NONE;
5583 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5585 return REQ_NONE;
5588 static struct view_ops stage_ops = {
5589 "line",
5590 NULL,
5591 NULL,
5592 pager_read,
5593 pager_draw,
5594 stage_request,
5595 pager_grep,
5596 pager_select,
5601 * Revision graph
5604 struct commit {
5605 char id[SIZEOF_REV]; /* SHA1 ID. */
5606 char title[128]; /* First line of the commit message. */
5607 char author[75]; /* Author of the commit. */
5608 struct tm time; /* Date from the author ident. */
5609 struct ref **refs; /* Repository references. */
5610 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5611 size_t graph_size; /* The width of the graph array. */
5612 bool has_parents; /* Rewritten --parents seen. */
5615 /* Size of rev graph with no "padding" columns */
5616 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5618 struct rev_graph {
5619 struct rev_graph *prev, *next, *parents;
5620 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5621 size_t size;
5622 struct commit *commit;
5623 size_t pos;
5624 unsigned int boundary:1;
5627 /* Parents of the commit being visualized. */
5628 static struct rev_graph graph_parents[4];
5630 /* The current stack of revisions on the graph. */
5631 static struct rev_graph graph_stacks[4] = {
5632 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5633 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5634 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5635 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5638 static inline bool
5639 graph_parent_is_merge(struct rev_graph *graph)
5641 return graph->parents->size > 1;
5644 static inline void
5645 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5647 struct commit *commit = graph->commit;
5649 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5650 commit->graph[commit->graph_size++] = symbol;
5653 static void
5654 clear_rev_graph(struct rev_graph *graph)
5656 graph->boundary = 0;
5657 graph->size = graph->pos = 0;
5658 graph->commit = NULL;
5659 memset(graph->parents, 0, sizeof(*graph->parents));
5662 static void
5663 done_rev_graph(struct rev_graph *graph)
5665 if (graph_parent_is_merge(graph) &&
5666 graph->pos < graph->size - 1 &&
5667 graph->next->size == graph->size + graph->parents->size - 1) {
5668 size_t i = graph->pos + graph->parents->size - 1;
5670 graph->commit->graph_size = i * 2;
5671 while (i < graph->next->size - 1) {
5672 append_to_rev_graph(graph, ' ');
5673 append_to_rev_graph(graph, '\\');
5674 i++;
5678 clear_rev_graph(graph);
5681 static void
5682 push_rev_graph(struct rev_graph *graph, const char *parent)
5684 int i;
5686 /* "Collapse" duplicate parents lines.
5688 * FIXME: This needs to also update update the drawn graph but
5689 * for now it just serves as a method for pruning graph lines. */
5690 for (i = 0; i < graph->size; i++)
5691 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5692 return;
5694 if (graph->size < SIZEOF_REVITEMS) {
5695 string_copy_rev(graph->rev[graph->size++], parent);
5699 static chtype
5700 get_rev_graph_symbol(struct rev_graph *graph)
5702 chtype symbol;
5704 if (graph->boundary)
5705 symbol = REVGRAPH_BOUND;
5706 else if (graph->parents->size == 0)
5707 symbol = REVGRAPH_INIT;
5708 else if (graph_parent_is_merge(graph))
5709 symbol = REVGRAPH_MERGE;
5710 else if (graph->pos >= graph->size)
5711 symbol = REVGRAPH_BRANCH;
5712 else
5713 symbol = REVGRAPH_COMMIT;
5715 return symbol;
5718 static void
5719 draw_rev_graph(struct rev_graph *graph)
5721 struct rev_filler {
5722 chtype separator, line;
5724 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5725 static struct rev_filler fillers[] = {
5726 { ' ', '|' },
5727 { '`', '.' },
5728 { '\'', ' ' },
5729 { '/', ' ' },
5731 chtype symbol = get_rev_graph_symbol(graph);
5732 struct rev_filler *filler;
5733 size_t i;
5735 if (opt_line_graphics)
5736 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5738 filler = &fillers[DEFAULT];
5740 for (i = 0; i < graph->pos; i++) {
5741 append_to_rev_graph(graph, filler->line);
5742 if (graph_parent_is_merge(graph->prev) &&
5743 graph->prev->pos == i)
5744 filler = &fillers[RSHARP];
5746 append_to_rev_graph(graph, filler->separator);
5749 /* Place the symbol for this revision. */
5750 append_to_rev_graph(graph, symbol);
5752 if (graph->prev->size > graph->size)
5753 filler = &fillers[RDIAG];
5754 else
5755 filler = &fillers[DEFAULT];
5757 i++;
5759 for (; i < graph->size; i++) {
5760 append_to_rev_graph(graph, filler->separator);
5761 append_to_rev_graph(graph, filler->line);
5762 if (graph_parent_is_merge(graph->prev) &&
5763 i < graph->prev->pos + graph->parents->size)
5764 filler = &fillers[RSHARP];
5765 if (graph->prev->size > graph->size)
5766 filler = &fillers[LDIAG];
5769 if (graph->prev->size > graph->size) {
5770 append_to_rev_graph(graph, filler->separator);
5771 if (filler->line != ' ')
5772 append_to_rev_graph(graph, filler->line);
5776 /* Prepare the next rev graph */
5777 static void
5778 prepare_rev_graph(struct rev_graph *graph)
5780 size_t i;
5782 /* First, traverse all lines of revisions up to the active one. */
5783 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5784 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5785 break;
5787 push_rev_graph(graph->next, graph->rev[graph->pos]);
5790 /* Interleave the new revision parent(s). */
5791 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5792 push_rev_graph(graph->next, graph->parents->rev[i]);
5794 /* Lastly, put any remaining revisions. */
5795 for (i = graph->pos + 1; i < graph->size; i++)
5796 push_rev_graph(graph->next, graph->rev[i]);
5799 static void
5800 update_rev_graph(struct view *view, struct rev_graph *graph)
5802 /* If this is the finalizing update ... */
5803 if (graph->commit)
5804 prepare_rev_graph(graph);
5806 /* Graph visualization needs a one rev look-ahead,
5807 * so the first update doesn't visualize anything. */
5808 if (!graph->prev->commit)
5809 return;
5811 if (view->lines > 2)
5812 view->line[view->lines - 3].dirty = 1;
5813 if (view->lines > 1)
5814 view->line[view->lines - 2].dirty = 1;
5815 draw_rev_graph(graph->prev);
5816 done_rev_graph(graph->prev->prev);
5821 * Main view backend
5824 static const char *main_argv[SIZEOF_ARG] = {
5825 "git", "log", "--no-color", "--pretty=raw", "--parents",
5826 "--topo-order", "%(head)", NULL
5829 static bool
5830 main_draw(struct view *view, struct line *line, unsigned int lineno)
5832 struct commit *commit = line->data;
5834 if (!*commit->author)
5835 return FALSE;
5837 if (opt_date && draw_date(view, &commit->time))
5838 return TRUE;
5840 if (opt_author && draw_author(view, commit->author))
5841 return TRUE;
5843 if (opt_rev_graph && commit->graph_size &&
5844 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5845 return TRUE;
5847 if (opt_show_refs && commit->refs) {
5848 size_t i = 0;
5850 do {
5851 enum line_type type;
5853 if (commit->refs[i]->head)
5854 type = LINE_MAIN_HEAD;
5855 else if (commit->refs[i]->ltag)
5856 type = LINE_MAIN_LOCAL_TAG;
5857 else if (commit->refs[i]->tag)
5858 type = LINE_MAIN_TAG;
5859 else if (commit->refs[i]->tracked)
5860 type = LINE_MAIN_TRACKED;
5861 else if (commit->refs[i]->remote)
5862 type = LINE_MAIN_REMOTE;
5863 else
5864 type = LINE_MAIN_REF;
5866 if (draw_text(view, type, "[", TRUE) ||
5867 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5868 draw_text(view, type, "]", TRUE))
5869 return TRUE;
5871 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5872 return TRUE;
5873 } while (commit->refs[i++]->next);
5876 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5877 return TRUE;
5880 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5881 static bool
5882 main_read(struct view *view, char *line)
5884 static struct rev_graph *graph = graph_stacks;
5885 enum line_type type;
5886 struct commit *commit;
5888 if (!line) {
5889 int i;
5891 if (!view->lines && !view->parent)
5892 die("No revisions match the given arguments.");
5893 if (view->lines > 0) {
5894 commit = view->line[view->lines - 1].data;
5895 view->line[view->lines - 1].dirty = 1;
5896 if (!*commit->author) {
5897 view->lines--;
5898 free(commit);
5899 graph->commit = NULL;
5902 update_rev_graph(view, graph);
5904 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5905 clear_rev_graph(&graph_stacks[i]);
5906 return TRUE;
5909 type = get_line_type(line);
5910 if (type == LINE_COMMIT) {
5911 commit = calloc(1, sizeof(struct commit));
5912 if (!commit)
5913 return FALSE;
5915 line += STRING_SIZE("commit ");
5916 if (*line == '-') {
5917 graph->boundary = 1;
5918 line++;
5921 string_copy_rev(commit->id, line);
5922 commit->refs = get_refs(commit->id);
5923 graph->commit = commit;
5924 add_line_data(view, commit, LINE_MAIN_COMMIT);
5926 while ((line = strchr(line, ' '))) {
5927 line++;
5928 push_rev_graph(graph->parents, line);
5929 commit->has_parents = TRUE;
5931 return TRUE;
5934 if (!view->lines)
5935 return TRUE;
5936 commit = view->line[view->lines - 1].data;
5938 switch (type) {
5939 case LINE_PARENT:
5940 if (commit->has_parents)
5941 break;
5942 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5943 break;
5945 case LINE_AUTHOR:
5946 parse_author_line(line + STRING_SIZE("author "),
5947 commit->author, sizeof(commit->author),
5948 &commit->time);
5949 update_rev_graph(view, graph);
5950 graph = graph->next;
5951 break;
5953 default:
5954 /* Fill in the commit title if it has not already been set. */
5955 if (commit->title[0])
5956 break;
5958 /* Require titles to start with a non-space character at the
5959 * offset used by git log. */
5960 if (strncmp(line, " ", 4))
5961 break;
5962 line += 4;
5963 /* Well, if the title starts with a whitespace character,
5964 * try to be forgiving. Otherwise we end up with no title. */
5965 while (isspace(*line))
5966 line++;
5967 if (*line == '\0')
5968 break;
5969 /* FIXME: More graceful handling of titles; append "..." to
5970 * shortened titles, etc. */
5972 string_expand(commit->title, sizeof(commit->title), line, 1);
5973 view->line[view->lines - 1].dirty = 1;
5976 return TRUE;
5979 static enum request
5980 main_request(struct view *view, enum request request, struct line *line)
5982 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5984 switch (request) {
5985 case REQ_ENTER:
5986 open_view(view, REQ_VIEW_DIFF, flags);
5987 break;
5988 case REQ_REFRESH:
5989 load_refs();
5990 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5991 break;
5992 default:
5993 return request;
5996 return REQ_NONE;
5999 static bool
6000 grep_refs(struct ref **refs, regex_t *regex)
6002 regmatch_t pmatch;
6003 size_t i = 0;
6005 if (!refs)
6006 return FALSE;
6007 do {
6008 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6009 return TRUE;
6010 } while (refs[i++]->next);
6012 return FALSE;
6015 static bool
6016 main_grep(struct view *view, struct line *line)
6018 struct commit *commit = line->data;
6019 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6020 char buf[DATE_COLS + 1];
6021 regmatch_t pmatch;
6023 for (state = S_TITLE; state < S_END; state++) {
6024 char *text;
6026 switch (state) {
6027 case S_TITLE: text = commit->title; break;
6028 case S_AUTHOR:
6029 if (!opt_author)
6030 continue;
6031 text = commit->author;
6032 break;
6033 case S_DATE:
6034 if (!opt_date)
6035 continue;
6036 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6037 continue;
6038 text = buf;
6039 break;
6040 case S_REFS:
6041 if (!opt_show_refs)
6042 continue;
6043 if (grep_refs(commit->refs, view->regex) == TRUE)
6044 return TRUE;
6045 continue;
6046 default:
6047 return FALSE;
6050 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6051 return TRUE;
6054 return FALSE;
6057 static void
6058 main_select(struct view *view, struct line *line)
6060 struct commit *commit = line->data;
6062 string_copy_rev(view->ref, commit->id);
6063 string_copy_rev(ref_commit, view->ref);
6066 static struct view_ops main_ops = {
6067 "commit",
6068 main_argv,
6069 NULL,
6070 main_read,
6071 main_draw,
6072 main_request,
6073 main_grep,
6074 main_select,
6079 * Unicode / UTF-8 handling
6081 * NOTE: Much of the following code for dealing with Unicode is derived from
6082 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6083 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6086 static inline int
6087 unicode_width(unsigned long c)
6089 if (c >= 0x1100 &&
6090 (c <= 0x115f /* Hangul Jamo */
6091 || c == 0x2329
6092 || c == 0x232a
6093 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6094 /* CJK ... Yi */
6095 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6096 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6097 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6098 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6099 || (c >= 0xffe0 && c <= 0xffe6)
6100 || (c >= 0x20000 && c <= 0x2fffd)
6101 || (c >= 0x30000 && c <= 0x3fffd)))
6102 return 2;
6104 if (c == '\t')
6105 return opt_tab_size;
6107 return 1;
6110 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6111 * Illegal bytes are set one. */
6112 static const unsigned char utf8_bytes[256] = {
6113 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,
6114 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,
6115 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,
6116 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,
6117 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,
6118 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,
6119 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,
6120 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,
6123 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6124 static inline unsigned long
6125 utf8_to_unicode(const char *string, size_t length)
6127 unsigned long unicode;
6129 switch (length) {
6130 case 1:
6131 unicode = string[0];
6132 break;
6133 case 2:
6134 unicode = (string[0] & 0x1f) << 6;
6135 unicode += (string[1] & 0x3f);
6136 break;
6137 case 3:
6138 unicode = (string[0] & 0x0f) << 12;
6139 unicode += ((string[1] & 0x3f) << 6);
6140 unicode += (string[2] & 0x3f);
6141 break;
6142 case 4:
6143 unicode = (string[0] & 0x0f) << 18;
6144 unicode += ((string[1] & 0x3f) << 12);
6145 unicode += ((string[2] & 0x3f) << 6);
6146 unicode += (string[3] & 0x3f);
6147 break;
6148 case 5:
6149 unicode = (string[0] & 0x0f) << 24;
6150 unicode += ((string[1] & 0x3f) << 18);
6151 unicode += ((string[2] & 0x3f) << 12);
6152 unicode += ((string[3] & 0x3f) << 6);
6153 unicode += (string[4] & 0x3f);
6154 break;
6155 case 6:
6156 unicode = (string[0] & 0x01) << 30;
6157 unicode += ((string[1] & 0x3f) << 24);
6158 unicode += ((string[2] & 0x3f) << 18);
6159 unicode += ((string[3] & 0x3f) << 12);
6160 unicode += ((string[4] & 0x3f) << 6);
6161 unicode += (string[5] & 0x3f);
6162 break;
6163 default:
6164 die("Invalid Unicode length");
6167 /* Invalid characters could return the special 0xfffd value but NUL
6168 * should be just as good. */
6169 return unicode > 0xffff ? 0 : unicode;
6172 /* Calculates how much of string can be shown within the given maximum width
6173 * and sets trimmed parameter to non-zero value if all of string could not be
6174 * shown. If the reserve flag is TRUE, it will reserve at least one
6175 * trailing character, which can be useful when drawing a delimiter.
6177 * Returns the number of bytes to output from string to satisfy max_width. */
6178 static size_t
6179 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6181 const char *string = *start;
6182 const char *end = strchr(string, '\0');
6183 unsigned char last_bytes = 0;
6184 size_t last_ucwidth = 0;
6186 *width = 0;
6187 *trimmed = 0;
6189 while (string < end) {
6190 int c = *(unsigned char *) string;
6191 unsigned char bytes = utf8_bytes[c];
6192 size_t ucwidth;
6193 unsigned long unicode;
6195 if (string + bytes > end)
6196 break;
6198 /* Change representation to figure out whether
6199 * it is a single- or double-width character. */
6201 unicode = utf8_to_unicode(string, bytes);
6202 /* FIXME: Graceful handling of invalid Unicode character. */
6203 if (!unicode)
6204 break;
6206 ucwidth = unicode_width(unicode);
6207 if (skip > 0) {
6208 skip -= ucwidth <= skip ? ucwidth : skip;
6209 *start += bytes;
6211 *width += ucwidth;
6212 if (*width > max_width) {
6213 *trimmed = 1;
6214 *width -= ucwidth;
6215 if (reserve && *width == max_width) {
6216 string -= last_bytes;
6217 *width -= last_ucwidth;
6219 break;
6222 string += bytes;
6223 last_bytes = ucwidth ? bytes : 0;
6224 last_ucwidth = ucwidth;
6227 return string - *start;
6232 * Status management
6235 /* Whether or not the curses interface has been initialized. */
6236 static bool cursed = FALSE;
6238 /* Terminal hacks and workarounds. */
6239 static bool use_scroll_redrawwin;
6240 static bool use_scroll_status_wclear;
6242 /* The status window is used for polling keystrokes. */
6243 static WINDOW *status_win;
6245 /* Reading from the prompt? */
6246 static bool input_mode = FALSE;
6248 static bool status_empty = FALSE;
6250 /* Update status and title window. */
6251 static void
6252 report(const char *msg, ...)
6254 struct view *view = display[current_view];
6256 if (input_mode)
6257 return;
6259 if (!view) {
6260 char buf[SIZEOF_STR];
6261 va_list args;
6263 va_start(args, msg);
6264 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6265 buf[sizeof(buf) - 1] = 0;
6266 buf[sizeof(buf) - 2] = '.';
6267 buf[sizeof(buf) - 3] = '.';
6268 buf[sizeof(buf) - 4] = '.';
6270 va_end(args);
6271 die("%s", buf);
6274 if (!status_empty || *msg) {
6275 va_list args;
6277 va_start(args, msg);
6279 wmove(status_win, 0, 0);
6280 if (view->has_scrolled && use_scroll_status_wclear)
6281 wclear(status_win);
6282 if (*msg) {
6283 vwprintw(status_win, msg, args);
6284 status_empty = FALSE;
6285 } else {
6286 status_empty = TRUE;
6288 wclrtoeol(status_win);
6289 wnoutrefresh(status_win);
6291 va_end(args);
6294 update_view_title(view);
6297 /* Controls when nodelay should be in effect when polling user input. */
6298 static void
6299 set_nonblocking_input(bool loading)
6301 static unsigned int loading_views;
6303 if ((loading == FALSE && loading_views-- == 1) ||
6304 (loading == TRUE && loading_views++ == 0))
6305 nodelay(status_win, loading);
6308 static void
6309 init_display(void)
6311 const char *term;
6312 int x, y;
6314 /* Initialize the curses library */
6315 if (isatty(STDIN_FILENO)) {
6316 cursed = !!initscr();
6317 opt_tty = stdin;
6318 } else {
6319 /* Leave stdin and stdout alone when acting as a pager. */
6320 opt_tty = fopen("/dev/tty", "r+");
6321 if (!opt_tty)
6322 die("Failed to open /dev/tty");
6323 cursed = !!newterm(NULL, opt_tty, opt_tty);
6326 if (!cursed)
6327 die("Failed to initialize curses");
6329 nonl(); /* Disable conversion and detect newlines from input. */
6330 cbreak(); /* Take input chars one at a time, no wait for \n */
6331 noecho(); /* Don't echo input */
6332 leaveok(stdscr, FALSE);
6334 if (has_colors())
6335 init_colors();
6337 getmaxyx(stdscr, y, x);
6338 status_win = newwin(1, 0, y - 1, 0);
6339 if (!status_win)
6340 die("Failed to create status window");
6342 /* Enable keyboard mapping */
6343 keypad(status_win, TRUE);
6344 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6346 TABSIZE = opt_tab_size;
6347 if (opt_line_graphics) {
6348 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6351 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6352 if (term && !strcmp(term, "gnome-terminal")) {
6353 /* In the gnome-terminal-emulator, the message from
6354 * scrolling up one line when impossible followed by
6355 * scrolling down one line causes corruption of the
6356 * status line. This is fixed by calling wclear. */
6357 use_scroll_status_wclear = TRUE;
6358 use_scroll_redrawwin = FALSE;
6360 } else if (term && !strcmp(term, "xrvt-xpm")) {
6361 /* No problems with full optimizations in xrvt-(unicode)
6362 * and aterm. */
6363 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6365 } else {
6366 /* When scrolling in (u)xterm the last line in the
6367 * scrolling direction will update slowly. */
6368 use_scroll_redrawwin = TRUE;
6369 use_scroll_status_wclear = FALSE;
6373 static int
6374 get_input(int prompt_position)
6376 struct view *view;
6377 int i, key, cursor_y, cursor_x;
6379 if (prompt_position)
6380 input_mode = TRUE;
6382 while (TRUE) {
6383 foreach_view (view, i) {
6384 update_view(view);
6385 if (view_is_displayed(view) && view->has_scrolled &&
6386 use_scroll_redrawwin)
6387 redrawwin(view->win);
6388 view->has_scrolled = FALSE;
6391 /* Update the cursor position. */
6392 if (prompt_position) {
6393 getbegyx(status_win, cursor_y, cursor_x);
6394 cursor_x = prompt_position;
6395 } else {
6396 view = display[current_view];
6397 getbegyx(view->win, cursor_y, cursor_x);
6398 cursor_x = view->width - 1;
6399 cursor_y += view->lineno - view->offset;
6401 setsyx(cursor_y, cursor_x);
6403 /* Refresh, accept single keystroke of input */
6404 doupdate();
6405 key = wgetch(status_win);
6407 /* wgetch() with nodelay() enabled returns ERR when
6408 * there's no input. */
6409 if (key == ERR) {
6411 } else if (key == KEY_RESIZE) {
6412 int height, width;
6414 getmaxyx(stdscr, height, width);
6416 wresize(status_win, 1, width);
6417 mvwin(status_win, height - 1, 0);
6418 wnoutrefresh(status_win);
6419 resize_display();
6420 redraw_display(TRUE);
6422 } else {
6423 input_mode = FALSE;
6424 return key;
6429 static char *
6430 prompt_input(const char *prompt, input_handler handler, void *data)
6432 enum input_status status = INPUT_OK;
6433 static char buf[SIZEOF_STR];
6434 size_t pos = 0;
6436 buf[pos] = 0;
6438 while (status == INPUT_OK || status == INPUT_SKIP) {
6439 int key;
6441 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6442 wclrtoeol(status_win);
6444 key = get_input(pos + 1);
6445 switch (key) {
6446 case KEY_RETURN:
6447 case KEY_ENTER:
6448 case '\n':
6449 status = pos ? INPUT_STOP : INPUT_CANCEL;
6450 break;
6452 case KEY_BACKSPACE:
6453 if (pos > 0)
6454 buf[--pos] = 0;
6455 else
6456 status = INPUT_CANCEL;
6457 break;
6459 case KEY_ESC:
6460 status = INPUT_CANCEL;
6461 break;
6463 default:
6464 if (pos >= sizeof(buf)) {
6465 report("Input string too long");
6466 return NULL;
6469 status = handler(data, buf, key);
6470 if (status == INPUT_OK)
6471 buf[pos++] = (char) key;
6475 /* Clear the status window */
6476 status_empty = FALSE;
6477 report("");
6479 if (status == INPUT_CANCEL)
6480 return NULL;
6482 buf[pos++] = 0;
6484 return buf;
6487 static enum input_status
6488 prompt_yesno_handler(void *data, char *buf, int c)
6490 if (c == 'y' || c == 'Y')
6491 return INPUT_STOP;
6492 if (c == 'n' || c == 'N')
6493 return INPUT_CANCEL;
6494 return INPUT_SKIP;
6497 static bool
6498 prompt_yesno(const char *prompt)
6500 char prompt2[SIZEOF_STR];
6502 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6503 return FALSE;
6505 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6508 static enum input_status
6509 read_prompt_handler(void *data, char *buf, int c)
6511 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6514 static char *
6515 read_prompt(const char *prompt)
6517 return prompt_input(prompt, read_prompt_handler, NULL);
6521 * Repository properties
6524 static struct ref *refs = NULL;
6525 static size_t refs_alloc = 0;
6526 static size_t refs_size = 0;
6528 /* Id <-> ref store */
6529 static struct ref ***id_refs = NULL;
6530 static size_t id_refs_alloc = 0;
6531 static size_t id_refs_size = 0;
6533 static int
6534 compare_refs(const void *ref1_, const void *ref2_)
6536 const struct ref *ref1 = *(const struct ref **)ref1_;
6537 const struct ref *ref2 = *(const struct ref **)ref2_;
6539 if (ref1->tag != ref2->tag)
6540 return ref2->tag - ref1->tag;
6541 if (ref1->ltag != ref2->ltag)
6542 return ref2->ltag - ref2->ltag;
6543 if (ref1->head != ref2->head)
6544 return ref2->head - ref1->head;
6545 if (ref1->tracked != ref2->tracked)
6546 return ref2->tracked - ref1->tracked;
6547 if (ref1->remote != ref2->remote)
6548 return ref2->remote - ref1->remote;
6549 return strcmp(ref1->name, ref2->name);
6552 static struct ref **
6553 get_refs(const char *id)
6555 struct ref ***tmp_id_refs;
6556 struct ref **ref_list = NULL;
6557 size_t ref_list_alloc = 0;
6558 size_t ref_list_size = 0;
6559 size_t i;
6561 for (i = 0; i < id_refs_size; i++)
6562 if (!strcmp(id, id_refs[i][0]->id))
6563 return id_refs[i];
6565 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6566 sizeof(*id_refs));
6567 if (!tmp_id_refs)
6568 return NULL;
6570 id_refs = tmp_id_refs;
6572 for (i = 0; i < refs_size; i++) {
6573 struct ref **tmp;
6575 if (strcmp(id, refs[i].id))
6576 continue;
6578 tmp = realloc_items(ref_list, &ref_list_alloc,
6579 ref_list_size + 1, sizeof(*ref_list));
6580 if (!tmp) {
6581 if (ref_list)
6582 free(ref_list);
6583 return NULL;
6586 ref_list = tmp;
6587 ref_list[ref_list_size] = &refs[i];
6588 /* XXX: The properties of the commit chains ensures that we can
6589 * safely modify the shared ref. The repo references will
6590 * always be similar for the same id. */
6591 ref_list[ref_list_size]->next = 1;
6593 ref_list_size++;
6596 if (ref_list) {
6597 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6598 ref_list[ref_list_size - 1]->next = 0;
6599 id_refs[id_refs_size++] = ref_list;
6602 return ref_list;
6605 static int
6606 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6608 struct ref *ref;
6609 bool tag = FALSE;
6610 bool ltag = FALSE;
6611 bool remote = FALSE;
6612 bool tracked = FALSE;
6613 bool check_replace = FALSE;
6614 bool head = FALSE;
6616 if (!prefixcmp(name, "refs/tags/")) {
6617 if (!suffixcmp(name, namelen, "^{}")) {
6618 namelen -= 3;
6619 name[namelen] = 0;
6620 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6621 check_replace = TRUE;
6622 } else {
6623 ltag = TRUE;
6626 tag = TRUE;
6627 namelen -= STRING_SIZE("refs/tags/");
6628 name += STRING_SIZE("refs/tags/");
6630 } else if (!prefixcmp(name, "refs/remotes/")) {
6631 remote = TRUE;
6632 namelen -= STRING_SIZE("refs/remotes/");
6633 name += STRING_SIZE("refs/remotes/");
6634 tracked = !strcmp(opt_remote, name);
6636 } else if (!prefixcmp(name, "refs/heads/")) {
6637 namelen -= STRING_SIZE("refs/heads/");
6638 name += STRING_SIZE("refs/heads/");
6639 head = !strncmp(opt_head, name, namelen);
6641 } else if (!strcmp(name, "HEAD")) {
6642 string_ncopy(opt_head_rev, id, idlen);
6643 return OK;
6646 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6647 /* it's an annotated tag, replace the previous SHA1 with the
6648 * resolved commit id; relies on the fact git-ls-remote lists
6649 * the commit id of an annotated tag right before the commit id
6650 * it points to. */
6651 refs[refs_size - 1].ltag = ltag;
6652 string_copy_rev(refs[refs_size - 1].id, id);
6654 return OK;
6656 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6657 if (!refs)
6658 return ERR;
6660 ref = &refs[refs_size++];
6661 ref->name = malloc(namelen + 1);
6662 if (!ref->name)
6663 return ERR;
6665 strncpy(ref->name, name, namelen);
6666 ref->name[namelen] = 0;
6667 ref->head = head;
6668 ref->tag = tag;
6669 ref->ltag = ltag;
6670 ref->remote = remote;
6671 ref->tracked = tracked;
6672 string_copy_rev(ref->id, id);
6674 return OK;
6677 static int
6678 load_refs(void)
6680 static const char *ls_remote_argv[SIZEOF_ARG] = {
6681 "git", "ls-remote", opt_git_dir, NULL
6683 static bool init = FALSE;
6685 if (!init) {
6686 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6687 init = TRUE;
6690 if (!*opt_git_dir)
6691 return OK;
6693 while (refs_size > 0)
6694 free(refs[--refs_size].name);
6695 while (id_refs_size > 0)
6696 free(id_refs[--id_refs_size]);
6698 return run_io_load(ls_remote_argv, "\t", read_ref);
6701 static void
6702 set_remote_branch(const char *name, const char *value, size_t valuelen)
6704 if (!strcmp(name, ".remote")) {
6705 string_ncopy(opt_remote, value, valuelen);
6707 } else if (*opt_remote && !strcmp(name, ".merge")) {
6708 size_t from = strlen(opt_remote);
6710 if (!prefixcmp(value, "refs/heads/"))
6711 value += STRING_SIZE("refs/heads/");
6713 if (!string_format_from(opt_remote, &from, "/%s", value))
6714 opt_remote[0] = 0;
6718 static void
6719 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6721 const char *argv[SIZEOF_ARG] = { name, "=" };
6722 int argc = 1 + (cmd == option_set_command);
6723 int error = ERR;
6725 if (!argv_from_string(argv, &argc, value))
6726 config_msg = "Too many option arguments";
6727 else
6728 error = cmd(argc, argv);
6730 if (error == ERR)
6731 warn("Option 'tig.%s': %s", name, config_msg);
6734 static bool
6735 set_environment_variable(const char *name, const char *value)
6737 size_t len = strlen(name) + 1 + strlen(value) + 1;
6738 char *env = malloc(len);
6740 if (env &&
6741 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6742 putenv(env) == 0)
6743 return TRUE;
6744 free(env);
6745 return FALSE;
6748 static void
6749 set_work_tree(const char *value)
6751 char cwd[SIZEOF_STR];
6753 if (!getcwd(cwd, sizeof(cwd)))
6754 die("Failed to get cwd path: %s", strerror(errno));
6755 if (chdir(opt_git_dir) < 0)
6756 die("Failed to chdir(%s): %s", strerror(errno));
6757 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6758 die("Failed to get git path: %s", strerror(errno));
6759 if (chdir(cwd) < 0)
6760 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6761 if (chdir(value) < 0)
6762 die("Failed to chdir(%s): %s", value, strerror(errno));
6763 if (!getcwd(cwd, sizeof(cwd)))
6764 die("Failed to get cwd path: %s", strerror(errno));
6765 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6766 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6767 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6768 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6769 opt_is_inside_work_tree = TRUE;
6772 static int
6773 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6775 if (!strcmp(name, "i18n.commitencoding"))
6776 string_ncopy(opt_encoding, value, valuelen);
6778 else if (!strcmp(name, "core.editor"))
6779 string_ncopy(opt_editor, value, valuelen);
6781 else if (!strcmp(name, "core.worktree"))
6782 set_work_tree(value);
6784 else if (!prefixcmp(name, "tig.color."))
6785 set_repo_config_option(name + 10, value, option_color_command);
6787 else if (!prefixcmp(name, "tig.bind."))
6788 set_repo_config_option(name + 9, value, option_bind_command);
6790 else if (!prefixcmp(name, "tig."))
6791 set_repo_config_option(name + 4, value, option_set_command);
6793 else if (*opt_head && !prefixcmp(name, "branch.") &&
6794 !strncmp(name + 7, opt_head, strlen(opt_head)))
6795 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6797 return OK;
6800 static int
6801 load_git_config(void)
6803 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6805 return run_io_load(config_list_argv, "=", read_repo_config_option);
6808 static int
6809 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6811 if (!opt_git_dir[0]) {
6812 string_ncopy(opt_git_dir, name, namelen);
6814 } else if (opt_is_inside_work_tree == -1) {
6815 /* This can be 3 different values depending on the
6816 * version of git being used. If git-rev-parse does not
6817 * understand --is-inside-work-tree it will simply echo
6818 * the option else either "true" or "false" is printed.
6819 * Default to true for the unknown case. */
6820 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6822 } else if (*name == '.') {
6823 string_ncopy(opt_cdup, name, namelen);
6825 } else {
6826 string_ncopy(opt_prefix, name, namelen);
6829 return OK;
6832 static int
6833 load_repo_info(void)
6835 const char *head_argv[] = {
6836 "git", "symbolic-ref", "HEAD", NULL
6838 const char *rev_parse_argv[] = {
6839 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6840 "--show-cdup", "--show-prefix", NULL
6843 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6844 chomp_string(opt_head);
6845 if (!prefixcmp(opt_head, "refs/heads/")) {
6846 char *offset = opt_head + STRING_SIZE("refs/heads/");
6848 memmove(opt_head, offset, strlen(offset) + 1);
6852 return run_io_load(rev_parse_argv, "=", read_repo_info);
6857 * Main
6860 static const char usage[] =
6861 "tig " TIG_VERSION " (" __DATE__ ")\n"
6862 "\n"
6863 "Usage: tig [options] [revs] [--] [paths]\n"
6864 " or: tig show [options] [revs] [--] [paths]\n"
6865 " or: tig blame [rev] path\n"
6866 " or: tig status\n"
6867 " or: tig < [git command output]\n"
6868 "\n"
6869 "Options:\n"
6870 " -v, --version Show version and exit\n"
6871 " -h, --help Show help message and exit";
6873 static void __NORETURN
6874 quit(int sig)
6876 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6877 if (cursed)
6878 endwin();
6879 exit(0);
6882 static void __NORETURN
6883 die(const char *err, ...)
6885 va_list args;
6887 endwin();
6889 va_start(args, err);
6890 fputs("tig: ", stderr);
6891 vfprintf(stderr, err, args);
6892 fputs("\n", stderr);
6893 va_end(args);
6895 exit(1);
6898 static void
6899 warn(const char *msg, ...)
6901 va_list args;
6903 va_start(args, msg);
6904 fputs("tig warning: ", stderr);
6905 vfprintf(stderr, msg, args);
6906 fputs("\n", stderr);
6907 va_end(args);
6910 static enum request
6911 parse_options(int argc, const char *argv[])
6913 enum request request = REQ_VIEW_MAIN;
6914 const char *subcommand;
6915 bool seen_dashdash = FALSE;
6916 /* XXX: This is vulnerable to the user overriding options
6917 * required for the main view parser. */
6918 const char *custom_argv[SIZEOF_ARG] = {
6919 "git", "log", "--no-color", "--pretty=raw", "--parents",
6920 "--topo-order", NULL
6922 int i, j = 6;
6924 if (!isatty(STDIN_FILENO)) {
6925 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6926 return REQ_VIEW_PAGER;
6929 if (argc <= 1)
6930 return REQ_NONE;
6932 subcommand = argv[1];
6933 if (!strcmp(subcommand, "status")) {
6934 if (argc > 2)
6935 warn("ignoring arguments after `%s'", subcommand);
6936 return REQ_VIEW_STATUS;
6938 } else if (!strcmp(subcommand, "blame")) {
6939 if (argc <= 2 || argc > 4)
6940 die("invalid number of options to blame\n\n%s", usage);
6942 i = 2;
6943 if (argc == 4) {
6944 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6945 i++;
6948 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6949 return REQ_VIEW_BLAME;
6951 } else if (!strcmp(subcommand, "show")) {
6952 request = REQ_VIEW_DIFF;
6954 } else {
6955 subcommand = NULL;
6958 if (subcommand) {
6959 custom_argv[1] = subcommand;
6960 j = 2;
6963 for (i = 1 + !!subcommand; i < argc; i++) {
6964 const char *opt = argv[i];
6966 if (seen_dashdash || !strcmp(opt, "--")) {
6967 seen_dashdash = TRUE;
6969 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6970 printf("tig version %s\n", TIG_VERSION);
6971 quit(0);
6973 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6974 printf("%s\n", usage);
6975 quit(0);
6978 custom_argv[j++] = opt;
6979 if (j >= ARRAY_SIZE(custom_argv))
6980 die("command too long");
6983 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6984 die("Failed to format arguments");
6986 return request;
6990 main(int argc, const char *argv[])
6992 enum request request = parse_options(argc, argv);
6993 struct view *view;
6994 size_t i;
6996 signal(SIGINT, quit);
6997 signal(SIGPIPE, SIG_IGN);
6999 if (setlocale(LC_ALL, "")) {
7000 char *codeset = nl_langinfo(CODESET);
7002 string_ncopy(opt_codeset, codeset, strlen(codeset));
7005 if (load_repo_info() == ERR)
7006 die("Failed to load repo info.");
7008 if (load_options() == ERR)
7009 die("Failed to load user config.");
7011 if (load_git_config() == ERR)
7012 die("Failed to load repo config.");
7014 /* Require a git repository unless when running in pager mode. */
7015 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7016 die("Not a git repository");
7018 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7019 opt_utf8 = FALSE;
7021 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7022 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7023 if (opt_iconv == ICONV_NONE)
7024 die("Failed to initialize character set conversion");
7027 if (load_refs() == ERR)
7028 die("Failed to load refs.");
7030 foreach_view (view, i)
7031 argv_from_env(view->ops->argv, view->cmd_env);
7033 init_display();
7035 if (request != REQ_NONE)
7036 open_view(NULL, request, OPEN_PREPARED);
7037 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7039 while (view_driver(display[current_view], request)) {
7040 int key = get_input(0);
7042 view = display[current_view];
7043 request = get_keybinding(view->keymap, key);
7045 /* Some low-level request handling. This keeps access to
7046 * status_win restricted. */
7047 switch (request) {
7048 case REQ_PROMPT:
7050 char *cmd = read_prompt(":");
7052 if (cmd && isdigit(*cmd)) {
7053 int lineno = view->lineno + 1;
7055 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7056 select_view_line(view, lineno - 1);
7057 report("");
7058 } else {
7059 report("Unable to parse '%s' as a line number", cmd);
7062 } else if (cmd) {
7063 struct view *next = VIEW(REQ_VIEW_PAGER);
7064 const char *argv[SIZEOF_ARG] = { "git" };
7065 int argc = 1;
7067 /* When running random commands, initially show the
7068 * command in the title. However, it maybe later be
7069 * overwritten if a commit line is selected. */
7070 string_ncopy(next->ref, cmd, strlen(cmd));
7072 if (!argv_from_string(argv, &argc, cmd)) {
7073 report("Too many arguments");
7074 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7075 report("Failed to format command");
7076 } else {
7077 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7081 request = REQ_NONE;
7082 break;
7084 case REQ_SEARCH:
7085 case REQ_SEARCH_BACK:
7087 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7088 char *search = read_prompt(prompt);
7090 if (search)
7091 string_ncopy(opt_search, search, strlen(search));
7092 else if (*opt_search)
7093 request = request == REQ_SEARCH ?
7094 REQ_FIND_NEXT :
7095 REQ_FIND_PREV;
7096 else
7097 request = REQ_NONE;
7098 break;
7100 default:
7101 break;
7105 quit(0);
7107 return 0;