Status view: report failures to update a file
[tig.git] / tig.c
blob8b8d593122b1d813f056ed5a5079a1c9b9078038
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 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB '\t'
126 #define KEY_RETURN '\r'
127 #define KEY_ESC 27
130 struct ref {
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
164 * String helpers
167 static inline void
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170 if (srclen > dstlen - 1)
171 srclen = dstlen - 1;
173 strncpy(dst, src, srclen);
174 dst[srclen] = 0;
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183 string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
191 static void
192 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
194 size_t size, pos;
196 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
197 if (src[pos] == '\t') {
198 size_t expanded = tabsize - (size % tabsize);
200 if (expanded + size >= dstlen - 1)
201 expanded = dstlen - size - 1;
202 memcpy(dst + size, " ", expanded);
203 size += expanded;
204 } else {
205 dst[size++] = src[pos];
209 dst[size] = 0;
212 static char *
213 chomp_string(char *name)
215 int namelen;
217 while (isspace(*name))
218 name++;
220 namelen = strlen(name) - 1;
221 while (namelen > 0 && isspace(name[namelen]))
222 name[namelen--] = 0;
224 return name;
227 static bool
228 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
230 va_list args;
231 size_t pos = bufpos ? *bufpos : 0;
233 va_start(args, fmt);
234 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
235 va_end(args);
237 if (bufpos)
238 *bufpos = pos;
240 return pos >= bufsize ? FALSE : TRUE;
243 #define string_format(buf, fmt, args...) \
244 string_nformat(buf, sizeof(buf), NULL, fmt, args)
246 #define string_format_from(buf, from, fmt, args...) \
247 string_nformat(buf, sizeof(buf), from, fmt, args)
249 static int
250 string_enum_compare(const char *str1, const char *str2, int len)
252 size_t i;
254 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
256 /* Diff-Header == DIFF_HEADER */
257 for (i = 0; i < len; i++) {
258 if (toupper(str1[i]) == toupper(str2[i]))
259 continue;
261 if (string_enum_sep(str1[i]) &&
262 string_enum_sep(str2[i]))
263 continue;
265 return str1[i] - str2[i];
268 return 0;
271 struct enum_map {
272 const char *name;
273 int namelen;
274 int value;
277 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
279 static bool
280 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
282 size_t namelen = strlen(name);
283 int i;
285 for (i = 0; i < map_size; i++)
286 if (namelen == map[i].namelen &&
287 !string_enum_compare(name, map[i].name, namelen)) {
288 *value = map[i].value;
289 return TRUE;
292 return FALSE;
295 #define map_enum(attr, map, name) \
296 map_enum_do(map, ARRAY_SIZE(map), attr, name)
298 #define prefixcmp(str1, str2) \
299 strncmp(str1, str2, STRING_SIZE(str2))
301 static inline int
302 suffixcmp(const char *str, int slen, const char *suffix)
304 size_t len = slen >= 0 ? slen : strlen(str);
305 size_t suffixlen = strlen(suffix);
307 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
311 static bool
312 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
314 int valuelen;
316 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
317 bool advance = cmd[valuelen] != 0;
319 cmd[valuelen] = 0;
320 argv[(*argc)++] = chomp_string(cmd);
321 cmd = chomp_string(cmd + valuelen + advance);
324 if (*argc < SIZEOF_ARG)
325 argv[*argc] = NULL;
326 return *argc < SIZEOF_ARG;
329 static void
330 argv_from_env(const char **argv, const char *name)
332 char *env = argv ? getenv(name) : NULL;
333 int argc = 0;
335 if (env && *env)
336 env = strdup(env);
337 if (env && !argv_from_string(argv, &argc, env))
338 die("Too many arguments in the `%s` environment variable", name);
343 * Executing external commands.
346 enum io_type {
347 IO_FD, /* File descriptor based IO. */
348 IO_BG, /* Execute command in the background. */
349 IO_FG, /* Execute command with same std{in,out,err}. */
350 IO_RD, /* Read only fork+exec IO. */
351 IO_WR, /* Write only fork+exec IO. */
352 IO_AP, /* Append fork+exec output to file. */
355 struct io {
356 enum io_type type; /* The requested type of pipe. */
357 const char *dir; /* Directory from which to execute. */
358 pid_t pid; /* Pipe for reading or writing. */
359 int pipe; /* Pipe end for reading or writing. */
360 int error; /* Error status. */
361 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
362 char *buf; /* Read buffer. */
363 size_t bufalloc; /* Allocated buffer size. */
364 size_t bufsize; /* Buffer content size. */
365 char *bufpos; /* Current buffer position. */
366 unsigned int eof:1; /* Has end of file been reached. */
369 static void
370 reset_io(struct io *io)
372 io->pipe = -1;
373 io->pid = 0;
374 io->buf = io->bufpos = NULL;
375 io->bufalloc = io->bufsize = 0;
376 io->error = 0;
377 io->eof = 0;
380 static void
381 init_io(struct io *io, const char *dir, enum io_type type)
383 reset_io(io);
384 io->type = type;
385 io->dir = dir;
388 static bool
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390 enum format_flags flags)
392 init_io(io, dir, IO_RD);
393 return format_argv(io->argv, argv, flags);
396 static bool
397 io_open(struct io *io, const char *name)
399 init_io(io, NULL, IO_FD);
400 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
401 if (io->pipe == -1)
402 io->error = errno;
403 return io->pipe != -1;
406 static bool
407 kill_io(struct io *io)
409 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
412 static bool
413 done_io(struct io *io)
415 pid_t pid = io->pid;
417 if (io->pipe != -1)
418 close(io->pipe);
419 free(io->buf);
420 reset_io(io);
422 while (pid > 0) {
423 int status;
424 pid_t waiting = waitpid(pid, &status, 0);
426 if (waiting < 0) {
427 if (errno == EINTR)
428 continue;
429 report("waitpid failed (%s)", strerror(errno));
430 return FALSE;
433 return waiting == pid &&
434 !WIFSIGNALED(status) &&
435 WIFEXITED(status) &&
436 !WEXITSTATUS(status);
439 return TRUE;
442 static bool
443 start_io(struct io *io)
445 int pipefds[2] = { -1, -1 };
447 if (io->type == IO_FD)
448 return TRUE;
450 if ((io->type == IO_RD || io->type == IO_WR) &&
451 pipe(pipefds) < 0)
452 return FALSE;
453 else if (io->type == IO_AP)
454 pipefds[1] = io->pipe;
456 if ((io->pid = fork())) {
457 if (pipefds[!(io->type == IO_WR)] != -1)
458 close(pipefds[!(io->type == IO_WR)]);
459 if (io->pid != -1) {
460 io->pipe = pipefds[!!(io->type == IO_WR)];
461 return TRUE;
464 } else {
465 if (io->type != IO_FG) {
466 int devnull = open("/dev/null", O_RDWR);
467 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
468 int writefd = (io->type == IO_RD || io->type == IO_AP)
469 ? pipefds[1] : devnull;
471 dup2(readfd, STDIN_FILENO);
472 dup2(writefd, STDOUT_FILENO);
473 dup2(devnull, STDERR_FILENO);
475 close(devnull);
476 if (pipefds[0] != -1)
477 close(pipefds[0]);
478 if (pipefds[1] != -1)
479 close(pipefds[1]);
482 if (io->dir && *io->dir && chdir(io->dir) == -1)
483 die("Failed to change directory: %s", strerror(errno));
485 execvp(io->argv[0], (char *const*) io->argv);
486 die("Failed to execute program: %s", strerror(errno));
489 if (pipefds[!!(io->type == IO_WR)] != -1)
490 close(pipefds[!!(io->type == IO_WR)]);
491 return FALSE;
494 static bool
495 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
497 init_io(io, dir, type);
498 if (!format_argv(io->argv, argv, FORMAT_NONE))
499 return FALSE;
500 return start_io(io);
503 static int
504 run_io_do(struct io *io)
506 return start_io(io) && done_io(io);
509 static int
510 run_io_bg(const char **argv)
512 struct io io = {};
514 init_io(&io, NULL, IO_BG);
515 if (!format_argv(io.argv, argv, FORMAT_NONE))
516 return FALSE;
517 return run_io_do(&io);
520 static bool
521 run_io_fg(const char **argv, const char *dir)
523 struct io io = {};
525 init_io(&io, dir, IO_FG);
526 if (!format_argv(io.argv, argv, FORMAT_NONE))
527 return FALSE;
528 return run_io_do(&io);
531 static bool
532 run_io_append(const char **argv, enum format_flags flags, int fd)
534 struct io io = {};
536 init_io(&io, NULL, IO_AP);
537 io.pipe = fd;
538 if (format_argv(io.argv, argv, flags))
539 return run_io_do(&io);
540 close(fd);
541 return FALSE;
544 static bool
545 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
547 return init_io_rd(io, argv, NULL, flags) && start_io(io);
550 static bool
551 io_eof(struct io *io)
553 return io->eof;
556 static int
557 io_error(struct io *io)
559 return io->error;
562 static char *
563 io_strerror(struct io *io)
565 return strerror(io->error);
568 static bool
569 io_can_read(struct io *io)
571 struct timeval tv = { 0, 500 };
572 fd_set fds;
574 FD_ZERO(&fds);
575 FD_SET(io->pipe, &fds);
577 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
580 static ssize_t
581 io_read(struct io *io, void *buf, size_t bufsize)
583 do {
584 ssize_t readsize = read(io->pipe, buf, bufsize);
586 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
587 continue;
588 else if (readsize == -1)
589 io->error = errno;
590 else if (readsize == 0)
591 io->eof = 1;
592 return readsize;
593 } while (1);
596 static char *
597 io_get(struct io *io, int c, bool can_read)
599 char *eol;
600 ssize_t readsize;
602 if (!io->buf) {
603 io->buf = io->bufpos = malloc(BUFSIZ);
604 if (!io->buf)
605 return NULL;
606 io->bufalloc = BUFSIZ;
607 io->bufsize = 0;
610 while (TRUE) {
611 if (io->bufsize > 0) {
612 eol = memchr(io->bufpos, c, io->bufsize);
613 if (eol) {
614 char *line = io->bufpos;
616 *eol = 0;
617 io->bufpos = eol + 1;
618 io->bufsize -= io->bufpos - line;
619 return line;
623 if (io_eof(io)) {
624 if (io->bufsize) {
625 io->bufpos[io->bufsize] = 0;
626 io->bufsize = 0;
627 return io->bufpos;
629 return NULL;
632 if (!can_read)
633 return NULL;
635 if (io->bufsize > 0 && io->bufpos > io->buf)
636 memmove(io->buf, io->bufpos, io->bufsize);
638 io->bufpos = io->buf;
639 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
640 if (io_error(io))
641 return NULL;
642 io->bufsize += readsize;
646 static bool
647 io_write(struct io *io, const void *buf, size_t bufsize)
649 size_t written = 0;
651 while (!io_error(io) && written < bufsize) {
652 ssize_t size;
654 size = write(io->pipe, buf + written, bufsize - written);
655 if (size < 0 && (errno == EAGAIN || errno == EINTR))
656 continue;
657 else if (size == -1)
658 io->error = errno;
659 else
660 written += size;
663 return written == bufsize;
666 static bool
667 io_read_buf(struct io *io, char buf[], size_t bufsize)
669 bool error;
671 io->buf = io->bufpos = buf;
672 io->bufalloc = bufsize;
673 error = !io_get(io, '\n', TRUE) && io_error(io);
674 io->buf = NULL;
676 return done_io(io) || error;
679 static bool
680 run_io_buf(const char **argv, char buf[], size_t bufsize)
682 struct io io = {};
684 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
687 static int
688 io_load(struct io *io, const char *separators,
689 int (*read_property)(char *, size_t, char *, size_t))
691 char *name;
692 int state = OK;
694 if (!start_io(io))
695 return ERR;
697 while (state == OK && (name = io_get(io, '\n', TRUE))) {
698 char *value;
699 size_t namelen;
700 size_t valuelen;
702 name = chomp_string(name);
703 namelen = strcspn(name, separators);
705 if (name[namelen]) {
706 name[namelen] = 0;
707 value = chomp_string(name + namelen + 1);
708 valuelen = strlen(value);
710 } else {
711 value = "";
712 valuelen = 0;
715 state = read_property(name, namelen, value, valuelen);
718 if (state != ERR && io_error(io))
719 state = ERR;
720 done_io(io);
722 return state;
725 static int
726 run_io_load(const char **argv, const char *separators,
727 int (*read_property)(char *, size_t, char *, size_t))
729 struct io io = {};
731 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
732 ? io_load(&io, separators, read_property) : ERR;
737 * User requests
740 #define REQ_INFO \
741 /* XXX: Keep the view request first and in sync with views[]. */ \
742 REQ_GROUP("View switching") \
743 REQ_(VIEW_MAIN, "Show main view"), \
744 REQ_(VIEW_DIFF, "Show diff view"), \
745 REQ_(VIEW_LOG, "Show log view"), \
746 REQ_(VIEW_TREE, "Show tree view"), \
747 REQ_(VIEW_BLOB, "Show blob view"), \
748 REQ_(VIEW_BLAME, "Show blame view"), \
749 REQ_(VIEW_HELP, "Show help page"), \
750 REQ_(VIEW_PAGER, "Show pager view"), \
751 REQ_(VIEW_STATUS, "Show status view"), \
752 REQ_(VIEW_STAGE, "Show stage view"), \
754 REQ_GROUP("View manipulation") \
755 REQ_(ENTER, "Enter current line and scroll"), \
756 REQ_(NEXT, "Move to next"), \
757 REQ_(PREVIOUS, "Move to previous"), \
758 REQ_(PARENT, "Move to parent"), \
759 REQ_(VIEW_NEXT, "Move focus to next view"), \
760 REQ_(REFRESH, "Reload and refresh"), \
761 REQ_(MAXIMIZE, "Maximize the current view"), \
762 REQ_(VIEW_CLOSE, "Close the current view"), \
763 REQ_(QUIT, "Close all views and quit"), \
765 REQ_GROUP("View specific requests") \
766 REQ_(STATUS_UPDATE, "Update file status"), \
767 REQ_(STATUS_REVERT, "Revert file changes"), \
768 REQ_(STATUS_MERGE, "Merge file using external tool"), \
769 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
771 REQ_GROUP("Cursor navigation") \
772 REQ_(MOVE_UP, "Move cursor one line up"), \
773 REQ_(MOVE_DOWN, "Move cursor one line down"), \
774 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
775 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
776 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
777 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
779 REQ_GROUP("Scrolling") \
780 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
781 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
782 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
783 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
784 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
785 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
787 REQ_GROUP("Searching") \
788 REQ_(SEARCH, "Search the view"), \
789 REQ_(SEARCH_BACK, "Search backwards in the view"), \
790 REQ_(FIND_NEXT, "Find next search match"), \
791 REQ_(FIND_PREV, "Find previous search match"), \
793 REQ_GROUP("Option manipulation") \
794 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
795 REQ_(TOGGLE_DATE, "Toggle date display"), \
796 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
797 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
798 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
800 REQ_GROUP("Misc") \
801 REQ_(PROMPT, "Bring up the prompt"), \
802 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
803 REQ_(SHOW_VERSION, "Show version information"), \
804 REQ_(STOP_LOADING, "Stop all loading views"), \
805 REQ_(EDIT, "Open in editor"), \
806 REQ_(NONE, "Do nothing")
809 /* User action requests. */
810 enum request {
811 #define REQ_GROUP(help)
812 #define REQ_(req, help) REQ_##req
814 /* Offset all requests to avoid conflicts with ncurses getch values. */
815 REQ_OFFSET = KEY_MAX + 1,
816 REQ_INFO
818 #undef REQ_GROUP
819 #undef REQ_
822 struct request_info {
823 enum request request;
824 const char *name;
825 int namelen;
826 const char *help;
829 static const struct request_info req_info[] = {
830 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
831 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
832 REQ_INFO
833 #undef REQ_GROUP
834 #undef REQ_
837 static enum request
838 get_request(const char *name)
840 int namelen = strlen(name);
841 int i;
843 for (i = 0; i < ARRAY_SIZE(req_info); i++)
844 if (req_info[i].namelen == namelen &&
845 !string_enum_compare(req_info[i].name, name, namelen))
846 return req_info[i].request;
848 return REQ_NONE;
853 * Options
856 /* Option and state variables. */
857 static bool opt_date = TRUE;
858 static bool opt_author = TRUE;
859 static bool opt_line_number = FALSE;
860 static bool opt_line_graphics = TRUE;
861 static bool opt_rev_graph = FALSE;
862 static bool opt_show_refs = TRUE;
863 static int opt_num_interval = NUMBER_INTERVAL;
864 static int opt_tab_size = TAB_SIZE;
865 static int opt_author_cols = AUTHOR_COLS-1;
866 static char opt_path[SIZEOF_STR] = "";
867 static char opt_file[SIZEOF_STR] = "";
868 static char opt_ref[SIZEOF_REF] = "";
869 static char opt_head[SIZEOF_REF] = "";
870 static char opt_head_rev[SIZEOF_REV] = "";
871 static char opt_remote[SIZEOF_REF] = "";
872 static char opt_encoding[20] = "UTF-8";
873 static bool opt_utf8 = TRUE;
874 static char opt_codeset[20] = "UTF-8";
875 static iconv_t opt_iconv = ICONV_NONE;
876 static char opt_search[SIZEOF_STR] = "";
877 static char opt_cdup[SIZEOF_STR] = "";
878 static char opt_prefix[SIZEOF_STR] = "";
879 static char opt_git_dir[SIZEOF_STR] = "";
880 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
881 static char opt_editor[SIZEOF_STR] = "";
882 static FILE *opt_tty = NULL;
884 #define is_initial_commit() (!*opt_head_rev)
885 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
889 * Line-oriented content detection.
892 #define LINE_INFO \
893 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
894 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
895 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
896 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
897 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
898 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
899 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
900 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
901 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
907 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
908 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
909 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
910 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
912 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
914 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
915 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
916 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
917 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
918 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
919 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
922 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
923 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
924 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
925 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
926 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
927 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
928 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
929 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
930 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
931 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
932 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
933 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
934 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
935 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
936 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
937 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
938 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
939 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
940 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
941 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
943 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
944 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
945 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
946 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
947 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
949 enum line_type {
950 #define LINE(type, line, fg, bg, attr) \
951 LINE_##type
952 LINE_INFO,
953 LINE_NONE
954 #undef LINE
957 struct line_info {
958 const char *name; /* Option name. */
959 int namelen; /* Size of option name. */
960 const char *line; /* The start of line to match. */
961 int linelen; /* Size of string to match. */
962 int fg, bg, attr; /* Color and text attributes for the lines. */
965 static struct line_info line_info[] = {
966 #define LINE(type, line, fg, bg, attr) \
967 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
968 LINE_INFO
969 #undef LINE
972 static enum line_type
973 get_line_type(const char *line)
975 int linelen = strlen(line);
976 enum line_type type;
978 for (type = 0; type < ARRAY_SIZE(line_info); type++)
979 /* Case insensitive search matches Signed-off-by lines better. */
980 if (linelen >= line_info[type].linelen &&
981 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
982 return type;
984 return LINE_DEFAULT;
987 static inline int
988 get_line_attr(enum line_type type)
990 assert(type < ARRAY_SIZE(line_info));
991 return COLOR_PAIR(type) | line_info[type].attr;
994 static struct line_info *
995 get_line_info(const char *name)
997 size_t namelen = strlen(name);
998 enum line_type type;
1000 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1001 if (namelen == line_info[type].namelen &&
1002 !string_enum_compare(line_info[type].name, name, namelen))
1003 return &line_info[type];
1005 return NULL;
1008 static void
1009 init_colors(void)
1011 int default_bg = line_info[LINE_DEFAULT].bg;
1012 int default_fg = line_info[LINE_DEFAULT].fg;
1013 enum line_type type;
1015 start_color();
1017 if (assume_default_colors(default_fg, default_bg) == ERR) {
1018 default_bg = COLOR_BLACK;
1019 default_fg = COLOR_WHITE;
1022 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1023 struct line_info *info = &line_info[type];
1024 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1025 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1027 init_pair(type, fg, bg);
1031 struct line {
1032 enum line_type type;
1034 /* State flags */
1035 unsigned int selected:1;
1036 unsigned int dirty:1;
1037 unsigned int cleareol:1;
1039 void *data; /* User data */
1044 * Keys
1047 struct keybinding {
1048 int alias;
1049 enum request request;
1052 static const struct keybinding default_keybindings[] = {
1053 /* View switching */
1054 { 'm', REQ_VIEW_MAIN },
1055 { 'd', REQ_VIEW_DIFF },
1056 { 'l', REQ_VIEW_LOG },
1057 { 't', REQ_VIEW_TREE },
1058 { 'f', REQ_VIEW_BLOB },
1059 { 'B', REQ_VIEW_BLAME },
1060 { 'p', REQ_VIEW_PAGER },
1061 { 'h', REQ_VIEW_HELP },
1062 { 'S', REQ_VIEW_STATUS },
1063 { 'c', REQ_VIEW_STAGE },
1065 /* View manipulation */
1066 { 'q', REQ_VIEW_CLOSE },
1067 { KEY_TAB, REQ_VIEW_NEXT },
1068 { KEY_RETURN, REQ_ENTER },
1069 { KEY_UP, REQ_PREVIOUS },
1070 { KEY_DOWN, REQ_NEXT },
1071 { 'R', REQ_REFRESH },
1072 { KEY_F(5), REQ_REFRESH },
1073 { 'O', REQ_MAXIMIZE },
1075 /* Cursor navigation */
1076 { 'k', REQ_MOVE_UP },
1077 { 'j', REQ_MOVE_DOWN },
1078 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1079 { KEY_END, REQ_MOVE_LAST_LINE },
1080 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1081 { ' ', REQ_MOVE_PAGE_DOWN },
1082 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1083 { 'b', REQ_MOVE_PAGE_UP },
1084 { '-', REQ_MOVE_PAGE_UP },
1086 /* Scrolling */
1087 { KEY_LEFT, REQ_SCROLL_LEFT },
1088 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1089 { KEY_IC, REQ_SCROLL_LINE_UP },
1090 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1091 { 'w', REQ_SCROLL_PAGE_UP },
1092 { 's', REQ_SCROLL_PAGE_DOWN },
1094 /* Searching */
1095 { '/', REQ_SEARCH },
1096 { '?', REQ_SEARCH_BACK },
1097 { 'n', REQ_FIND_NEXT },
1098 { 'N', REQ_FIND_PREV },
1100 /* Misc */
1101 { 'Q', REQ_QUIT },
1102 { 'z', REQ_STOP_LOADING },
1103 { 'v', REQ_SHOW_VERSION },
1104 { 'r', REQ_SCREEN_REDRAW },
1105 { '.', REQ_TOGGLE_LINENO },
1106 { 'D', REQ_TOGGLE_DATE },
1107 { 'A', REQ_TOGGLE_AUTHOR },
1108 { 'g', REQ_TOGGLE_REV_GRAPH },
1109 { 'F', REQ_TOGGLE_REFS },
1110 { ':', REQ_PROMPT },
1111 { 'u', REQ_STATUS_UPDATE },
1112 { '!', REQ_STATUS_REVERT },
1113 { 'M', REQ_STATUS_MERGE },
1114 { '@', REQ_STAGE_NEXT },
1115 { ',', REQ_PARENT },
1116 { 'e', REQ_EDIT },
1119 #define KEYMAP_INFO \
1120 KEYMAP_(GENERIC), \
1121 KEYMAP_(MAIN), \
1122 KEYMAP_(DIFF), \
1123 KEYMAP_(LOG), \
1124 KEYMAP_(TREE), \
1125 KEYMAP_(BLOB), \
1126 KEYMAP_(BLAME), \
1127 KEYMAP_(PAGER), \
1128 KEYMAP_(HELP), \
1129 KEYMAP_(STATUS), \
1130 KEYMAP_(STAGE)
1132 enum keymap {
1133 #define KEYMAP_(name) KEYMAP_##name
1134 KEYMAP_INFO
1135 #undef KEYMAP_
1138 static const struct enum_map keymap_table[] = {
1139 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1140 KEYMAP_INFO
1141 #undef KEYMAP_
1144 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1146 struct keybinding_table {
1147 struct keybinding *data;
1148 size_t size;
1151 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1153 static void
1154 add_keybinding(enum keymap keymap, enum request request, int key)
1156 struct keybinding_table *table = &keybindings[keymap];
1158 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1159 if (!table->data)
1160 die("Failed to allocate keybinding");
1161 table->data[table->size].alias = key;
1162 table->data[table->size++].request = request;
1165 /* Looks for a key binding first in the given map, then in the generic map, and
1166 * lastly in the default keybindings. */
1167 static enum request
1168 get_keybinding(enum keymap keymap, int key)
1170 size_t i;
1172 for (i = 0; i < keybindings[keymap].size; i++)
1173 if (keybindings[keymap].data[i].alias == key)
1174 return keybindings[keymap].data[i].request;
1176 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1177 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1178 return keybindings[KEYMAP_GENERIC].data[i].request;
1180 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1181 if (default_keybindings[i].alias == key)
1182 return default_keybindings[i].request;
1184 return (enum request) key;
1188 struct key {
1189 const char *name;
1190 int value;
1193 static const struct key key_table[] = {
1194 { "Enter", KEY_RETURN },
1195 { "Space", ' ' },
1196 { "Backspace", KEY_BACKSPACE },
1197 { "Tab", KEY_TAB },
1198 { "Escape", KEY_ESC },
1199 { "Left", KEY_LEFT },
1200 { "Right", KEY_RIGHT },
1201 { "Up", KEY_UP },
1202 { "Down", KEY_DOWN },
1203 { "Insert", KEY_IC },
1204 { "Delete", KEY_DC },
1205 { "Hash", '#' },
1206 { "Home", KEY_HOME },
1207 { "End", KEY_END },
1208 { "PageUp", KEY_PPAGE },
1209 { "PageDown", KEY_NPAGE },
1210 { "F1", KEY_F(1) },
1211 { "F2", KEY_F(2) },
1212 { "F3", KEY_F(3) },
1213 { "F4", KEY_F(4) },
1214 { "F5", KEY_F(5) },
1215 { "F6", KEY_F(6) },
1216 { "F7", KEY_F(7) },
1217 { "F8", KEY_F(8) },
1218 { "F9", KEY_F(9) },
1219 { "F10", KEY_F(10) },
1220 { "F11", KEY_F(11) },
1221 { "F12", KEY_F(12) },
1224 static int
1225 get_key_value(const char *name)
1227 int i;
1229 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1230 if (!strcasecmp(key_table[i].name, name))
1231 return key_table[i].value;
1233 if (strlen(name) == 1 && isprint(*name))
1234 return (int) *name;
1236 return ERR;
1239 static const char *
1240 get_key_name(int key_value)
1242 static char key_char[] = "'X'";
1243 const char *seq = NULL;
1244 int key;
1246 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1247 if (key_table[key].value == key_value)
1248 seq = key_table[key].name;
1250 if (seq == NULL &&
1251 key_value < 127 &&
1252 isprint(key_value)) {
1253 key_char[1] = (char) key_value;
1254 seq = key_char;
1257 return seq ? seq : "(no key)";
1260 static const char *
1261 get_key(enum request request)
1263 static char buf[BUFSIZ];
1264 size_t pos = 0;
1265 char *sep = "";
1266 int i;
1268 buf[pos] = 0;
1270 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1271 const struct keybinding *keybinding = &default_keybindings[i];
1273 if (keybinding->request != request)
1274 continue;
1276 if (!string_format_from(buf, &pos, "%s%s", sep,
1277 get_key_name(keybinding->alias)))
1278 return "Too many keybindings!";
1279 sep = ", ";
1282 return buf;
1285 struct run_request {
1286 enum keymap keymap;
1287 int key;
1288 const char *argv[SIZEOF_ARG];
1291 static struct run_request *run_request;
1292 static size_t run_requests;
1294 static enum request
1295 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1297 struct run_request *req;
1299 if (argc >= ARRAY_SIZE(req->argv) - 1)
1300 return REQ_NONE;
1302 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1303 if (!req)
1304 return REQ_NONE;
1306 run_request = req;
1307 req = &run_request[run_requests];
1308 req->keymap = keymap;
1309 req->key = key;
1310 req->argv[0] = NULL;
1312 if (!format_argv(req->argv, argv, FORMAT_NONE))
1313 return REQ_NONE;
1315 return REQ_NONE + ++run_requests;
1318 static struct run_request *
1319 get_run_request(enum request request)
1321 if (request <= REQ_NONE)
1322 return NULL;
1323 return &run_request[request - REQ_NONE - 1];
1326 static void
1327 add_builtin_run_requests(void)
1329 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1330 const char *gc[] = { "git", "gc", NULL };
1331 struct {
1332 enum keymap keymap;
1333 int key;
1334 int argc;
1335 const char **argv;
1336 } reqs[] = {
1337 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1338 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1340 int i;
1342 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1343 enum request req;
1345 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1346 if (req != REQ_NONE)
1347 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1352 * User config file handling.
1355 static int config_lineno;
1356 static bool config_errors;
1357 static const char *config_msg;
1359 static const struct enum_map color_map[] = {
1360 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1361 COLOR_MAP(DEFAULT),
1362 COLOR_MAP(BLACK),
1363 COLOR_MAP(BLUE),
1364 COLOR_MAP(CYAN),
1365 COLOR_MAP(GREEN),
1366 COLOR_MAP(MAGENTA),
1367 COLOR_MAP(RED),
1368 COLOR_MAP(WHITE),
1369 COLOR_MAP(YELLOW),
1372 static const struct enum_map attr_map[] = {
1373 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1374 ATTR_MAP(NORMAL),
1375 ATTR_MAP(BLINK),
1376 ATTR_MAP(BOLD),
1377 ATTR_MAP(DIM),
1378 ATTR_MAP(REVERSE),
1379 ATTR_MAP(STANDOUT),
1380 ATTR_MAP(UNDERLINE),
1383 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1385 static int
1386 parse_int(int *opt, const char *arg, int min, int max)
1388 int value = atoi(arg);
1390 if (min <= value && value <= max) {
1391 *opt = value;
1392 return OK;
1395 config_msg = "Integer value out of bound";
1396 return ERR;
1399 static bool
1400 set_color(int *color, const char *name)
1402 if (map_enum(color, color_map, name))
1403 return TRUE;
1404 if (!prefixcmp(name, "color"))
1405 return parse_int(color, name + 5, 0, 255) == OK;
1406 return FALSE;
1409 /* Wants: object fgcolor bgcolor [attribute] */
1410 static int
1411 option_color_command(int argc, const char *argv[])
1413 struct line_info *info;
1415 if (argc != 3 && argc != 4) {
1416 config_msg = "Wrong number of arguments given to color command";
1417 return ERR;
1420 info = get_line_info(argv[0]);
1421 if (!info) {
1422 static const struct enum_map obsolete[] = {
1423 ENUM_MAP("main-delim", LINE_DELIMITER),
1424 ENUM_MAP("main-date", LINE_DATE),
1425 ENUM_MAP("main-author", LINE_AUTHOR),
1427 int index;
1429 if (!map_enum(&index, obsolete, argv[0])) {
1430 config_msg = "Unknown color name";
1431 return ERR;
1433 info = &line_info[index];
1436 if (!set_color(&info->fg, argv[1]) ||
1437 !set_color(&info->bg, argv[2])) {
1438 config_msg = "Unknown color";
1439 return ERR;
1442 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1443 config_msg = "Unknown attribute";
1444 return ERR;
1447 return OK;
1450 static int parse_bool(bool *opt, const char *arg)
1452 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1453 ? TRUE : FALSE;
1454 return OK;
1457 static int
1458 parse_string(char *opt, const char *arg, size_t optsize)
1460 int arglen = strlen(arg);
1462 switch (arg[0]) {
1463 case '\"':
1464 case '\'':
1465 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1466 config_msg = "Unmatched quotation";
1467 return ERR;
1469 arg += 1; arglen -= 2;
1470 default:
1471 string_ncopy_do(opt, optsize, arg, strlen(arg));
1472 return OK;
1476 /* Wants: name = value */
1477 static int
1478 option_set_command(int argc, const char *argv[])
1480 if (argc != 3) {
1481 config_msg = "Wrong number of arguments given to set command";
1482 return ERR;
1485 if (strcmp(argv[1], "=")) {
1486 config_msg = "No value assigned";
1487 return ERR;
1490 if (!strcmp(argv[0], "show-author"))
1491 return parse_bool(&opt_author, argv[2]);
1493 if (!strcmp(argv[0], "show-date"))
1494 return parse_bool(&opt_date, argv[2]);
1496 if (!strcmp(argv[0], "show-rev-graph"))
1497 return parse_bool(&opt_rev_graph, argv[2]);
1499 if (!strcmp(argv[0], "show-refs"))
1500 return parse_bool(&opt_show_refs, argv[2]);
1502 if (!strcmp(argv[0], "show-line-numbers"))
1503 return parse_bool(&opt_line_number, argv[2]);
1505 if (!strcmp(argv[0], "line-graphics"))
1506 return parse_bool(&opt_line_graphics, argv[2]);
1508 if (!strcmp(argv[0], "line-number-interval"))
1509 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1511 if (!strcmp(argv[0], "author-width"))
1512 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1514 if (!strcmp(argv[0], "tab-size"))
1515 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1517 if (!strcmp(argv[0], "commit-encoding"))
1518 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1520 config_msg = "Unknown variable name";
1521 return ERR;
1524 /* Wants: mode request key */
1525 static int
1526 option_bind_command(int argc, const char *argv[])
1528 enum request request;
1529 int keymap;
1530 int key;
1532 if (argc < 3) {
1533 config_msg = "Wrong number of arguments given to bind command";
1534 return ERR;
1537 if (set_keymap(&keymap, argv[0]) == ERR) {
1538 config_msg = "Unknown key map";
1539 return ERR;
1542 key = get_key_value(argv[1]);
1543 if (key == ERR) {
1544 config_msg = "Unknown key";
1545 return ERR;
1548 request = get_request(argv[2]);
1549 if (request == REQ_NONE) {
1550 static const struct enum_map obsolete[] = {
1551 ENUM_MAP("cherry-pick", REQ_NONE),
1552 ENUM_MAP("screen-resize", REQ_NONE),
1553 ENUM_MAP("tree-parent", REQ_PARENT),
1555 int alias;
1557 if (map_enum(&alias, obsolete, argv[2])) {
1558 if (alias != REQ_NONE)
1559 add_keybinding(keymap, alias, key);
1560 config_msg = "Obsolete request name";
1561 return ERR;
1564 if (request == REQ_NONE && *argv[2]++ == '!')
1565 request = add_run_request(keymap, key, argc - 2, argv + 2);
1566 if (request == REQ_NONE) {
1567 config_msg = "Unknown request name";
1568 return ERR;
1571 add_keybinding(keymap, request, key);
1573 return OK;
1576 static int
1577 set_option(const char *opt, char *value)
1579 const char *argv[SIZEOF_ARG];
1580 int argc = 0;
1582 if (!argv_from_string(argv, &argc, value)) {
1583 config_msg = "Too many option arguments";
1584 return ERR;
1587 if (!strcmp(opt, "color"))
1588 return option_color_command(argc, argv);
1590 if (!strcmp(opt, "set"))
1591 return option_set_command(argc, argv);
1593 if (!strcmp(opt, "bind"))
1594 return option_bind_command(argc, argv);
1596 config_msg = "Unknown option command";
1597 return ERR;
1600 static int
1601 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1603 int status = OK;
1605 config_lineno++;
1606 config_msg = "Internal error";
1608 /* Check for comment markers, since read_properties() will
1609 * only ensure opt and value are split at first " \t". */
1610 optlen = strcspn(opt, "#");
1611 if (optlen == 0)
1612 return OK;
1614 if (opt[optlen] != 0) {
1615 config_msg = "No option value";
1616 status = ERR;
1618 } else {
1619 /* Look for comment endings in the value. */
1620 size_t len = strcspn(value, "#");
1622 if (len < valuelen) {
1623 valuelen = len;
1624 value[valuelen] = 0;
1627 status = set_option(opt, value);
1630 if (status == ERR) {
1631 warn("Error on line %d, near '%.*s': %s",
1632 config_lineno, (int) optlen, opt, config_msg);
1633 config_errors = TRUE;
1636 /* Always keep going if errors are encountered. */
1637 return OK;
1640 static void
1641 load_option_file(const char *path)
1643 struct io io = {};
1645 /* It's OK that the file doesn't exist. */
1646 if (!io_open(&io, path))
1647 return;
1649 config_lineno = 0;
1650 config_errors = FALSE;
1652 if (io_load(&io, " \t", read_option) == ERR ||
1653 config_errors == TRUE)
1654 warn("Errors while loading %s.", path);
1657 static int
1658 load_options(void)
1660 const char *home = getenv("HOME");
1661 const char *tigrc_user = getenv("TIGRC_USER");
1662 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1663 char buf[SIZEOF_STR];
1665 add_builtin_run_requests();
1667 if (!tigrc_system)
1668 tigrc_system = SYSCONFDIR "/tigrc";
1669 load_option_file(tigrc_system);
1671 if (!tigrc_user) {
1672 if (!home || !string_format(buf, "%s/.tigrc", home))
1673 return ERR;
1674 tigrc_user = buf;
1676 load_option_file(tigrc_user);
1678 return OK;
1683 * The viewer
1686 struct view;
1687 struct view_ops;
1689 /* The display array of active views and the index of the current view. */
1690 static struct view *display[2];
1691 static unsigned int current_view;
1693 #define foreach_displayed_view(view, i) \
1694 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1696 #define displayed_views() (display[1] != NULL ? 2 : 1)
1698 /* Current head and commit ID */
1699 static char ref_blob[SIZEOF_REF] = "";
1700 static char ref_commit[SIZEOF_REF] = "HEAD";
1701 static char ref_head[SIZEOF_REF] = "HEAD";
1703 struct view {
1704 const char *name; /* View name */
1705 const char *cmd_env; /* Command line set via environment */
1706 const char *id; /* Points to either of ref_{head,commit,blob} */
1708 struct view_ops *ops; /* View operations */
1710 enum keymap keymap; /* What keymap does this view have */
1711 bool git_dir; /* Whether the view requires a git directory. */
1713 char ref[SIZEOF_REF]; /* Hovered commit reference */
1714 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1716 int height, width; /* The width and height of the main window */
1717 WINDOW *win; /* The main window */
1718 WINDOW *title; /* The title window living below the main window */
1720 /* Navigation */
1721 unsigned long offset; /* Offset of the window top */
1722 unsigned long yoffset; /* Offset from the window side. */
1723 unsigned long lineno; /* Current line number */
1724 unsigned long p_offset; /* Previous offset of the window top */
1725 unsigned long p_yoffset;/* Previous offset from the window side */
1726 unsigned long p_lineno; /* Previous current line number */
1727 bool p_restore; /* Should the previous position be restored. */
1729 /* Searching */
1730 char grep[SIZEOF_STR]; /* Search string */
1731 regex_t *regex; /* Pre-compiled regexp */
1733 /* If non-NULL, points to the view that opened this view. If this view
1734 * is closed tig will switch back to the parent view. */
1735 struct view *parent;
1737 /* Buffering */
1738 size_t lines; /* Total number of lines */
1739 struct line *line; /* Line index */
1740 size_t line_alloc; /* Total number of allocated lines */
1741 unsigned int digits; /* Number of digits in the lines member. */
1743 /* Drawing */
1744 struct line *curline; /* Line currently being drawn. */
1745 enum line_type curtype; /* Attribute currently used for drawing. */
1746 unsigned long col; /* Column when drawing. */
1747 bool has_scrolled; /* View was scrolled. */
1748 bool can_hscroll; /* View can be scrolled horizontally. */
1750 /* Loading */
1751 struct io io;
1752 struct io *pipe;
1753 time_t start_time;
1754 time_t update_secs;
1757 struct view_ops {
1758 /* What type of content being displayed. Used in the title bar. */
1759 const char *type;
1760 /* Default command arguments. */
1761 const char **argv;
1762 /* Open and reads in all view content. */
1763 bool (*open)(struct view *view);
1764 /* Read one line; updates view->line. */
1765 bool (*read)(struct view *view, char *data);
1766 /* Draw one line; @lineno must be < view->height. */
1767 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1768 /* Depending on view handle a special requests. */
1769 enum request (*request)(struct view *view, enum request request, struct line *line);
1770 /* Search for regexp in a line. */
1771 bool (*grep)(struct view *view, struct line *line);
1772 /* Select line */
1773 void (*select)(struct view *view, struct line *line);
1776 static struct view_ops blame_ops;
1777 static struct view_ops blob_ops;
1778 static struct view_ops diff_ops;
1779 static struct view_ops help_ops;
1780 static struct view_ops log_ops;
1781 static struct view_ops main_ops;
1782 static struct view_ops pager_ops;
1783 static struct view_ops stage_ops;
1784 static struct view_ops status_ops;
1785 static struct view_ops tree_ops;
1787 #define VIEW_STR(name, env, ref, ops, map, git) \
1788 { name, #env, ref, ops, map, git }
1790 #define VIEW_(id, name, ops, git, ref) \
1791 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1794 static struct view views[] = {
1795 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1796 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1797 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1798 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1799 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1800 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1801 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1802 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1803 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1804 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1807 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1808 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1810 #define foreach_view(view, i) \
1811 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1813 #define view_is_displayed(view) \
1814 (view == display[0] || view == display[1])
1817 enum line_graphic {
1818 LINE_GRAPHIC_VLINE
1821 static int line_graphics[] = {
1822 /* LINE_GRAPHIC_VLINE: */ '|'
1825 static inline void
1826 set_view_attr(struct view *view, enum line_type type)
1828 if (!view->curline->selected && view->curtype != type) {
1829 wattrset(view->win, get_line_attr(type));
1830 wchgat(view->win, -1, 0, type, NULL);
1831 view->curtype = type;
1835 static int
1836 draw_chars(struct view *view, enum line_type type, const char *string,
1837 int max_len, bool use_tilde)
1839 int len = 0;
1840 int col = 0;
1841 int trimmed = FALSE;
1842 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1844 if (max_len <= 0)
1845 return 0;
1847 if (opt_utf8) {
1848 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1849 } else {
1850 col = len = strlen(string);
1851 if (len > max_len) {
1852 if (use_tilde) {
1853 max_len -= 1;
1855 col = len = max_len;
1856 trimmed = TRUE;
1860 set_view_attr(view, type);
1861 if (len > 0)
1862 waddnstr(view->win, string, len);
1863 if (trimmed && use_tilde) {
1864 set_view_attr(view, LINE_DELIMITER);
1865 waddch(view->win, '~');
1866 col++;
1869 if (view->col + col >= view->width + view->yoffset)
1870 view->can_hscroll = TRUE;
1872 return col;
1875 static int
1876 draw_space(struct view *view, enum line_type type, int max, int spaces)
1878 static char space[] = " ";
1879 int col = 0;
1881 spaces = MIN(max, spaces);
1883 while (spaces > 0) {
1884 int len = MIN(spaces, sizeof(space) - 1);
1886 col += draw_chars(view, type, space, spaces, FALSE);
1887 spaces -= len;
1890 return col;
1893 static bool
1894 draw_lineno(struct view *view, unsigned int lineno)
1896 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1897 char number[10];
1898 int digits3 = view->digits < 3 ? 3 : view->digits;
1899 int max_number = MIN(digits3, STRING_SIZE(number));
1900 int max = view->width - view->col;
1901 int col;
1903 if (max < max_number)
1904 max_number = max;
1906 lineno += view->offset + 1;
1907 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1908 static char fmt[] = "%1ld";
1910 if (view->digits <= 9)
1911 fmt[1] = '0' + digits3;
1913 if (!string_format(number, fmt, lineno))
1914 number[0] = 0;
1915 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1916 } else {
1917 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1920 if (col < max && skip <= col) {
1921 set_view_attr(view, LINE_DEFAULT);
1922 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1924 col++;
1926 view->col += col;
1927 if (col < max && skip <= col)
1928 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1929 view->col++;
1931 return view->width + view->yoffset <= view->col;
1934 static bool
1935 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1937 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1938 return view->width - view->col <= 0;
1941 static bool
1942 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1944 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1945 int max = view->width - view->col;
1946 int i;
1948 if (max < size)
1949 size = max;
1951 set_view_attr(view, type);
1952 /* Using waddch() instead of waddnstr() ensures that
1953 * they'll be rendered correctly for the cursor line. */
1954 for (i = skip; i < size; i++)
1955 waddch(view->win, graphic[i]);
1957 view->col += size;
1958 if (size < max && skip <= size)
1959 waddch(view->win, ' ');
1960 view->col++;
1962 return view->width - view->col <= 0;
1965 static bool
1966 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1968 int max = MIN(view->width - view->col, len);
1969 int col;
1971 if (text)
1972 col = draw_chars(view, type, text, max - 1, trim);
1973 else
1974 col = draw_space(view, type, max - 1, max - 1);
1976 view->col += col;
1977 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1978 return view->width + view->yoffset <= view->col;
1981 static bool
1982 draw_date(struct view *view, struct tm *time)
1984 char buf[DATE_COLS];
1985 char *date;
1986 int timelen = 0;
1988 if (time)
1989 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1990 date = timelen ? buf : NULL;
1992 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1995 static bool
1996 draw_author(struct view *view, const char *author)
1998 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2000 if (!trim) {
2001 static char initials[10];
2002 size_t pos;
2004 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2006 memset(initials, 0, sizeof(initials));
2007 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2008 while (is_initial_sep(*author))
2009 author++;
2010 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2011 while (*author && !is_initial_sep(author[1]))
2012 author++;
2015 author = initials;
2018 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2021 static bool
2022 draw_mode(struct view *view, mode_t mode)
2024 static const char dir_mode[] = "drwxr-xr-x";
2025 static const char link_mode[] = "lrwxrwxrwx";
2026 static const char exe_mode[] = "-rwxr-xr-x";
2027 static const char file_mode[] = "-rw-r--r--";
2028 const char *str;
2030 if (S_ISDIR(mode))
2031 str = dir_mode;
2032 else if (S_ISLNK(mode))
2033 str = link_mode;
2034 else if (mode & S_IXUSR)
2035 str = exe_mode;
2036 else
2037 str = file_mode;
2039 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2042 static bool
2043 draw_view_line(struct view *view, unsigned int lineno)
2045 struct line *line;
2046 bool selected = (view->offset + lineno == view->lineno);
2048 assert(view_is_displayed(view));
2050 if (view->offset + lineno >= view->lines)
2051 return FALSE;
2053 line = &view->line[view->offset + lineno];
2055 wmove(view->win, lineno, 0);
2056 if (line->cleareol)
2057 wclrtoeol(view->win);
2058 view->col = 0;
2059 view->curline = line;
2060 view->curtype = LINE_NONE;
2061 line->selected = FALSE;
2062 line->dirty = line->cleareol = 0;
2064 if (selected) {
2065 set_view_attr(view, LINE_CURSOR);
2066 line->selected = TRUE;
2067 view->ops->select(view, line);
2070 return view->ops->draw(view, line, lineno);
2073 static void
2074 redraw_view_dirty(struct view *view)
2076 bool dirty = FALSE;
2077 int lineno;
2079 for (lineno = 0; lineno < view->height; lineno++) {
2080 if (view->offset + lineno >= view->lines)
2081 break;
2082 if (!view->line[view->offset + lineno].dirty)
2083 continue;
2084 dirty = TRUE;
2085 if (!draw_view_line(view, lineno))
2086 break;
2089 if (!dirty)
2090 return;
2091 wnoutrefresh(view->win);
2094 static void
2095 redraw_view_from(struct view *view, int lineno)
2097 assert(0 <= lineno && lineno < view->height);
2099 if (lineno == 0)
2100 view->can_hscroll = FALSE;
2102 for (; lineno < view->height; lineno++) {
2103 if (!draw_view_line(view, lineno))
2104 break;
2107 wnoutrefresh(view->win);
2110 static void
2111 redraw_view(struct view *view)
2113 werase(view->win);
2114 redraw_view_from(view, 0);
2118 static void
2119 update_view_title(struct view *view)
2121 char buf[SIZEOF_STR];
2122 char state[SIZEOF_STR];
2123 size_t bufpos = 0, statelen = 0;
2125 assert(view_is_displayed(view));
2127 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2128 unsigned int view_lines = view->offset + view->height;
2129 unsigned int lines = view->lines
2130 ? MIN(view_lines, view->lines) * 100 / view->lines
2131 : 0;
2133 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2134 view->ops->type,
2135 view->lineno + 1,
2136 view->lines,
2137 lines);
2141 if (view->pipe) {
2142 time_t secs = time(NULL) - view->start_time;
2144 /* Three git seconds are a long time ... */
2145 if (secs > 2)
2146 string_format_from(state, &statelen, " loading %lds", secs);
2149 string_format_from(buf, &bufpos, "[%s]", view->name);
2150 if (*view->ref && bufpos < view->width) {
2151 size_t refsize = strlen(view->ref);
2152 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2154 if (minsize < view->width)
2155 refsize = view->width - minsize + 7;
2156 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2159 if (statelen && bufpos < view->width) {
2160 string_format_from(buf, &bufpos, "%s", state);
2163 if (view == display[current_view])
2164 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2165 else
2166 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2168 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2169 wclrtoeol(view->title);
2170 wnoutrefresh(view->title);
2173 static void
2174 resize_display(void)
2176 int offset, i;
2177 struct view *base = display[0];
2178 struct view *view = display[1] ? display[1] : display[0];
2180 /* Setup window dimensions */
2182 getmaxyx(stdscr, base->height, base->width);
2184 /* Make room for the status window. */
2185 base->height -= 1;
2187 if (view != base) {
2188 /* Horizontal split. */
2189 view->width = base->width;
2190 view->height = SCALE_SPLIT_VIEW(base->height);
2191 base->height -= view->height;
2193 /* Make room for the title bar. */
2194 view->height -= 1;
2197 /* Make room for the title bar. */
2198 base->height -= 1;
2200 offset = 0;
2202 foreach_displayed_view (view, i) {
2203 if (!view->win) {
2204 view->win = newwin(view->height, 0, offset, 0);
2205 if (!view->win)
2206 die("Failed to create %s view", view->name);
2208 scrollok(view->win, FALSE);
2210 view->title = newwin(1, 0, offset + view->height, 0);
2211 if (!view->title)
2212 die("Failed to create title window");
2214 } else {
2215 wresize(view->win, view->height, view->width);
2216 mvwin(view->win, offset, 0);
2217 mvwin(view->title, offset + view->height, 0);
2220 offset += view->height + 1;
2224 static void
2225 redraw_display(bool clear)
2227 struct view *view;
2228 int i;
2230 foreach_displayed_view (view, i) {
2231 if (clear)
2232 wclear(view->win);
2233 redraw_view(view);
2234 update_view_title(view);
2238 static void
2239 toggle_view_option(bool *option, const char *help)
2241 *option = !*option;
2242 redraw_display(FALSE);
2243 report("%sabling %s", *option ? "En" : "Dis", help);
2246 static void
2247 maximize_view(struct view *view)
2249 memset(display, 0, sizeof(display));
2250 current_view = 0;
2251 display[current_view] = view;
2252 resize_display();
2253 redraw_display(FALSE);
2254 report("");
2259 * Navigation
2262 static bool
2263 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2265 if (lineno >= view->lines)
2266 lineno = view->lines > 0 ? view->lines - 1 : 0;
2268 if (offset > lineno || offset + view->height <= lineno) {
2269 unsigned long half = view->height / 2;
2271 if (lineno > half)
2272 offset = lineno - half;
2273 else
2274 offset = 0;
2277 if (offset != view->offset || lineno != view->lineno) {
2278 view->offset = offset;
2279 view->lineno = lineno;
2280 return TRUE;
2283 return FALSE;
2286 /* Scrolling backend */
2287 static void
2288 do_scroll_view(struct view *view, int lines)
2290 bool redraw_current_line = FALSE;
2292 /* The rendering expects the new offset. */
2293 view->offset += lines;
2295 assert(0 <= view->offset && view->offset < view->lines);
2296 assert(lines);
2298 /* Move current line into the view. */
2299 if (view->lineno < view->offset) {
2300 view->lineno = view->offset;
2301 redraw_current_line = TRUE;
2302 } else if (view->lineno >= view->offset + view->height) {
2303 view->lineno = view->offset + view->height - 1;
2304 redraw_current_line = TRUE;
2307 assert(view->offset <= view->lineno && view->lineno < view->lines);
2309 /* Redraw the whole screen if scrolling is pointless. */
2310 if (view->height < ABS(lines)) {
2311 redraw_view(view);
2313 } else {
2314 int line = lines > 0 ? view->height - lines : 0;
2315 int end = line + ABS(lines);
2317 scrollok(view->win, TRUE);
2318 wscrl(view->win, lines);
2319 scrollok(view->win, FALSE);
2321 while (line < end && draw_view_line(view, line))
2322 line++;
2324 if (redraw_current_line)
2325 draw_view_line(view, view->lineno - view->offset);
2326 wnoutrefresh(view->win);
2329 view->has_scrolled = TRUE;
2330 report("");
2333 /* Scroll frontend */
2334 static void
2335 scroll_view(struct view *view, enum request request)
2337 int lines = 1;
2339 assert(view_is_displayed(view));
2341 switch (request) {
2342 case REQ_SCROLL_LEFT:
2343 if (view->yoffset == 0) {
2344 report("Cannot scroll beyond the first column");
2345 return;
2347 if (view->yoffset <= SCROLL_INTERVAL)
2348 view->yoffset = 0;
2349 else
2350 view->yoffset -= SCROLL_INTERVAL;
2351 redraw_view_from(view, 0);
2352 report("");
2353 return;
2354 case REQ_SCROLL_RIGHT:
2355 if (!view->can_hscroll) {
2356 report("Cannot scroll beyond the last column");
2357 return;
2359 view->yoffset += SCROLL_INTERVAL;
2360 redraw_view(view);
2361 report("");
2362 return;
2363 case REQ_SCROLL_PAGE_DOWN:
2364 lines = view->height;
2365 case REQ_SCROLL_LINE_DOWN:
2366 if (view->offset + lines > view->lines)
2367 lines = view->lines - view->offset;
2369 if (lines == 0 || view->offset + view->height >= view->lines) {
2370 report("Cannot scroll beyond the last line");
2371 return;
2373 break;
2375 case REQ_SCROLL_PAGE_UP:
2376 lines = view->height;
2377 case REQ_SCROLL_LINE_UP:
2378 if (lines > view->offset)
2379 lines = view->offset;
2381 if (lines == 0) {
2382 report("Cannot scroll beyond the first line");
2383 return;
2386 lines = -lines;
2387 break;
2389 default:
2390 die("request %d not handled in switch", request);
2393 do_scroll_view(view, lines);
2396 /* Cursor moving */
2397 static void
2398 move_view(struct view *view, enum request request)
2400 int scroll_steps = 0;
2401 int steps;
2403 switch (request) {
2404 case REQ_MOVE_FIRST_LINE:
2405 steps = -view->lineno;
2406 break;
2408 case REQ_MOVE_LAST_LINE:
2409 steps = view->lines - view->lineno - 1;
2410 break;
2412 case REQ_MOVE_PAGE_UP:
2413 steps = view->height > view->lineno
2414 ? -view->lineno : -view->height;
2415 break;
2417 case REQ_MOVE_PAGE_DOWN:
2418 steps = view->lineno + view->height >= view->lines
2419 ? view->lines - view->lineno - 1 : view->height;
2420 break;
2422 case REQ_MOVE_UP:
2423 steps = -1;
2424 break;
2426 case REQ_MOVE_DOWN:
2427 steps = 1;
2428 break;
2430 default:
2431 die("request %d not handled in switch", request);
2434 if (steps <= 0 && view->lineno == 0) {
2435 report("Cannot move beyond the first line");
2436 return;
2438 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2439 report("Cannot move beyond the last line");
2440 return;
2443 /* Move the current line */
2444 view->lineno += steps;
2445 assert(0 <= view->lineno && view->lineno < view->lines);
2447 /* Check whether the view needs to be scrolled */
2448 if (view->lineno < view->offset ||
2449 view->lineno >= view->offset + view->height) {
2450 scroll_steps = steps;
2451 if (steps < 0 && -steps > view->offset) {
2452 scroll_steps = -view->offset;
2454 } else if (steps > 0) {
2455 if (view->lineno == view->lines - 1 &&
2456 view->lines > view->height) {
2457 scroll_steps = view->lines - view->offset - 1;
2458 if (scroll_steps >= view->height)
2459 scroll_steps -= view->height - 1;
2464 if (!view_is_displayed(view)) {
2465 view->offset += scroll_steps;
2466 assert(0 <= view->offset && view->offset < view->lines);
2467 view->ops->select(view, &view->line[view->lineno]);
2468 return;
2471 /* Repaint the old "current" line if we be scrolling */
2472 if (ABS(steps) < view->height)
2473 draw_view_line(view, view->lineno - steps - view->offset);
2475 if (scroll_steps) {
2476 do_scroll_view(view, scroll_steps);
2477 return;
2480 /* Draw the current line */
2481 draw_view_line(view, view->lineno - view->offset);
2483 wnoutrefresh(view->win);
2484 report("");
2489 * Searching
2492 static void search_view(struct view *view, enum request request);
2494 static void
2495 select_view_line(struct view *view, unsigned long lineno)
2497 unsigned long old_lineno = view->lineno;
2498 unsigned long old_offset = view->offset;
2500 if (goto_view_line(view, view->offset, lineno)) {
2501 if (view_is_displayed(view)) {
2502 if (old_offset != view->offset) {
2503 redraw_view(view);
2504 } else {
2505 draw_view_line(view, old_lineno - view->offset);
2506 draw_view_line(view, view->lineno - view->offset);
2507 wnoutrefresh(view->win);
2509 } else {
2510 view->ops->select(view, &view->line[view->lineno]);
2515 static void
2516 find_next(struct view *view, enum request request)
2518 unsigned long lineno = view->lineno;
2519 int direction;
2521 if (!*view->grep) {
2522 if (!*opt_search)
2523 report("No previous search");
2524 else
2525 search_view(view, request);
2526 return;
2529 switch (request) {
2530 case REQ_SEARCH:
2531 case REQ_FIND_NEXT:
2532 direction = 1;
2533 break;
2535 case REQ_SEARCH_BACK:
2536 case REQ_FIND_PREV:
2537 direction = -1;
2538 break;
2540 default:
2541 return;
2544 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2545 lineno += direction;
2547 /* Note, lineno is unsigned long so will wrap around in which case it
2548 * will become bigger than view->lines. */
2549 for (; lineno < view->lines; lineno += direction) {
2550 if (view->ops->grep(view, &view->line[lineno])) {
2551 select_view_line(view, lineno);
2552 report("Line %ld matches '%s'", lineno + 1, view->grep);
2553 return;
2557 report("No match found for '%s'", view->grep);
2560 static void
2561 search_view(struct view *view, enum request request)
2563 int regex_err;
2565 if (view->regex) {
2566 regfree(view->regex);
2567 *view->grep = 0;
2568 } else {
2569 view->regex = calloc(1, sizeof(*view->regex));
2570 if (!view->regex)
2571 return;
2574 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2575 if (regex_err != 0) {
2576 char buf[SIZEOF_STR] = "unknown error";
2578 regerror(regex_err, view->regex, buf, sizeof(buf));
2579 report("Search failed: %s", buf);
2580 return;
2583 string_copy(view->grep, opt_search);
2585 find_next(view, request);
2589 * Incremental updating
2592 static void
2593 reset_view(struct view *view)
2595 int i;
2597 for (i = 0; i < view->lines; i++)
2598 free(view->line[i].data);
2599 free(view->line);
2601 view->p_offset = view->offset;
2602 view->p_yoffset = view->yoffset;
2603 view->p_lineno = view->lineno;
2605 view->line = NULL;
2606 view->offset = 0;
2607 view->yoffset = 0;
2608 view->lines = 0;
2609 view->lineno = 0;
2610 view->line_alloc = 0;
2611 view->vid[0] = 0;
2612 view->update_secs = 0;
2615 static void
2616 free_argv(const char *argv[])
2618 int argc;
2620 for (argc = 0; argv[argc]; argc++)
2621 free((void *) argv[argc]);
2624 static bool
2625 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2627 char buf[SIZEOF_STR];
2628 int argc;
2629 bool noreplace = flags == FORMAT_NONE;
2631 free_argv(dst_argv);
2633 for (argc = 0; src_argv[argc]; argc++) {
2634 const char *arg = src_argv[argc];
2635 size_t bufpos = 0;
2637 while (arg) {
2638 char *next = strstr(arg, "%(");
2639 int len = next - arg;
2640 const char *value;
2642 if (!next || noreplace) {
2643 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2644 noreplace = TRUE;
2645 len = strlen(arg);
2646 value = "";
2648 } else if (!prefixcmp(next, "%(directory)")) {
2649 value = opt_path;
2651 } else if (!prefixcmp(next, "%(file)")) {
2652 value = opt_file;
2654 } else if (!prefixcmp(next, "%(ref)")) {
2655 value = *opt_ref ? opt_ref : "HEAD";
2657 } else if (!prefixcmp(next, "%(head)")) {
2658 value = ref_head;
2660 } else if (!prefixcmp(next, "%(commit)")) {
2661 value = ref_commit;
2663 } else if (!prefixcmp(next, "%(blob)")) {
2664 value = ref_blob;
2666 } else {
2667 report("Unknown replacement: `%s`", next);
2668 return FALSE;
2671 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2672 return FALSE;
2674 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2677 dst_argv[argc] = strdup(buf);
2678 if (!dst_argv[argc])
2679 break;
2682 dst_argv[argc] = NULL;
2684 return src_argv[argc] == NULL;
2687 static bool
2688 restore_view_position(struct view *view)
2690 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2691 return FALSE;
2693 /* Changing the view position cancels the restoring. */
2694 /* FIXME: Changing back to the first line is not detected. */
2695 if (view->offset != 0 || view->lineno != 0) {
2696 view->p_restore = FALSE;
2697 return FALSE;
2700 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2701 view_is_displayed(view))
2702 werase(view->win);
2704 view->yoffset = view->p_yoffset;
2705 view->p_restore = FALSE;
2707 return TRUE;
2710 static void
2711 end_update(struct view *view, bool force)
2713 if (!view->pipe)
2714 return;
2715 while (!view->ops->read(view, NULL))
2716 if (!force)
2717 return;
2718 set_nonblocking_input(FALSE);
2719 if (force)
2720 kill_io(view->pipe);
2721 done_io(view->pipe);
2722 view->pipe = NULL;
2725 static void
2726 setup_update(struct view *view, const char *vid)
2728 set_nonblocking_input(TRUE);
2729 reset_view(view);
2730 string_copy_rev(view->vid, vid);
2731 view->pipe = &view->io;
2732 view->start_time = time(NULL);
2735 static bool
2736 prepare_update(struct view *view, const char *argv[], const char *dir,
2737 enum format_flags flags)
2739 if (view->pipe)
2740 end_update(view, TRUE);
2741 return init_io_rd(&view->io, argv, dir, flags);
2744 static bool
2745 prepare_update_file(struct view *view, const char *name)
2747 if (view->pipe)
2748 end_update(view, TRUE);
2749 return io_open(&view->io, name);
2752 static bool
2753 begin_update(struct view *view, bool refresh)
2755 if (view->pipe)
2756 end_update(view, TRUE);
2758 if (refresh) {
2759 if (!start_io(&view->io))
2760 return FALSE;
2762 } else {
2763 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2764 opt_path[0] = 0;
2766 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2767 return FALSE;
2769 /* Put the current ref_* value to the view title ref
2770 * member. This is needed by the blob view. Most other
2771 * views sets it automatically after loading because the
2772 * first line is a commit line. */
2773 string_copy_rev(view->ref, view->id);
2776 setup_update(view, view->id);
2778 return TRUE;
2781 #define ITEM_CHUNK_SIZE 256
2782 static void *
2783 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2785 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2786 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2788 if (mem == NULL || num_chunks != num_chunks_new) {
2789 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2790 mem = realloc(mem, *size * item_size);
2793 return mem;
2796 static struct line *
2797 realloc_lines(struct view *view, size_t line_size)
2799 size_t alloc = view->line_alloc;
2800 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2801 sizeof(*view->line));
2803 if (!tmp)
2804 return NULL;
2806 view->line = tmp;
2807 view->line_alloc = alloc;
2808 return view->line;
2811 static bool
2812 update_view(struct view *view)
2814 char out_buffer[BUFSIZ * 2];
2815 char *line;
2816 /* Clear the view and redraw everything since the tree sorting
2817 * might have rearranged things. */
2818 bool redraw = view->lines == 0;
2819 bool can_read = TRUE;
2821 if (!view->pipe)
2822 return TRUE;
2824 if (!io_can_read(view->pipe)) {
2825 if (view->lines == 0) {
2826 time_t secs = time(NULL) - view->start_time;
2828 if (secs > 1 && secs > view->update_secs) {
2829 if (view->update_secs == 0)
2830 redraw_view(view);
2831 update_view_title(view);
2832 view->update_secs = secs;
2835 return TRUE;
2838 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2839 if (opt_iconv != ICONV_NONE) {
2840 ICONV_CONST char *inbuf = line;
2841 size_t inlen = strlen(line) + 1;
2843 char *outbuf = out_buffer;
2844 size_t outlen = sizeof(out_buffer);
2846 size_t ret;
2848 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2849 if (ret != (size_t) -1)
2850 line = out_buffer;
2853 if (!view->ops->read(view, line)) {
2854 report("Allocation failure");
2855 end_update(view, TRUE);
2856 return FALSE;
2861 unsigned long lines = view->lines;
2862 int digits;
2864 for (digits = 0; lines; digits++)
2865 lines /= 10;
2867 /* Keep the displayed view in sync with line number scaling. */
2868 if (digits != view->digits) {
2869 view->digits = digits;
2870 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2871 redraw = TRUE;
2875 if (io_error(view->pipe)) {
2876 report("Failed to read: %s", io_strerror(view->pipe));
2877 end_update(view, TRUE);
2879 } else if (io_eof(view->pipe)) {
2880 report("");
2881 end_update(view, FALSE);
2884 if (restore_view_position(view))
2885 redraw = TRUE;
2887 if (!view_is_displayed(view))
2888 return TRUE;
2890 if (redraw)
2891 redraw_view_from(view, 0);
2892 else
2893 redraw_view_dirty(view);
2895 /* Update the title _after_ the redraw so that if the redraw picks up a
2896 * commit reference in view->ref it'll be available here. */
2897 update_view_title(view);
2898 return TRUE;
2901 static struct line *
2902 add_line_data(struct view *view, void *data, enum line_type type)
2904 struct line *line;
2906 if (!realloc_lines(view, view->lines + 1))
2907 return NULL;
2909 line = &view->line[view->lines++];
2910 memset(line, 0, sizeof(*line));
2911 line->type = type;
2912 line->data = data;
2913 line->dirty = 1;
2915 return line;
2918 static struct line *
2919 add_line_text(struct view *view, const char *text, enum line_type type)
2921 char *data = text ? strdup(text) : NULL;
2923 return data ? add_line_data(view, data, type) : NULL;
2926 static struct line *
2927 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2929 char buf[SIZEOF_STR];
2930 va_list args;
2932 va_start(args, fmt);
2933 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2934 buf[0] = 0;
2935 va_end(args);
2937 return buf[0] ? add_line_text(view, buf, type) : NULL;
2941 * View opening
2944 enum open_flags {
2945 OPEN_DEFAULT = 0, /* Use default view switching. */
2946 OPEN_SPLIT = 1, /* Split current view. */
2947 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2948 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2949 OPEN_PREPARED = 32, /* Open already prepared command. */
2952 static void
2953 open_view(struct view *prev, enum request request, enum open_flags flags)
2955 bool split = !!(flags & OPEN_SPLIT);
2956 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2957 bool nomaximize = !!(flags & OPEN_REFRESH);
2958 struct view *view = VIEW(request);
2959 int nviews = displayed_views();
2960 struct view *base_view = display[0];
2962 if (view == prev && nviews == 1 && !reload) {
2963 report("Already in %s view", view->name);
2964 return;
2967 if (view->git_dir && !opt_git_dir[0]) {
2968 report("The %s view is disabled in pager view", view->name);
2969 return;
2972 if (split) {
2973 display[1] = view;
2974 current_view = 1;
2975 } else if (!nomaximize) {
2976 /* Maximize the current view. */
2977 memset(display, 0, sizeof(display));
2978 current_view = 0;
2979 display[current_view] = view;
2982 /* Resize the view when switching between split- and full-screen,
2983 * or when switching between two different full-screen views. */
2984 if (nviews != displayed_views() ||
2985 (nviews == 1 && base_view != display[0]))
2986 resize_display();
2988 if (view->ops->open) {
2989 if (view->pipe)
2990 end_update(view, TRUE);
2991 if (!view->ops->open(view)) {
2992 report("Failed to load %s view", view->name);
2993 return;
2995 restore_view_position(view);
2997 } else if ((reload || strcmp(view->vid, view->id)) &&
2998 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2999 report("Failed to load %s view", view->name);
3000 return;
3003 if (split && prev->lineno - prev->offset >= prev->height) {
3004 /* Take the title line into account. */
3005 int lines = prev->lineno - prev->offset - prev->height + 1;
3007 /* Scroll the view that was split if the current line is
3008 * outside the new limited view. */
3009 do_scroll_view(prev, lines);
3012 if (prev && view != prev) {
3013 if (split) {
3014 /* "Blur" the previous view. */
3015 update_view_title(prev);
3018 view->parent = prev;
3021 if (view->pipe && view->lines == 0) {
3022 /* Clear the old view and let the incremental updating refill
3023 * the screen. */
3024 werase(view->win);
3025 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3026 report("");
3027 } else if (view_is_displayed(view)) {
3028 redraw_view(view);
3029 report("");
3033 static void
3034 open_external_viewer(const char *argv[], const char *dir)
3036 def_prog_mode(); /* save current tty modes */
3037 endwin(); /* restore original tty modes */
3038 run_io_fg(argv, dir);
3039 fprintf(stderr, "Press Enter to continue");
3040 getc(opt_tty);
3041 reset_prog_mode();
3042 redraw_display(TRUE);
3045 static void
3046 open_mergetool(const char *file)
3048 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3050 open_external_viewer(mergetool_argv, opt_cdup);
3053 static void
3054 open_editor(bool from_root, const char *file)
3056 const char *editor_argv[] = { "vi", file, NULL };
3057 const char *editor;
3059 editor = getenv("GIT_EDITOR");
3060 if (!editor && *opt_editor)
3061 editor = opt_editor;
3062 if (!editor)
3063 editor = getenv("VISUAL");
3064 if (!editor)
3065 editor = getenv("EDITOR");
3066 if (!editor)
3067 editor = "vi";
3069 editor_argv[0] = editor;
3070 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3073 static void
3074 open_run_request(enum request request)
3076 struct run_request *req = get_run_request(request);
3077 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3079 if (!req) {
3080 report("Unknown run request");
3081 return;
3084 if (format_argv(argv, req->argv, FORMAT_ALL))
3085 open_external_viewer(argv, NULL);
3086 free_argv(argv);
3090 * User request switch noodle
3093 static int
3094 view_driver(struct view *view, enum request request)
3096 int i;
3098 if (request == REQ_NONE) {
3099 doupdate();
3100 return TRUE;
3103 if (request > REQ_NONE) {
3104 open_run_request(request);
3105 /* FIXME: When all views can refresh always do this. */
3106 if (view == VIEW(REQ_VIEW_STATUS) ||
3107 view == VIEW(REQ_VIEW_MAIN) ||
3108 view == VIEW(REQ_VIEW_LOG) ||
3109 view == VIEW(REQ_VIEW_STAGE))
3110 request = REQ_REFRESH;
3111 else
3112 return TRUE;
3115 if (view && view->lines) {
3116 request = view->ops->request(view, request, &view->line[view->lineno]);
3117 if (request == REQ_NONE)
3118 return TRUE;
3121 switch (request) {
3122 case REQ_MOVE_UP:
3123 case REQ_MOVE_DOWN:
3124 case REQ_MOVE_PAGE_UP:
3125 case REQ_MOVE_PAGE_DOWN:
3126 case REQ_MOVE_FIRST_LINE:
3127 case REQ_MOVE_LAST_LINE:
3128 move_view(view, request);
3129 break;
3131 case REQ_SCROLL_LEFT:
3132 case REQ_SCROLL_RIGHT:
3133 case REQ_SCROLL_LINE_DOWN:
3134 case REQ_SCROLL_LINE_UP:
3135 case REQ_SCROLL_PAGE_DOWN:
3136 case REQ_SCROLL_PAGE_UP:
3137 scroll_view(view, request);
3138 break;
3140 case REQ_VIEW_BLAME:
3141 if (!opt_file[0]) {
3142 report("No file chosen, press %s to open tree view",
3143 get_key(REQ_VIEW_TREE));
3144 break;
3146 open_view(view, request, OPEN_DEFAULT);
3147 break;
3149 case REQ_VIEW_BLOB:
3150 if (!ref_blob[0]) {
3151 report("No file chosen, press %s to open tree view",
3152 get_key(REQ_VIEW_TREE));
3153 break;
3155 open_view(view, request, OPEN_DEFAULT);
3156 break;
3158 case REQ_VIEW_PAGER:
3159 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3160 report("No pager content, press %s to run command from prompt",
3161 get_key(REQ_PROMPT));
3162 break;
3164 open_view(view, request, OPEN_DEFAULT);
3165 break;
3167 case REQ_VIEW_STAGE:
3168 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3169 report("No stage content, press %s to open the status view and choose file",
3170 get_key(REQ_VIEW_STATUS));
3171 break;
3173 open_view(view, request, OPEN_DEFAULT);
3174 break;
3176 case REQ_VIEW_STATUS:
3177 if (opt_is_inside_work_tree == FALSE) {
3178 report("The status view requires a working tree");
3179 break;
3181 open_view(view, request, OPEN_DEFAULT);
3182 break;
3184 case REQ_VIEW_MAIN:
3185 case REQ_VIEW_DIFF:
3186 case REQ_VIEW_LOG:
3187 case REQ_VIEW_TREE:
3188 case REQ_VIEW_HELP:
3189 open_view(view, request, OPEN_DEFAULT);
3190 break;
3192 case REQ_NEXT:
3193 case REQ_PREVIOUS:
3194 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3196 if ((view == VIEW(REQ_VIEW_DIFF) &&
3197 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3198 (view == VIEW(REQ_VIEW_DIFF) &&
3199 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3200 (view == VIEW(REQ_VIEW_STAGE) &&
3201 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3202 (view == VIEW(REQ_VIEW_BLOB) &&
3203 view->parent == VIEW(REQ_VIEW_TREE))) {
3204 int line;
3206 view = view->parent;
3207 line = view->lineno;
3208 move_view(view, request);
3209 if (view_is_displayed(view))
3210 update_view_title(view);
3211 if (line != view->lineno)
3212 view->ops->request(view, REQ_ENTER,
3213 &view->line[view->lineno]);
3215 } else {
3216 move_view(view, request);
3218 break;
3220 case REQ_VIEW_NEXT:
3222 int nviews = displayed_views();
3223 int next_view = (current_view + 1) % nviews;
3225 if (next_view == current_view) {
3226 report("Only one view is displayed");
3227 break;
3230 current_view = next_view;
3231 /* Blur out the title of the previous view. */
3232 update_view_title(view);
3233 report("");
3234 break;
3236 case REQ_REFRESH:
3237 report("Refreshing is not yet supported for the %s view", view->name);
3238 break;
3240 case REQ_MAXIMIZE:
3241 if (displayed_views() == 2)
3242 maximize_view(view);
3243 break;
3245 case REQ_TOGGLE_LINENO:
3246 toggle_view_option(&opt_line_number, "line numbers");
3247 break;
3249 case REQ_TOGGLE_DATE:
3250 toggle_view_option(&opt_date, "date display");
3251 break;
3253 case REQ_TOGGLE_AUTHOR:
3254 toggle_view_option(&opt_author, "author display");
3255 break;
3257 case REQ_TOGGLE_REV_GRAPH:
3258 toggle_view_option(&opt_rev_graph, "revision graph display");
3259 break;
3261 case REQ_TOGGLE_REFS:
3262 toggle_view_option(&opt_show_refs, "reference display");
3263 break;
3265 case REQ_SEARCH:
3266 case REQ_SEARCH_BACK:
3267 search_view(view, request);
3268 break;
3270 case REQ_FIND_NEXT:
3271 case REQ_FIND_PREV:
3272 find_next(view, request);
3273 break;
3275 case REQ_STOP_LOADING:
3276 for (i = 0; i < ARRAY_SIZE(views); i++) {
3277 view = &views[i];
3278 if (view->pipe)
3279 report("Stopped loading the %s view", view->name),
3280 end_update(view, TRUE);
3282 break;
3284 case REQ_SHOW_VERSION:
3285 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3286 return TRUE;
3288 case REQ_SCREEN_REDRAW:
3289 redraw_display(TRUE);
3290 break;
3292 case REQ_EDIT:
3293 report("Nothing to edit");
3294 break;
3296 case REQ_ENTER:
3297 report("Nothing to enter");
3298 break;
3300 case REQ_VIEW_CLOSE:
3301 /* XXX: Mark closed views by letting view->parent point to the
3302 * view itself. Parents to closed view should never be
3303 * followed. */
3304 if (view->parent &&
3305 view->parent->parent != view->parent) {
3306 maximize_view(view->parent);
3307 view->parent = view;
3308 break;
3310 /* Fall-through */
3311 case REQ_QUIT:
3312 return FALSE;
3314 default:
3315 report("Unknown key, press 'h' for help");
3316 return TRUE;
3319 return TRUE;
3324 * View backend utilities
3327 static void
3328 parse_timezone(time_t *time, const char *zone)
3330 long tz;
3332 tz = ('0' - zone[1]) * 60 * 60 * 10;
3333 tz += ('0' - zone[2]) * 60 * 60;
3334 tz += ('0' - zone[3]) * 60;
3335 tz += ('0' - zone[4]);
3337 if (zone[0] == '-')
3338 tz = -tz;
3340 *time -= tz;
3343 /* Parse author lines where the name may be empty:
3344 * author <email@address.tld> 1138474660 +0100
3346 static void
3347 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3349 char *nameend = strchr(ident, '<');
3350 char *emailend = strchr(ident, '>');
3352 if (nameend && emailend)
3353 *nameend = *emailend = 0;
3354 ident = chomp_string(ident);
3355 if (!*ident) {
3356 if (nameend)
3357 ident = chomp_string(nameend + 1);
3358 if (!*ident)
3359 ident = "Unknown";
3362 string_ncopy_do(author, authorsize, ident, strlen(ident));
3364 /* Parse epoch and timezone */
3365 if (emailend && emailend[1] == ' ') {
3366 char *secs = emailend + 2;
3367 char *zone = strchr(secs, ' ');
3368 time_t time = (time_t) atol(secs);
3370 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3371 parse_timezone(&time, zone + 1);
3373 gmtime_r(&time, tm);
3377 static enum input_status
3378 select_commit_parent_handler(void *data, char *buf, int c)
3380 size_t parents = *(size_t *) data;
3381 int parent = 0;
3383 if (!isdigit(c))
3384 return INPUT_SKIP;
3386 if (*buf)
3387 parent = atoi(buf) * 10;
3388 parent += c - '0';
3390 if (parent > parents)
3391 return INPUT_SKIP;
3392 return INPUT_OK;
3395 static bool
3396 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3398 char buf[SIZEOF_STR * 4];
3399 const char *revlist_argv[] = {
3400 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3402 int parents;
3404 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3405 !*chomp_string(buf) ||
3406 (parents = (strlen(buf) / 40) - 1) < 0) {
3407 report("Failed to get parent information");
3408 return FALSE;
3410 } else if (parents == 0) {
3411 if (path)
3412 report("Path '%s' does not exist in the parent", path);
3413 else
3414 report("The selected commit has no parents");
3415 return FALSE;
3418 if (parents > 1) {
3419 char prompt[SIZEOF_STR];
3420 char *result;
3422 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3423 return FALSE;
3424 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3425 if (!result)
3426 return FALSE;
3427 parents = atoi(result);
3430 string_copy_rev(rev, &buf[41 * parents]);
3431 return TRUE;
3435 * Pager backend
3438 static bool
3439 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3441 char text[SIZEOF_STR];
3443 if (opt_line_number && draw_lineno(view, lineno))
3444 return TRUE;
3446 string_expand(text, sizeof(text), line->data, opt_tab_size);
3447 draw_text(view, line->type, text, TRUE);
3448 return TRUE;
3451 static bool
3452 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3454 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3455 char refbuf[SIZEOF_STR];
3456 char *ref = NULL;
3458 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3459 ref = chomp_string(refbuf);
3461 if (!ref || !*ref)
3462 return TRUE;
3464 /* This is the only fatal call, since it can "corrupt" the buffer. */
3465 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3466 return FALSE;
3468 return TRUE;
3471 static void
3472 add_pager_refs(struct view *view, struct line *line)
3474 char buf[SIZEOF_STR];
3475 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3476 struct ref **refs;
3477 size_t bufpos = 0, refpos = 0;
3478 const char *sep = "Refs: ";
3479 bool is_tag = FALSE;
3481 assert(line->type == LINE_COMMIT);
3483 refs = get_refs(commit_id);
3484 if (!refs) {
3485 if (view == VIEW(REQ_VIEW_DIFF))
3486 goto try_add_describe_ref;
3487 return;
3490 do {
3491 struct ref *ref = refs[refpos];
3492 const char *fmt = ref->tag ? "%s[%s]" :
3493 ref->remote ? "%s<%s>" : "%s%s";
3495 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3496 return;
3497 sep = ", ";
3498 if (ref->tag)
3499 is_tag = TRUE;
3500 } while (refs[refpos++]->next);
3502 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3503 try_add_describe_ref:
3504 /* Add <tag>-g<commit_id> "fake" reference. */
3505 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3506 return;
3509 if (bufpos == 0)
3510 return;
3512 add_line_text(view, buf, LINE_PP_REFS);
3515 static bool
3516 pager_read(struct view *view, char *data)
3518 struct line *line;
3520 if (!data)
3521 return TRUE;
3523 line = add_line_text(view, data, get_line_type(data));
3524 if (!line)
3525 return FALSE;
3527 if (line->type == LINE_COMMIT &&
3528 (view == VIEW(REQ_VIEW_DIFF) ||
3529 view == VIEW(REQ_VIEW_LOG)))
3530 add_pager_refs(view, line);
3532 return TRUE;
3535 static enum request
3536 pager_request(struct view *view, enum request request, struct line *line)
3538 int split = 0;
3540 if (request != REQ_ENTER)
3541 return request;
3543 if (line->type == LINE_COMMIT &&
3544 (view == VIEW(REQ_VIEW_LOG) ||
3545 view == VIEW(REQ_VIEW_PAGER))) {
3546 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3547 split = 1;
3550 /* Always scroll the view even if it was split. That way
3551 * you can use Enter to scroll through the log view and
3552 * split open each commit diff. */
3553 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3555 /* FIXME: A minor workaround. Scrolling the view will call report("")
3556 * but if we are scrolling a non-current view this won't properly
3557 * update the view title. */
3558 if (split)
3559 update_view_title(view);
3561 return REQ_NONE;
3564 static bool
3565 pager_grep(struct view *view, struct line *line)
3567 regmatch_t pmatch;
3568 char *text = line->data;
3570 if (!*text)
3571 return FALSE;
3573 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3574 return FALSE;
3576 return TRUE;
3579 static void
3580 pager_select(struct view *view, struct line *line)
3582 if (line->type == LINE_COMMIT) {
3583 char *text = (char *)line->data + STRING_SIZE("commit ");
3585 if (view != VIEW(REQ_VIEW_PAGER))
3586 string_copy_rev(view->ref, text);
3587 string_copy_rev(ref_commit, text);
3591 static struct view_ops pager_ops = {
3592 "line",
3593 NULL,
3594 NULL,
3595 pager_read,
3596 pager_draw,
3597 pager_request,
3598 pager_grep,
3599 pager_select,
3602 static const char *log_argv[SIZEOF_ARG] = {
3603 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3606 static enum request
3607 log_request(struct view *view, enum request request, struct line *line)
3609 switch (request) {
3610 case REQ_REFRESH:
3611 load_refs();
3612 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3613 return REQ_NONE;
3614 default:
3615 return pager_request(view, request, line);
3619 static struct view_ops log_ops = {
3620 "line",
3621 log_argv,
3622 NULL,
3623 pager_read,
3624 pager_draw,
3625 log_request,
3626 pager_grep,
3627 pager_select,
3630 static const char *diff_argv[SIZEOF_ARG] = {
3631 "git", "show", "--pretty=fuller", "--no-color", "--root",
3632 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3635 static struct view_ops diff_ops = {
3636 "line",
3637 diff_argv,
3638 NULL,
3639 pager_read,
3640 pager_draw,
3641 pager_request,
3642 pager_grep,
3643 pager_select,
3647 * Help backend
3650 static bool
3651 help_open(struct view *view)
3653 char buf[SIZEOF_STR];
3654 size_t bufpos;
3655 int i;
3657 if (view->lines > 0)
3658 return TRUE;
3660 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3662 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3663 const char *key;
3665 if (req_info[i].request == REQ_NONE)
3666 continue;
3668 if (!req_info[i].request) {
3669 add_line_text(view, "", LINE_DEFAULT);
3670 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3671 continue;
3674 key = get_key(req_info[i].request);
3675 if (!*key)
3676 key = "(no key defined)";
3678 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3679 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3680 if (buf[bufpos] == '_')
3681 buf[bufpos] = '-';
3684 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3685 key, buf, req_info[i].help);
3688 if (run_requests) {
3689 add_line_text(view, "", LINE_DEFAULT);
3690 add_line_text(view, "External commands:", LINE_DEFAULT);
3693 for (i = 0; i < run_requests; i++) {
3694 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3695 const char *key;
3696 int argc;
3698 if (!req)
3699 continue;
3701 key = get_key_name(req->key);
3702 if (!*key)
3703 key = "(no key defined)";
3705 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3706 if (!string_format_from(buf, &bufpos, "%s%s",
3707 argc ? " " : "", req->argv[argc]))
3708 return REQ_NONE;
3710 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3711 keymap_table[req->keymap].name, key, buf);
3714 return TRUE;
3717 static struct view_ops help_ops = {
3718 "line",
3719 NULL,
3720 help_open,
3721 NULL,
3722 pager_draw,
3723 pager_request,
3724 pager_grep,
3725 pager_select,
3730 * Tree backend
3733 struct tree_stack_entry {
3734 struct tree_stack_entry *prev; /* Entry below this in the stack */
3735 unsigned long lineno; /* Line number to restore */
3736 char *name; /* Position of name in opt_path */
3739 /* The top of the path stack. */
3740 static struct tree_stack_entry *tree_stack = NULL;
3741 unsigned long tree_lineno = 0;
3743 static void
3744 pop_tree_stack_entry(void)
3746 struct tree_stack_entry *entry = tree_stack;
3748 tree_lineno = entry->lineno;
3749 entry->name[0] = 0;
3750 tree_stack = entry->prev;
3751 free(entry);
3754 static void
3755 push_tree_stack_entry(const char *name, unsigned long lineno)
3757 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3758 size_t pathlen = strlen(opt_path);
3760 if (!entry)
3761 return;
3763 entry->prev = tree_stack;
3764 entry->name = opt_path + pathlen;
3765 tree_stack = entry;
3767 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3768 pop_tree_stack_entry();
3769 return;
3772 /* Move the current line to the first tree entry. */
3773 tree_lineno = 1;
3774 entry->lineno = lineno;
3777 /* Parse output from git-ls-tree(1):
3779 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3782 #define SIZEOF_TREE_ATTR \
3783 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3785 #define SIZEOF_TREE_MODE \
3786 STRING_SIZE("100644 ")
3788 #define TREE_ID_OFFSET \
3789 STRING_SIZE("100644 blob ")
3791 struct tree_entry {
3792 char id[SIZEOF_REV];
3793 mode_t mode;
3794 struct tm time; /* Date from the author ident. */
3795 char author[75]; /* Author of the commit. */
3796 char name[1];
3799 static const char *
3800 tree_path(struct line *line)
3802 return ((struct tree_entry *) line->data)->name;
3806 static int
3807 tree_compare_entry(struct line *line1, struct line *line2)
3809 if (line1->type != line2->type)
3810 return line1->type == LINE_TREE_DIR ? -1 : 1;
3811 return strcmp(tree_path(line1), tree_path(line2));
3814 static struct line *
3815 tree_entry(struct view *view, enum line_type type, const char *path,
3816 const char *mode, const char *id)
3818 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3819 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3821 if (!entry || !line) {
3822 free(entry);
3823 return NULL;
3826 strncpy(entry->name, path, strlen(path));
3827 if (mode)
3828 entry->mode = strtoul(mode, NULL, 8);
3829 if (id)
3830 string_copy_rev(entry->id, id);
3832 return line;
3835 static bool
3836 tree_read_date(struct view *view, char *text, bool *read_date)
3838 static char author_name[SIZEOF_STR];
3839 static struct tm author_time;
3841 if (!text && *read_date) {
3842 *read_date = FALSE;
3843 return TRUE;
3845 } else if (!text) {
3846 char *path = *opt_path ? opt_path : ".";
3847 /* Find next entry to process */
3848 const char *log_file[] = {
3849 "git", "log", "--no-color", "--pretty=raw",
3850 "--cc", "--raw", view->id, "--", path, NULL
3852 struct io io = {};
3854 if (!view->lines) {
3855 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3856 report("Tree is empty");
3857 return TRUE;
3860 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3861 report("Failed to load tree data");
3862 return TRUE;
3865 done_io(view->pipe);
3866 view->io = io;
3867 *read_date = TRUE;
3868 return FALSE;
3870 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3871 parse_author_line(text + STRING_SIZE("author "),
3872 author_name, sizeof(author_name), &author_time);
3874 } else if (*text == ':') {
3875 char *pos;
3876 size_t annotated = 1;
3877 size_t i;
3879 pos = strchr(text, '\t');
3880 if (!pos)
3881 return TRUE;
3882 text = pos + 1;
3883 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3884 text += strlen(opt_prefix);
3885 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3886 text += strlen(opt_path);
3887 pos = strchr(text, '/');
3888 if (pos)
3889 *pos = 0;
3891 for (i = 1; i < view->lines; i++) {
3892 struct line *line = &view->line[i];
3893 struct tree_entry *entry = line->data;
3895 annotated += !!*entry->author;
3896 if (*entry->author || strcmp(entry->name, text))
3897 continue;
3899 string_copy(entry->author, author_name);
3900 memcpy(&entry->time, &author_time, sizeof(entry->time));
3901 line->dirty = 1;
3902 break;
3905 if (annotated == view->lines)
3906 kill_io(view->pipe);
3908 return TRUE;
3911 static bool
3912 tree_read(struct view *view, char *text)
3914 static bool read_date = FALSE;
3915 struct tree_entry *data;
3916 struct line *entry, *line;
3917 enum line_type type;
3918 size_t textlen = text ? strlen(text) : 0;
3919 char *path = text + SIZEOF_TREE_ATTR;
3921 if (read_date || !text)
3922 return tree_read_date(view, text, &read_date);
3924 if (textlen <= SIZEOF_TREE_ATTR)
3925 return FALSE;
3926 if (view->lines == 0 &&
3927 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3928 return FALSE;
3930 /* Strip the path part ... */
3931 if (*opt_path) {
3932 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3933 size_t striplen = strlen(opt_path);
3935 if (pathlen > striplen)
3936 memmove(path, path + striplen,
3937 pathlen - striplen + 1);
3939 /* Insert "link" to parent directory. */
3940 if (view->lines == 1 &&
3941 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3942 return FALSE;
3945 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3946 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3947 if (!entry)
3948 return FALSE;
3949 data = entry->data;
3951 /* Skip "Directory ..." and ".." line. */
3952 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3953 if (tree_compare_entry(line, entry) <= 0)
3954 continue;
3956 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3958 line->data = data;
3959 line->type = type;
3960 for (; line <= entry; line++)
3961 line->dirty = line->cleareol = 1;
3962 return TRUE;
3965 if (tree_lineno > view->lineno) {
3966 view->lineno = tree_lineno;
3967 tree_lineno = 0;
3970 return TRUE;
3973 static bool
3974 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3976 struct tree_entry *entry = line->data;
3978 if (line->type == LINE_TREE_HEAD) {
3979 if (draw_text(view, line->type, "Directory path /", TRUE))
3980 return TRUE;
3981 } else {
3982 if (draw_mode(view, entry->mode))
3983 return TRUE;
3985 if (opt_author && draw_author(view, entry->author))
3986 return TRUE;
3988 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3989 return TRUE;
3991 if (draw_text(view, line->type, entry->name, TRUE))
3992 return TRUE;
3993 return TRUE;
3996 static void
3997 open_blob_editor()
3999 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4000 int fd = mkstemp(file);
4002 if (fd == -1)
4003 report("Failed to create temporary file");
4004 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4005 report("Failed to save blob data to file");
4006 else
4007 open_editor(FALSE, file);
4008 if (fd != -1)
4009 unlink(file);
4012 static enum request
4013 tree_request(struct view *view, enum request request, struct line *line)
4015 enum open_flags flags;
4017 switch (request) {
4018 case REQ_VIEW_BLAME:
4019 if (line->type != LINE_TREE_FILE) {
4020 report("Blame only supported for files");
4021 return REQ_NONE;
4024 string_copy(opt_ref, view->vid);
4025 return request;
4027 case REQ_EDIT:
4028 if (line->type != LINE_TREE_FILE) {
4029 report("Edit only supported for files");
4030 } else if (!is_head_commit(view->vid)) {
4031 open_blob_editor();
4032 } else {
4033 open_editor(TRUE, opt_file);
4035 return REQ_NONE;
4037 case REQ_PARENT:
4038 if (!*opt_path) {
4039 /* quit view if at top of tree */
4040 return REQ_VIEW_CLOSE;
4042 /* fake 'cd ..' */
4043 line = &view->line[1];
4044 break;
4046 case REQ_ENTER:
4047 break;
4049 default:
4050 return request;
4053 /* Cleanup the stack if the tree view is at a different tree. */
4054 while (!*opt_path && tree_stack)
4055 pop_tree_stack_entry();
4057 switch (line->type) {
4058 case LINE_TREE_DIR:
4059 /* Depending on whether it is a subdirectory or parent link
4060 * mangle the path buffer. */
4061 if (line == &view->line[1] && *opt_path) {
4062 pop_tree_stack_entry();
4064 } else {
4065 const char *basename = tree_path(line);
4067 push_tree_stack_entry(basename, view->lineno);
4070 /* Trees and subtrees share the same ID, so they are not not
4071 * unique like blobs. */
4072 flags = OPEN_RELOAD;
4073 request = REQ_VIEW_TREE;
4074 break;
4076 case LINE_TREE_FILE:
4077 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4078 request = REQ_VIEW_BLOB;
4079 break;
4081 default:
4082 return REQ_NONE;
4085 open_view(view, request, flags);
4086 if (request == REQ_VIEW_TREE)
4087 view->lineno = tree_lineno;
4089 return REQ_NONE;
4092 static void
4093 tree_select(struct view *view, struct line *line)
4095 struct tree_entry *entry = line->data;
4097 if (line->type == LINE_TREE_FILE) {
4098 string_copy_rev(ref_blob, entry->id);
4099 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4101 } else if (line->type != LINE_TREE_DIR) {
4102 return;
4105 string_copy_rev(view->ref, entry->id);
4108 static const char *tree_argv[SIZEOF_ARG] = {
4109 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4112 static struct view_ops tree_ops = {
4113 "file",
4114 tree_argv,
4115 NULL,
4116 tree_read,
4117 tree_draw,
4118 tree_request,
4119 pager_grep,
4120 tree_select,
4123 static bool
4124 blob_read(struct view *view, char *line)
4126 if (!line)
4127 return TRUE;
4128 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4131 static enum request
4132 blob_request(struct view *view, enum request request, struct line *line)
4134 switch (request) {
4135 case REQ_EDIT:
4136 open_blob_editor();
4137 return REQ_NONE;
4138 default:
4139 return pager_request(view, request, line);
4143 static const char *blob_argv[SIZEOF_ARG] = {
4144 "git", "cat-file", "blob", "%(blob)", NULL
4147 static struct view_ops blob_ops = {
4148 "line",
4149 blob_argv,
4150 NULL,
4151 blob_read,
4152 pager_draw,
4153 blob_request,
4154 pager_grep,
4155 pager_select,
4159 * Blame backend
4161 * Loading the blame view is a two phase job:
4163 * 1. File content is read either using opt_file from the
4164 * filesystem or using git-cat-file.
4165 * 2. Then blame information is incrementally added by
4166 * reading output from git-blame.
4169 static const char *blame_head_argv[] = {
4170 "git", "blame", "--incremental", "--", "%(file)", NULL
4173 static const char *blame_ref_argv[] = {
4174 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4177 static const char *blame_cat_file_argv[] = {
4178 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4181 struct blame_commit {
4182 char id[SIZEOF_REV]; /* SHA1 ID. */
4183 char title[128]; /* First line of the commit message. */
4184 char author[75]; /* Author of the commit. */
4185 struct tm time; /* Date from the author ident. */
4186 char filename[128]; /* Name of file. */
4187 bool has_previous; /* Was a "previous" line detected. */
4190 struct blame {
4191 struct blame_commit *commit;
4192 unsigned long lineno;
4193 char text[1];
4196 static bool
4197 blame_open(struct view *view)
4199 if (*opt_ref || !io_open(&view->io, opt_file)) {
4200 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4201 return FALSE;
4204 setup_update(view, opt_file);
4205 string_format(view->ref, "%s ...", opt_file);
4207 return TRUE;
4210 static struct blame_commit *
4211 get_blame_commit(struct view *view, const char *id)
4213 size_t i;
4215 for (i = 0; i < view->lines; i++) {
4216 struct blame *blame = view->line[i].data;
4218 if (!blame->commit)
4219 continue;
4221 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4222 return blame->commit;
4226 struct blame_commit *commit = calloc(1, sizeof(*commit));
4228 if (commit)
4229 string_ncopy(commit->id, id, SIZEOF_REV);
4230 return commit;
4234 static bool
4235 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4237 const char *pos = *posref;
4239 *posref = NULL;
4240 pos = strchr(pos + 1, ' ');
4241 if (!pos || !isdigit(pos[1]))
4242 return FALSE;
4243 *number = atoi(pos + 1);
4244 if (*number < min || *number > max)
4245 return FALSE;
4247 *posref = pos;
4248 return TRUE;
4251 static struct blame_commit *
4252 parse_blame_commit(struct view *view, const char *text, int *blamed)
4254 struct blame_commit *commit;
4255 struct blame *blame;
4256 const char *pos = text + SIZEOF_REV - 2;
4257 size_t orig_lineno = 0;
4258 size_t lineno;
4259 size_t group;
4261 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4262 return NULL;
4264 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4265 !parse_number(&pos, &lineno, 1, view->lines) ||
4266 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4267 return NULL;
4269 commit = get_blame_commit(view, text);
4270 if (!commit)
4271 return NULL;
4273 *blamed += group;
4274 while (group--) {
4275 struct line *line = &view->line[lineno + group - 1];
4277 blame = line->data;
4278 blame->commit = commit;
4279 blame->lineno = orig_lineno + group - 1;
4280 line->dirty = 1;
4283 return commit;
4286 static bool
4287 blame_read_file(struct view *view, const char *line, bool *read_file)
4289 if (!line) {
4290 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4291 struct io io = {};
4293 if (view->lines == 0 && !view->parent)
4294 die("No blame exist for %s", view->vid);
4296 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4297 report("Failed to load blame data");
4298 return TRUE;
4301 done_io(view->pipe);
4302 view->io = io;
4303 *read_file = FALSE;
4304 return FALSE;
4306 } else {
4307 size_t linelen = strlen(line);
4308 struct blame *blame = malloc(sizeof(*blame) + linelen);
4310 if (!blame)
4311 return FALSE;
4313 blame->commit = NULL;
4314 strncpy(blame->text, line, linelen);
4315 blame->text[linelen] = 0;
4316 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4320 static bool
4321 match_blame_header(const char *name, char **line)
4323 size_t namelen = strlen(name);
4324 bool matched = !strncmp(name, *line, namelen);
4326 if (matched)
4327 *line += namelen;
4329 return matched;
4332 static bool
4333 blame_read(struct view *view, char *line)
4335 static struct blame_commit *commit = NULL;
4336 static int blamed = 0;
4337 static time_t author_time;
4338 static bool read_file = TRUE;
4340 if (read_file)
4341 return blame_read_file(view, line, &read_file);
4343 if (!line) {
4344 /* Reset all! */
4345 commit = NULL;
4346 blamed = 0;
4347 read_file = TRUE;
4348 string_format(view->ref, "%s", view->vid);
4349 if (view_is_displayed(view)) {
4350 update_view_title(view);
4351 redraw_view_from(view, 0);
4353 return TRUE;
4356 if (!commit) {
4357 commit = parse_blame_commit(view, line, &blamed);
4358 string_format(view->ref, "%s %2d%%", view->vid,
4359 view->lines ? blamed * 100 / view->lines : 0);
4361 } else if (match_blame_header("author ", &line)) {
4362 string_ncopy(commit->author, line, strlen(line));
4364 } else if (match_blame_header("author-time ", &line)) {
4365 author_time = (time_t) atol(line);
4367 } else if (match_blame_header("author-tz ", &line)) {
4368 parse_timezone(&author_time, line);
4369 gmtime_r(&author_time, &commit->time);
4371 } else if (match_blame_header("summary ", &line)) {
4372 string_ncopy(commit->title, line, strlen(line));
4374 } else if (match_blame_header("previous ", &line)) {
4375 commit->has_previous = TRUE;
4377 } else if (match_blame_header("filename ", &line)) {
4378 string_ncopy(commit->filename, line, strlen(line));
4379 commit = NULL;
4382 return TRUE;
4385 static bool
4386 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4388 struct blame *blame = line->data;
4389 struct tm *time = NULL;
4390 const char *id = NULL, *author = NULL;
4391 char text[SIZEOF_STR];
4393 if (blame->commit && *blame->commit->filename) {
4394 id = blame->commit->id;
4395 author = blame->commit->author;
4396 time = &blame->commit->time;
4399 if (opt_date && draw_date(view, time))
4400 return TRUE;
4402 if (opt_author && draw_author(view, author))
4403 return TRUE;
4405 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4406 return TRUE;
4408 if (draw_lineno(view, lineno))
4409 return TRUE;
4411 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4412 draw_text(view, LINE_DEFAULT, text, TRUE);
4413 return TRUE;
4416 static bool
4417 check_blame_commit(struct blame *blame, bool check_null_id)
4419 if (!blame->commit)
4420 report("Commit data not loaded yet");
4421 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4422 report("No commit exist for the selected line");
4423 else
4424 return TRUE;
4425 return FALSE;
4428 static void
4429 setup_blame_parent_line(struct view *view, struct blame *blame)
4431 const char *diff_tree_argv[] = {
4432 "git", "diff-tree", "-U0", blame->commit->id,
4433 "--", blame->commit->filename, NULL
4435 struct io io = {};
4436 int parent_lineno = -1;
4437 int blamed_lineno = -1;
4438 char *line;
4440 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4441 return;
4443 while ((line = io_get(&io, '\n', TRUE))) {
4444 if (*line == '@') {
4445 char *pos = strchr(line, '+');
4447 parent_lineno = atoi(line + 4);
4448 if (pos)
4449 blamed_lineno = atoi(pos + 1);
4451 } else if (*line == '+' && parent_lineno != -1) {
4452 if (blame->lineno == blamed_lineno - 1 &&
4453 !strcmp(blame->text, line + 1)) {
4454 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4455 break;
4457 blamed_lineno++;
4461 done_io(&io);
4464 static enum request
4465 blame_request(struct view *view, enum request request, struct line *line)
4467 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4468 struct blame *blame = line->data;
4470 switch (request) {
4471 case REQ_VIEW_BLAME:
4472 if (check_blame_commit(blame, TRUE)) {
4473 string_copy(opt_ref, blame->commit->id);
4474 string_copy(opt_file, blame->commit->filename);
4475 if (blame->lineno)
4476 view->lineno = blame->lineno;
4477 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4479 break;
4481 case REQ_PARENT:
4482 if (check_blame_commit(blame, TRUE) &&
4483 select_commit_parent(blame->commit->id, opt_ref,
4484 blame->commit->filename)) {
4485 string_copy(opt_file, blame->commit->filename);
4486 setup_blame_parent_line(view, blame);
4487 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4489 break;
4491 case REQ_ENTER:
4492 if (!check_blame_commit(blame, FALSE))
4493 break;
4495 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4496 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4497 break;
4499 if (!strcmp(blame->commit->id, NULL_ID)) {
4500 struct view *diff = VIEW(REQ_VIEW_DIFF);
4501 const char *diff_index_argv[] = {
4502 "git", "diff-index", "--root", "--patch-with-stat",
4503 "-C", "-M", "HEAD", "--", view->vid, NULL
4506 if (!blame->commit->has_previous) {
4507 diff_index_argv[1] = "diff";
4508 diff_index_argv[2] = "--no-color";
4509 diff_index_argv[6] = "--";
4510 diff_index_argv[7] = "/dev/null";
4513 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4514 report("Failed to allocate diff command");
4515 break;
4517 flags |= OPEN_PREPARED;
4520 open_view(view, REQ_VIEW_DIFF, flags);
4521 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4522 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4523 break;
4525 default:
4526 return request;
4529 return REQ_NONE;
4532 static bool
4533 blame_grep(struct view *view, struct line *line)
4535 struct blame *blame = line->data;
4536 struct blame_commit *commit = blame->commit;
4537 regmatch_t pmatch;
4539 #define MATCH(text, on) \
4540 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4542 if (commit) {
4543 char buf[DATE_COLS + 1];
4545 if (MATCH(commit->title, 1) ||
4546 MATCH(commit->author, opt_author) ||
4547 MATCH(commit->id, opt_date))
4548 return TRUE;
4550 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4551 MATCH(buf, 1))
4552 return TRUE;
4555 return MATCH(blame->text, 1);
4557 #undef MATCH
4560 static void
4561 blame_select(struct view *view, struct line *line)
4563 struct blame *blame = line->data;
4564 struct blame_commit *commit = blame->commit;
4566 if (!commit)
4567 return;
4569 if (!strcmp(commit->id, NULL_ID))
4570 string_ncopy(ref_commit, "HEAD", 4);
4571 else
4572 string_copy_rev(ref_commit, commit->id);
4575 static struct view_ops blame_ops = {
4576 "line",
4577 NULL,
4578 blame_open,
4579 blame_read,
4580 blame_draw,
4581 blame_request,
4582 blame_grep,
4583 blame_select,
4587 * Status backend
4590 struct status {
4591 char status;
4592 struct {
4593 mode_t mode;
4594 char rev[SIZEOF_REV];
4595 char name[SIZEOF_STR];
4596 } old;
4597 struct {
4598 mode_t mode;
4599 char rev[SIZEOF_REV];
4600 char name[SIZEOF_STR];
4601 } new;
4604 static char status_onbranch[SIZEOF_STR];
4605 static struct status stage_status;
4606 static enum line_type stage_line_type;
4607 static size_t stage_chunks;
4608 static int *stage_chunk;
4610 /* This should work even for the "On branch" line. */
4611 static inline bool
4612 status_has_none(struct view *view, struct line *line)
4614 return line < view->line + view->lines && !line[1].data;
4617 /* Get fields from the diff line:
4618 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4620 static inline bool
4621 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4623 const char *old_mode = buf + 1;
4624 const char *new_mode = buf + 8;
4625 const char *old_rev = buf + 15;
4626 const char *new_rev = buf + 56;
4627 const char *status = buf + 97;
4629 if (bufsize < 98 ||
4630 old_mode[-1] != ':' ||
4631 new_mode[-1] != ' ' ||
4632 old_rev[-1] != ' ' ||
4633 new_rev[-1] != ' ' ||
4634 status[-1] != ' ')
4635 return FALSE;
4637 file->status = *status;
4639 string_copy_rev(file->old.rev, old_rev);
4640 string_copy_rev(file->new.rev, new_rev);
4642 file->old.mode = strtoul(old_mode, NULL, 8);
4643 file->new.mode = strtoul(new_mode, NULL, 8);
4645 file->old.name[0] = file->new.name[0] = 0;
4647 return TRUE;
4650 static bool
4651 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4653 struct status *unmerged = NULL;
4654 char *buf;
4655 struct io io = {};
4657 if (!run_io(&io, argv, NULL, IO_RD))
4658 return FALSE;
4660 add_line_data(view, NULL, type);
4662 while ((buf = io_get(&io, 0, TRUE))) {
4663 struct status *file = unmerged;
4665 if (!file) {
4666 file = calloc(1, sizeof(*file));
4667 if (!file || !add_line_data(view, file, type))
4668 goto error_out;
4671 /* Parse diff info part. */
4672 if (status) {
4673 file->status = status;
4674 if (status == 'A')
4675 string_copy(file->old.rev, NULL_ID);
4677 } else if (!file->status || file == unmerged) {
4678 if (!status_get_diff(file, buf, strlen(buf)))
4679 goto error_out;
4681 buf = io_get(&io, 0, TRUE);
4682 if (!buf)
4683 break;
4685 /* Collapse all modified entries that follow an
4686 * associated unmerged entry. */
4687 if (unmerged == file) {
4688 unmerged->status = 'U';
4689 unmerged = NULL;
4690 } else if (file->status == 'U') {
4691 unmerged = file;
4695 /* Grab the old name for rename/copy. */
4696 if (!*file->old.name &&
4697 (file->status == 'R' || file->status == 'C')) {
4698 string_ncopy(file->old.name, buf, strlen(buf));
4700 buf = io_get(&io, 0, TRUE);
4701 if (!buf)
4702 break;
4705 /* git-ls-files just delivers a NUL separated list of
4706 * file names similar to the second half of the
4707 * git-diff-* output. */
4708 string_ncopy(file->new.name, buf, strlen(buf));
4709 if (!*file->old.name)
4710 string_copy(file->old.name, file->new.name);
4711 file = NULL;
4714 if (io_error(&io)) {
4715 error_out:
4716 done_io(&io);
4717 return FALSE;
4720 if (!view->line[view->lines - 1].data)
4721 add_line_data(view, NULL, LINE_STAT_NONE);
4723 done_io(&io);
4724 return TRUE;
4727 /* Don't show unmerged entries in the staged section. */
4728 static const char *status_diff_index_argv[] = {
4729 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4730 "--cached", "-M", "HEAD", NULL
4733 static const char *status_diff_files_argv[] = {
4734 "git", "diff-files", "-z", NULL
4737 static const char *status_list_other_argv[] = {
4738 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4741 static const char *status_list_no_head_argv[] = {
4742 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4745 static const char *update_index_argv[] = {
4746 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4749 /* Restore the previous line number to stay in the context or select a
4750 * line with something that can be updated. */
4751 static void
4752 status_restore(struct view *view)
4754 if (view->p_lineno >= view->lines)
4755 view->p_lineno = view->lines - 1;
4756 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4757 view->p_lineno++;
4758 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4759 view->p_lineno--;
4761 /* If the above fails, always skip the "On branch" line. */
4762 if (view->p_lineno < view->lines)
4763 view->lineno = view->p_lineno;
4764 else
4765 view->lineno = 1;
4767 if (view->lineno < view->offset)
4768 view->offset = view->lineno;
4769 else if (view->offset + view->height <= view->lineno)
4770 view->offset = view->lineno - view->height + 1;
4772 view->p_restore = FALSE;
4775 static void
4776 status_update_onbranch(void)
4778 static const char *paths[][2] = {
4779 { "rebase-apply/rebasing", "Rebasing" },
4780 { "rebase-apply/applying", "Applying mailbox" },
4781 { "rebase-apply/", "Rebasing mailbox" },
4782 { "rebase-merge/interactive", "Interactive rebase" },
4783 { "rebase-merge/", "Rebase merge" },
4784 { "MERGE_HEAD", "Merging" },
4785 { "BISECT_LOG", "Bisecting" },
4786 { "HEAD", "On branch" },
4788 char buf[SIZEOF_STR];
4789 struct stat stat;
4790 int i;
4792 if (is_initial_commit()) {
4793 string_copy(status_onbranch, "Initial commit");
4794 return;
4797 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4798 char *head = opt_head;
4800 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4801 lstat(buf, &stat) < 0)
4802 continue;
4804 if (!*opt_head) {
4805 struct io io = {};
4807 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4808 io_open(&io, buf) &&
4809 io_read_buf(&io, buf, sizeof(buf))) {
4810 head = chomp_string(buf);
4811 if (!prefixcmp(head, "refs/heads/"))
4812 head += STRING_SIZE("refs/heads/");
4816 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4817 string_copy(status_onbranch, opt_head);
4818 return;
4821 string_copy(status_onbranch, "Not currently on any branch");
4824 /* First parse staged info using git-diff-index(1), then parse unstaged
4825 * info using git-diff-files(1), and finally untracked files using
4826 * git-ls-files(1). */
4827 static bool
4828 status_open(struct view *view)
4830 reset_view(view);
4832 add_line_data(view, NULL, LINE_STAT_HEAD);
4833 status_update_onbranch();
4835 run_io_bg(update_index_argv);
4837 if (is_initial_commit()) {
4838 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4839 return FALSE;
4840 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4841 return FALSE;
4844 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4845 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4846 return FALSE;
4848 /* Restore the exact position or use the specialized restore
4849 * mode? */
4850 if (!view->p_restore)
4851 status_restore(view);
4852 return TRUE;
4855 static bool
4856 status_draw(struct view *view, struct line *line, unsigned int lineno)
4858 struct status *status = line->data;
4859 enum line_type type;
4860 const char *text;
4862 if (!status) {
4863 switch (line->type) {
4864 case LINE_STAT_STAGED:
4865 type = LINE_STAT_SECTION;
4866 text = "Changes to be committed:";
4867 break;
4869 case LINE_STAT_UNSTAGED:
4870 type = LINE_STAT_SECTION;
4871 text = "Changed but not updated:";
4872 break;
4874 case LINE_STAT_UNTRACKED:
4875 type = LINE_STAT_SECTION;
4876 text = "Untracked files:";
4877 break;
4879 case LINE_STAT_NONE:
4880 type = LINE_DEFAULT;
4881 text = " (no files)";
4882 break;
4884 case LINE_STAT_HEAD:
4885 type = LINE_STAT_HEAD;
4886 text = status_onbranch;
4887 break;
4889 default:
4890 return FALSE;
4892 } else {
4893 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4895 buf[0] = status->status;
4896 if (draw_text(view, line->type, buf, TRUE))
4897 return TRUE;
4898 type = LINE_DEFAULT;
4899 text = status->new.name;
4902 draw_text(view, type, text, TRUE);
4903 return TRUE;
4906 static enum request
4907 status_load_error(struct view *view, struct view *stage, const char *path)
4909 if (displayed_views() == 2 || display[current_view] != view)
4910 maximize_view(view);
4911 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4912 return REQ_NONE;
4915 static enum request
4916 status_enter(struct view *view, struct line *line)
4918 struct status *status = line->data;
4919 const char *oldpath = status ? status->old.name : NULL;
4920 /* Diffs for unmerged entries are empty when passing the new
4921 * path, so leave it empty. */
4922 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4923 const char *info;
4924 enum open_flags split;
4925 struct view *stage = VIEW(REQ_VIEW_STAGE);
4927 if (line->type == LINE_STAT_NONE ||
4928 (!status && line[1].type == LINE_STAT_NONE)) {
4929 report("No file to diff");
4930 return REQ_NONE;
4933 switch (line->type) {
4934 case LINE_STAT_STAGED:
4935 if (is_initial_commit()) {
4936 const char *no_head_diff_argv[] = {
4937 "git", "diff", "--no-color", "--patch-with-stat",
4938 "--", "/dev/null", newpath, NULL
4941 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4942 return status_load_error(view, stage, newpath);
4943 } else {
4944 const char *index_show_argv[] = {
4945 "git", "diff-index", "--root", "--patch-with-stat",
4946 "-C", "-M", "--cached", "HEAD", "--",
4947 oldpath, newpath, NULL
4950 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4951 return status_load_error(view, stage, newpath);
4954 if (status)
4955 info = "Staged changes to %s";
4956 else
4957 info = "Staged changes";
4958 break;
4960 case LINE_STAT_UNSTAGED:
4962 const char *files_show_argv[] = {
4963 "git", "diff-files", "--root", "--patch-with-stat",
4964 "-C", "-M", "--", oldpath, newpath, NULL
4967 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4968 return status_load_error(view, stage, newpath);
4969 if (status)
4970 info = "Unstaged changes to %s";
4971 else
4972 info = "Unstaged changes";
4973 break;
4975 case LINE_STAT_UNTRACKED:
4976 if (!newpath) {
4977 report("No file to show");
4978 return REQ_NONE;
4981 if (!suffixcmp(status->new.name, -1, "/")) {
4982 report("Cannot display a directory");
4983 return REQ_NONE;
4986 if (!prepare_update_file(stage, newpath))
4987 return status_load_error(view, stage, newpath);
4988 info = "Untracked file %s";
4989 break;
4991 case LINE_STAT_HEAD:
4992 return REQ_NONE;
4994 default:
4995 die("line type %d not handled in switch", line->type);
4998 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4999 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5000 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5001 if (status) {
5002 stage_status = *status;
5003 } else {
5004 memset(&stage_status, 0, sizeof(stage_status));
5007 stage_line_type = line->type;
5008 stage_chunks = 0;
5009 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5012 return REQ_NONE;
5015 static bool
5016 status_exists(struct status *status, enum line_type type)
5018 struct view *view = VIEW(REQ_VIEW_STATUS);
5019 unsigned long lineno;
5021 for (lineno = 0; lineno < view->lines; lineno++) {
5022 struct line *line = &view->line[lineno];
5023 struct status *pos = line->data;
5025 if (line->type != type)
5026 continue;
5027 if (!pos && (!status || !status->status) && line[1].data) {
5028 select_view_line(view, lineno);
5029 return TRUE;
5031 if (pos && !strcmp(status->new.name, pos->new.name)) {
5032 select_view_line(view, lineno);
5033 return TRUE;
5037 return FALSE;
5041 static bool
5042 status_update_prepare(struct io *io, enum line_type type)
5044 const char *staged_argv[] = {
5045 "git", "update-index", "-z", "--index-info", NULL
5047 const char *others_argv[] = {
5048 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5051 switch (type) {
5052 case LINE_STAT_STAGED:
5053 return run_io(io, staged_argv, opt_cdup, IO_WR);
5055 case LINE_STAT_UNSTAGED:
5056 return run_io(io, others_argv, opt_cdup, IO_WR);
5058 case LINE_STAT_UNTRACKED:
5059 return run_io(io, others_argv, NULL, IO_WR);
5061 default:
5062 die("line type %d not handled in switch", type);
5063 return FALSE;
5067 static bool
5068 status_update_write(struct io *io, struct status *status, enum line_type type)
5070 char buf[SIZEOF_STR];
5071 size_t bufsize = 0;
5073 switch (type) {
5074 case LINE_STAT_STAGED:
5075 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5076 status->old.mode,
5077 status->old.rev,
5078 status->old.name, 0))
5079 return FALSE;
5080 break;
5082 case LINE_STAT_UNSTAGED:
5083 case LINE_STAT_UNTRACKED:
5084 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5085 return FALSE;
5086 break;
5088 default:
5089 die("line type %d not handled in switch", type);
5092 return io_write(io, buf, bufsize);
5095 static bool
5096 status_update_file(struct status *status, enum line_type type)
5098 struct io io = {};
5099 bool result;
5101 if (!status_update_prepare(&io, type))
5102 return FALSE;
5104 result = status_update_write(&io, status, type);
5105 return done_io(&io) && result;
5108 static bool
5109 status_update_files(struct view *view, struct line *line)
5111 char buf[sizeof(view->ref)];
5112 struct io io = {};
5113 bool result = TRUE;
5114 struct line *pos = view->line + view->lines;
5115 int files = 0;
5116 int file, done;
5118 if (!status_update_prepare(&io, line->type))
5119 return FALSE;
5121 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5122 files++;
5124 string_copy(buf, view->ref);
5125 for (file = 0, done = 5; result && file < files; line++, file++) {
5126 int almost_done = file * 100 / files;
5128 if (almost_done > done) {
5129 done = almost_done;
5130 string_format(view->ref, "updating file %u of %u (%d%% done)",
5131 file, files, done);
5132 update_view_title(view);
5133 doupdate();
5135 result = status_update_write(&io, line->data, line->type);
5137 string_copy(view->ref, buf);
5139 return done_io(&io) && result;
5142 static bool
5143 status_update(struct view *view)
5145 struct line *line = &view->line[view->lineno];
5147 assert(view->lines);
5149 if (!line->data) {
5150 /* This should work even for the "On branch" line. */
5151 if (line < view->line + view->lines && !line[1].data) {
5152 report("Nothing to update");
5153 return FALSE;
5156 if (!status_update_files(view, line + 1)) {
5157 report("Failed to update file status");
5158 return FALSE;
5161 } else if (!status_update_file(line->data, line->type)) {
5162 report("Failed to update file status");
5163 return FALSE;
5166 return TRUE;
5169 static bool
5170 status_revert(struct status *status, enum line_type type, bool has_none)
5172 if (!status || type != LINE_STAT_UNSTAGED) {
5173 if (type == LINE_STAT_STAGED) {
5174 report("Cannot revert changes to staged files");
5175 } else if (type == LINE_STAT_UNTRACKED) {
5176 report("Cannot revert changes to untracked files");
5177 } else if (has_none) {
5178 report("Nothing to revert");
5179 } else {
5180 report("Cannot revert changes to multiple files");
5182 return FALSE;
5184 } else {
5185 char mode[10] = "100644";
5186 const char *reset_argv[] = {
5187 "git", "update-index", "--cacheinfo", mode,
5188 status->old.rev, status->old.name, NULL
5190 const char *checkout_argv[] = {
5191 "git", "checkout", "--", status->old.name, NULL
5194 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5195 return FALSE;
5196 string_format(mode, "%o", status->old.mode);
5197 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5198 run_io_fg(checkout_argv, opt_cdup);
5202 static enum request
5203 status_request(struct view *view, enum request request, struct line *line)
5205 struct status *status = line->data;
5207 switch (request) {
5208 case REQ_STATUS_UPDATE:
5209 if (!status_update(view))
5210 return REQ_NONE;
5211 break;
5213 case REQ_STATUS_REVERT:
5214 if (!status_revert(status, line->type, status_has_none(view, line)))
5215 return REQ_NONE;
5216 break;
5218 case REQ_STATUS_MERGE:
5219 if (!status || status->status != 'U') {
5220 report("Merging only possible for files with unmerged status ('U').");
5221 return REQ_NONE;
5223 open_mergetool(status->new.name);
5224 break;
5226 case REQ_EDIT:
5227 if (!status)
5228 return request;
5229 if (status->status == 'D') {
5230 report("File has been deleted.");
5231 return REQ_NONE;
5234 open_editor(status->status != '?', status->new.name);
5235 break;
5237 case REQ_VIEW_BLAME:
5238 if (status) {
5239 string_copy(opt_file, status->new.name);
5240 opt_ref[0] = 0;
5242 return request;
5244 case REQ_ENTER:
5245 /* After returning the status view has been split to
5246 * show the stage view. No further reloading is
5247 * necessary. */
5248 return status_enter(view, line);
5250 case REQ_REFRESH:
5251 /* Simply reload the view. */
5252 break;
5254 default:
5255 return request;
5258 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5260 return REQ_NONE;
5263 static void
5264 status_select(struct view *view, struct line *line)
5266 struct status *status = line->data;
5267 char file[SIZEOF_STR] = "all files";
5268 const char *text;
5269 const char *key;
5271 if (status && !string_format(file, "'%s'", status->new.name))
5272 return;
5274 if (!status && line[1].type == LINE_STAT_NONE)
5275 line++;
5277 switch (line->type) {
5278 case LINE_STAT_STAGED:
5279 text = "Press %s to unstage %s for commit";
5280 break;
5282 case LINE_STAT_UNSTAGED:
5283 text = "Press %s to stage %s for commit";
5284 break;
5286 case LINE_STAT_UNTRACKED:
5287 text = "Press %s to stage %s for addition";
5288 break;
5290 case LINE_STAT_HEAD:
5291 case LINE_STAT_NONE:
5292 text = "Nothing to update";
5293 break;
5295 default:
5296 die("line type %d not handled in switch", line->type);
5299 if (status && status->status == 'U') {
5300 text = "Press %s to resolve conflict in %s";
5301 key = get_key(REQ_STATUS_MERGE);
5303 } else {
5304 key = get_key(REQ_STATUS_UPDATE);
5307 string_format(view->ref, text, key, file);
5310 static bool
5311 status_grep(struct view *view, struct line *line)
5313 struct status *status = line->data;
5314 enum { S_STATUS, S_NAME, S_END } state;
5315 char buf[2] = "?";
5316 regmatch_t pmatch;
5318 if (!status)
5319 return FALSE;
5321 for (state = S_STATUS; state < S_END; state++) {
5322 const char *text;
5324 switch (state) {
5325 case S_NAME: text = status->new.name; break;
5326 case S_STATUS:
5327 buf[0] = status->status;
5328 text = buf;
5329 break;
5331 default:
5332 return FALSE;
5335 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5336 return TRUE;
5339 return FALSE;
5342 static struct view_ops status_ops = {
5343 "file",
5344 NULL,
5345 status_open,
5346 NULL,
5347 status_draw,
5348 status_request,
5349 status_grep,
5350 status_select,
5354 static bool
5355 stage_diff_write(struct io *io, struct line *line, struct line *end)
5357 while (line < end) {
5358 if (!io_write(io, line->data, strlen(line->data)) ||
5359 !io_write(io, "\n", 1))
5360 return FALSE;
5361 line++;
5362 if (line->type == LINE_DIFF_CHUNK ||
5363 line->type == LINE_DIFF_HEADER)
5364 break;
5367 return TRUE;
5370 static struct line *
5371 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5373 for (; view->line < line; line--)
5374 if (line->type == type)
5375 return line;
5377 return NULL;
5380 static bool
5381 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5383 const char *apply_argv[SIZEOF_ARG] = {
5384 "git", "apply", "--whitespace=nowarn", NULL
5386 struct line *diff_hdr;
5387 struct io io = {};
5388 int argc = 3;
5390 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5391 if (!diff_hdr)
5392 return FALSE;
5394 if (!revert)
5395 apply_argv[argc++] = "--cached";
5396 if (revert || stage_line_type == LINE_STAT_STAGED)
5397 apply_argv[argc++] = "-R";
5398 apply_argv[argc++] = "-";
5399 apply_argv[argc++] = NULL;
5400 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5401 return FALSE;
5403 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5404 !stage_diff_write(&io, chunk, view->line + view->lines))
5405 chunk = NULL;
5407 done_io(&io);
5408 run_io_bg(update_index_argv);
5410 return chunk ? TRUE : FALSE;
5413 static bool
5414 stage_update(struct view *view, struct line *line)
5416 struct line *chunk = NULL;
5418 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5419 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5421 if (chunk) {
5422 if (!stage_apply_chunk(view, chunk, FALSE)) {
5423 report("Failed to apply chunk");
5424 return FALSE;
5427 } else if (!stage_status.status) {
5428 view = VIEW(REQ_VIEW_STATUS);
5430 for (line = view->line; line < view->line + view->lines; line++)
5431 if (line->type == stage_line_type)
5432 break;
5434 if (!status_update_files(view, line + 1)) {
5435 report("Failed to update files");
5436 return FALSE;
5439 } else if (!status_update_file(&stage_status, stage_line_type)) {
5440 report("Failed to update file");
5441 return FALSE;
5444 return TRUE;
5447 static bool
5448 stage_revert(struct view *view, struct line *line)
5450 struct line *chunk = NULL;
5452 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5453 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5455 if (chunk) {
5456 if (!prompt_yesno("Are you sure you want to revert changes?"))
5457 return FALSE;
5459 if (!stage_apply_chunk(view, chunk, TRUE)) {
5460 report("Failed to revert chunk");
5461 return FALSE;
5463 return TRUE;
5465 } else {
5466 return status_revert(stage_status.status ? &stage_status : NULL,
5467 stage_line_type, FALSE);
5472 static void
5473 stage_next(struct view *view, struct line *line)
5475 int i;
5477 if (!stage_chunks) {
5478 static size_t alloc = 0;
5479 int *tmp;
5481 for (line = view->line; line < view->line + view->lines; line++) {
5482 if (line->type != LINE_DIFF_CHUNK)
5483 continue;
5485 tmp = realloc_items(stage_chunk, &alloc,
5486 stage_chunks, sizeof(*tmp));
5487 if (!tmp) {
5488 report("Allocation failure");
5489 return;
5492 stage_chunk = tmp;
5493 stage_chunk[stage_chunks++] = line - view->line;
5497 for (i = 0; i < stage_chunks; i++) {
5498 if (stage_chunk[i] > view->lineno) {
5499 do_scroll_view(view, stage_chunk[i] - view->lineno);
5500 report("Chunk %d of %d", i + 1, stage_chunks);
5501 return;
5505 report("No next chunk found");
5508 static enum request
5509 stage_request(struct view *view, enum request request, struct line *line)
5511 switch (request) {
5512 case REQ_STATUS_UPDATE:
5513 if (!stage_update(view, line))
5514 return REQ_NONE;
5515 break;
5517 case REQ_STATUS_REVERT:
5518 if (!stage_revert(view, line))
5519 return REQ_NONE;
5520 break;
5522 case REQ_STAGE_NEXT:
5523 if (stage_line_type == LINE_STAT_UNTRACKED) {
5524 report("File is untracked; press %s to add",
5525 get_key(REQ_STATUS_UPDATE));
5526 return REQ_NONE;
5528 stage_next(view, line);
5529 return REQ_NONE;
5531 case REQ_EDIT:
5532 if (!stage_status.new.name[0])
5533 return request;
5534 if (stage_status.status == 'D') {
5535 report("File has been deleted.");
5536 return REQ_NONE;
5539 open_editor(stage_status.status != '?', stage_status.new.name);
5540 break;
5542 case REQ_REFRESH:
5543 /* Reload everything ... */
5544 break;
5546 case REQ_VIEW_BLAME:
5547 if (stage_status.new.name[0]) {
5548 string_copy(opt_file, stage_status.new.name);
5549 opt_ref[0] = 0;
5551 return request;
5553 case REQ_ENTER:
5554 return pager_request(view, request, line);
5556 default:
5557 return request;
5560 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5561 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5563 /* Check whether the staged entry still exists, and close the
5564 * stage view if it doesn't. */
5565 if (!status_exists(&stage_status, stage_line_type)) {
5566 status_restore(VIEW(REQ_VIEW_STATUS));
5567 return REQ_VIEW_CLOSE;
5570 if (stage_line_type == LINE_STAT_UNTRACKED) {
5571 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5572 report("Cannot display a directory");
5573 return REQ_NONE;
5576 if (!prepare_update_file(view, stage_status.new.name)) {
5577 report("Failed to open file: %s", strerror(errno));
5578 return REQ_NONE;
5581 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5583 return REQ_NONE;
5586 static struct view_ops stage_ops = {
5587 "line",
5588 NULL,
5589 NULL,
5590 pager_read,
5591 pager_draw,
5592 stage_request,
5593 pager_grep,
5594 pager_select,
5599 * Revision graph
5602 struct commit {
5603 char id[SIZEOF_REV]; /* SHA1 ID. */
5604 char title[128]; /* First line of the commit message. */
5605 char author[75]; /* Author of the commit. */
5606 struct tm time; /* Date from the author ident. */
5607 struct ref **refs; /* Repository references. */
5608 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5609 size_t graph_size; /* The width of the graph array. */
5610 bool has_parents; /* Rewritten --parents seen. */
5613 /* Size of rev graph with no "padding" columns */
5614 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5616 struct rev_graph {
5617 struct rev_graph *prev, *next, *parents;
5618 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5619 size_t size;
5620 struct commit *commit;
5621 size_t pos;
5622 unsigned int boundary:1;
5625 /* Parents of the commit being visualized. */
5626 static struct rev_graph graph_parents[4];
5628 /* The current stack of revisions on the graph. */
5629 static struct rev_graph graph_stacks[4] = {
5630 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5631 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5632 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5633 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5636 static inline bool
5637 graph_parent_is_merge(struct rev_graph *graph)
5639 return graph->parents->size > 1;
5642 static inline void
5643 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5645 struct commit *commit = graph->commit;
5647 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5648 commit->graph[commit->graph_size++] = symbol;
5651 static void
5652 clear_rev_graph(struct rev_graph *graph)
5654 graph->boundary = 0;
5655 graph->size = graph->pos = 0;
5656 graph->commit = NULL;
5657 memset(graph->parents, 0, sizeof(*graph->parents));
5660 static void
5661 done_rev_graph(struct rev_graph *graph)
5663 if (graph_parent_is_merge(graph) &&
5664 graph->pos < graph->size - 1 &&
5665 graph->next->size == graph->size + graph->parents->size - 1) {
5666 size_t i = graph->pos + graph->parents->size - 1;
5668 graph->commit->graph_size = i * 2;
5669 while (i < graph->next->size - 1) {
5670 append_to_rev_graph(graph, ' ');
5671 append_to_rev_graph(graph, '\\');
5672 i++;
5676 clear_rev_graph(graph);
5679 static void
5680 push_rev_graph(struct rev_graph *graph, const char *parent)
5682 int i;
5684 /* "Collapse" duplicate parents lines.
5686 * FIXME: This needs to also update update the drawn graph but
5687 * for now it just serves as a method for pruning graph lines. */
5688 for (i = 0; i < graph->size; i++)
5689 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5690 return;
5692 if (graph->size < SIZEOF_REVITEMS) {
5693 string_copy_rev(graph->rev[graph->size++], parent);
5697 static chtype
5698 get_rev_graph_symbol(struct rev_graph *graph)
5700 chtype symbol;
5702 if (graph->boundary)
5703 symbol = REVGRAPH_BOUND;
5704 else if (graph->parents->size == 0)
5705 symbol = REVGRAPH_INIT;
5706 else if (graph_parent_is_merge(graph))
5707 symbol = REVGRAPH_MERGE;
5708 else if (graph->pos >= graph->size)
5709 symbol = REVGRAPH_BRANCH;
5710 else
5711 symbol = REVGRAPH_COMMIT;
5713 return symbol;
5716 static void
5717 draw_rev_graph(struct rev_graph *graph)
5719 struct rev_filler {
5720 chtype separator, line;
5722 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5723 static struct rev_filler fillers[] = {
5724 { ' ', '|' },
5725 { '`', '.' },
5726 { '\'', ' ' },
5727 { '/', ' ' },
5729 chtype symbol = get_rev_graph_symbol(graph);
5730 struct rev_filler *filler;
5731 size_t i;
5733 if (opt_line_graphics)
5734 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5736 filler = &fillers[DEFAULT];
5738 for (i = 0; i < graph->pos; i++) {
5739 append_to_rev_graph(graph, filler->line);
5740 if (graph_parent_is_merge(graph->prev) &&
5741 graph->prev->pos == i)
5742 filler = &fillers[RSHARP];
5744 append_to_rev_graph(graph, filler->separator);
5747 /* Place the symbol for this revision. */
5748 append_to_rev_graph(graph, symbol);
5750 if (graph->prev->size > graph->size)
5751 filler = &fillers[RDIAG];
5752 else
5753 filler = &fillers[DEFAULT];
5755 i++;
5757 for (; i < graph->size; i++) {
5758 append_to_rev_graph(graph, filler->separator);
5759 append_to_rev_graph(graph, filler->line);
5760 if (graph_parent_is_merge(graph->prev) &&
5761 i < graph->prev->pos + graph->parents->size)
5762 filler = &fillers[RSHARP];
5763 if (graph->prev->size > graph->size)
5764 filler = &fillers[LDIAG];
5767 if (graph->prev->size > graph->size) {
5768 append_to_rev_graph(graph, filler->separator);
5769 if (filler->line != ' ')
5770 append_to_rev_graph(graph, filler->line);
5774 /* Prepare the next rev graph */
5775 static void
5776 prepare_rev_graph(struct rev_graph *graph)
5778 size_t i;
5780 /* First, traverse all lines of revisions up to the active one. */
5781 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5782 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5783 break;
5785 push_rev_graph(graph->next, graph->rev[graph->pos]);
5788 /* Interleave the new revision parent(s). */
5789 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5790 push_rev_graph(graph->next, graph->parents->rev[i]);
5792 /* Lastly, put any remaining revisions. */
5793 for (i = graph->pos + 1; i < graph->size; i++)
5794 push_rev_graph(graph->next, graph->rev[i]);
5797 static void
5798 update_rev_graph(struct view *view, struct rev_graph *graph)
5800 /* If this is the finalizing update ... */
5801 if (graph->commit)
5802 prepare_rev_graph(graph);
5804 /* Graph visualization needs a one rev look-ahead,
5805 * so the first update doesn't visualize anything. */
5806 if (!graph->prev->commit)
5807 return;
5809 if (view->lines > 2)
5810 view->line[view->lines - 3].dirty = 1;
5811 if (view->lines > 1)
5812 view->line[view->lines - 2].dirty = 1;
5813 draw_rev_graph(graph->prev);
5814 done_rev_graph(graph->prev->prev);
5819 * Main view backend
5822 static const char *main_argv[SIZEOF_ARG] = {
5823 "git", "log", "--no-color", "--pretty=raw", "--parents",
5824 "--topo-order", "%(head)", NULL
5827 static bool
5828 main_draw(struct view *view, struct line *line, unsigned int lineno)
5830 struct commit *commit = line->data;
5832 if (!*commit->author)
5833 return FALSE;
5835 if (opt_date && draw_date(view, &commit->time))
5836 return TRUE;
5838 if (opt_author && draw_author(view, commit->author))
5839 return TRUE;
5841 if (opt_rev_graph && commit->graph_size &&
5842 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5843 return TRUE;
5845 if (opt_show_refs && commit->refs) {
5846 size_t i = 0;
5848 do {
5849 enum line_type type;
5851 if (commit->refs[i]->head)
5852 type = LINE_MAIN_HEAD;
5853 else if (commit->refs[i]->ltag)
5854 type = LINE_MAIN_LOCAL_TAG;
5855 else if (commit->refs[i]->tag)
5856 type = LINE_MAIN_TAG;
5857 else if (commit->refs[i]->tracked)
5858 type = LINE_MAIN_TRACKED;
5859 else if (commit->refs[i]->remote)
5860 type = LINE_MAIN_REMOTE;
5861 else
5862 type = LINE_MAIN_REF;
5864 if (draw_text(view, type, "[", TRUE) ||
5865 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5866 draw_text(view, type, "]", TRUE))
5867 return TRUE;
5869 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5870 return TRUE;
5871 } while (commit->refs[i++]->next);
5874 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5875 return TRUE;
5878 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5879 static bool
5880 main_read(struct view *view, char *line)
5882 static struct rev_graph *graph = graph_stacks;
5883 enum line_type type;
5884 struct commit *commit;
5886 if (!line) {
5887 int i;
5889 if (!view->lines && !view->parent)
5890 die("No revisions match the given arguments.");
5891 if (view->lines > 0) {
5892 commit = view->line[view->lines - 1].data;
5893 view->line[view->lines - 1].dirty = 1;
5894 if (!*commit->author) {
5895 view->lines--;
5896 free(commit);
5897 graph->commit = NULL;
5900 update_rev_graph(view, graph);
5902 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5903 clear_rev_graph(&graph_stacks[i]);
5904 return TRUE;
5907 type = get_line_type(line);
5908 if (type == LINE_COMMIT) {
5909 commit = calloc(1, sizeof(struct commit));
5910 if (!commit)
5911 return FALSE;
5913 line += STRING_SIZE("commit ");
5914 if (*line == '-') {
5915 graph->boundary = 1;
5916 line++;
5919 string_copy_rev(commit->id, line);
5920 commit->refs = get_refs(commit->id);
5921 graph->commit = commit;
5922 add_line_data(view, commit, LINE_MAIN_COMMIT);
5924 while ((line = strchr(line, ' '))) {
5925 line++;
5926 push_rev_graph(graph->parents, line);
5927 commit->has_parents = TRUE;
5929 return TRUE;
5932 if (!view->lines)
5933 return TRUE;
5934 commit = view->line[view->lines - 1].data;
5936 switch (type) {
5937 case LINE_PARENT:
5938 if (commit->has_parents)
5939 break;
5940 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5941 break;
5943 case LINE_AUTHOR:
5944 parse_author_line(line + STRING_SIZE("author "),
5945 commit->author, sizeof(commit->author),
5946 &commit->time);
5947 update_rev_graph(view, graph);
5948 graph = graph->next;
5949 break;
5951 default:
5952 /* Fill in the commit title if it has not already been set. */
5953 if (commit->title[0])
5954 break;
5956 /* Require titles to start with a non-space character at the
5957 * offset used by git log. */
5958 if (strncmp(line, " ", 4))
5959 break;
5960 line += 4;
5961 /* Well, if the title starts with a whitespace character,
5962 * try to be forgiving. Otherwise we end up with no title. */
5963 while (isspace(*line))
5964 line++;
5965 if (*line == '\0')
5966 break;
5967 /* FIXME: More graceful handling of titles; append "..." to
5968 * shortened titles, etc. */
5970 string_expand(commit->title, sizeof(commit->title), line, 1);
5971 view->line[view->lines - 1].dirty = 1;
5974 return TRUE;
5977 static enum request
5978 main_request(struct view *view, enum request request, struct line *line)
5980 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5982 switch (request) {
5983 case REQ_ENTER:
5984 open_view(view, REQ_VIEW_DIFF, flags);
5985 break;
5986 case REQ_REFRESH:
5987 load_refs();
5988 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5989 break;
5990 default:
5991 return request;
5994 return REQ_NONE;
5997 static bool
5998 grep_refs(struct ref **refs, regex_t *regex)
6000 regmatch_t pmatch;
6001 size_t i = 0;
6003 if (!refs)
6004 return FALSE;
6005 do {
6006 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6007 return TRUE;
6008 } while (refs[i++]->next);
6010 return FALSE;
6013 static bool
6014 main_grep(struct view *view, struct line *line)
6016 struct commit *commit = line->data;
6017 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6018 char buf[DATE_COLS + 1];
6019 regmatch_t pmatch;
6021 for (state = S_TITLE; state < S_END; state++) {
6022 char *text;
6024 switch (state) {
6025 case S_TITLE: text = commit->title; break;
6026 case S_AUTHOR:
6027 if (!opt_author)
6028 continue;
6029 text = commit->author;
6030 break;
6031 case S_DATE:
6032 if (!opt_date)
6033 continue;
6034 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6035 continue;
6036 text = buf;
6037 break;
6038 case S_REFS:
6039 if (!opt_show_refs)
6040 continue;
6041 if (grep_refs(commit->refs, view->regex) == TRUE)
6042 return TRUE;
6043 continue;
6044 default:
6045 return FALSE;
6048 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6049 return TRUE;
6052 return FALSE;
6055 static void
6056 main_select(struct view *view, struct line *line)
6058 struct commit *commit = line->data;
6060 string_copy_rev(view->ref, commit->id);
6061 string_copy_rev(ref_commit, view->ref);
6064 static struct view_ops main_ops = {
6065 "commit",
6066 main_argv,
6067 NULL,
6068 main_read,
6069 main_draw,
6070 main_request,
6071 main_grep,
6072 main_select,
6077 * Unicode / UTF-8 handling
6079 * NOTE: Much of the following code for dealing with Unicode is derived from
6080 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6081 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6084 static inline int
6085 unicode_width(unsigned long c)
6087 if (c >= 0x1100 &&
6088 (c <= 0x115f /* Hangul Jamo */
6089 || c == 0x2329
6090 || c == 0x232a
6091 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6092 /* CJK ... Yi */
6093 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6094 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6095 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6096 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6097 || (c >= 0xffe0 && c <= 0xffe6)
6098 || (c >= 0x20000 && c <= 0x2fffd)
6099 || (c >= 0x30000 && c <= 0x3fffd)))
6100 return 2;
6102 if (c == '\t')
6103 return opt_tab_size;
6105 return 1;
6108 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6109 * Illegal bytes are set one. */
6110 static const unsigned char utf8_bytes[256] = {
6111 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,
6112 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,
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 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,
6118 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,
6121 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6122 static inline unsigned long
6123 utf8_to_unicode(const char *string, size_t length)
6125 unsigned long unicode;
6127 switch (length) {
6128 case 1:
6129 unicode = string[0];
6130 break;
6131 case 2:
6132 unicode = (string[0] & 0x1f) << 6;
6133 unicode += (string[1] & 0x3f);
6134 break;
6135 case 3:
6136 unicode = (string[0] & 0x0f) << 12;
6137 unicode += ((string[1] & 0x3f) << 6);
6138 unicode += (string[2] & 0x3f);
6139 break;
6140 case 4:
6141 unicode = (string[0] & 0x0f) << 18;
6142 unicode += ((string[1] & 0x3f) << 12);
6143 unicode += ((string[2] & 0x3f) << 6);
6144 unicode += (string[3] & 0x3f);
6145 break;
6146 case 5:
6147 unicode = (string[0] & 0x0f) << 24;
6148 unicode += ((string[1] & 0x3f) << 18);
6149 unicode += ((string[2] & 0x3f) << 12);
6150 unicode += ((string[3] & 0x3f) << 6);
6151 unicode += (string[4] & 0x3f);
6152 break;
6153 case 6:
6154 unicode = (string[0] & 0x01) << 30;
6155 unicode += ((string[1] & 0x3f) << 24);
6156 unicode += ((string[2] & 0x3f) << 18);
6157 unicode += ((string[3] & 0x3f) << 12);
6158 unicode += ((string[4] & 0x3f) << 6);
6159 unicode += (string[5] & 0x3f);
6160 break;
6161 default:
6162 die("Invalid Unicode length");
6165 /* Invalid characters could return the special 0xfffd value but NUL
6166 * should be just as good. */
6167 return unicode > 0xffff ? 0 : unicode;
6170 /* Calculates how much of string can be shown within the given maximum width
6171 * and sets trimmed parameter to non-zero value if all of string could not be
6172 * shown. If the reserve flag is TRUE, it will reserve at least one
6173 * trailing character, which can be useful when drawing a delimiter.
6175 * Returns the number of bytes to output from string to satisfy max_width. */
6176 static size_t
6177 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6179 const char *string = *start;
6180 const char *end = strchr(string, '\0');
6181 unsigned char last_bytes = 0;
6182 size_t last_ucwidth = 0;
6184 *width = 0;
6185 *trimmed = 0;
6187 while (string < end) {
6188 int c = *(unsigned char *) string;
6189 unsigned char bytes = utf8_bytes[c];
6190 size_t ucwidth;
6191 unsigned long unicode;
6193 if (string + bytes > end)
6194 break;
6196 /* Change representation to figure out whether
6197 * it is a single- or double-width character. */
6199 unicode = utf8_to_unicode(string, bytes);
6200 /* FIXME: Graceful handling of invalid Unicode character. */
6201 if (!unicode)
6202 break;
6204 ucwidth = unicode_width(unicode);
6205 if (skip > 0) {
6206 skip -= ucwidth <= skip ? ucwidth : skip;
6207 *start += bytes;
6209 *width += ucwidth;
6210 if (*width > max_width) {
6211 *trimmed = 1;
6212 *width -= ucwidth;
6213 if (reserve && *width == max_width) {
6214 string -= last_bytes;
6215 *width -= last_ucwidth;
6217 break;
6220 string += bytes;
6221 last_bytes = ucwidth ? bytes : 0;
6222 last_ucwidth = ucwidth;
6225 return string - *start;
6230 * Status management
6233 /* Whether or not the curses interface has been initialized. */
6234 static bool cursed = FALSE;
6236 /* Terminal hacks and workarounds. */
6237 static bool use_scroll_redrawwin;
6238 static bool use_scroll_status_wclear;
6240 /* The status window is used for polling keystrokes. */
6241 static WINDOW *status_win;
6243 /* Reading from the prompt? */
6244 static bool input_mode = FALSE;
6246 static bool status_empty = FALSE;
6248 /* Update status and title window. */
6249 static void
6250 report(const char *msg, ...)
6252 struct view *view = display[current_view];
6254 if (input_mode)
6255 return;
6257 if (!view) {
6258 char buf[SIZEOF_STR];
6259 va_list args;
6261 va_start(args, msg);
6262 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6263 buf[sizeof(buf) - 1] = 0;
6264 buf[sizeof(buf) - 2] = '.';
6265 buf[sizeof(buf) - 3] = '.';
6266 buf[sizeof(buf) - 4] = '.';
6268 va_end(args);
6269 die("%s", buf);
6272 if (!status_empty || *msg) {
6273 va_list args;
6275 va_start(args, msg);
6277 wmove(status_win, 0, 0);
6278 if (view->has_scrolled && use_scroll_status_wclear)
6279 wclear(status_win);
6280 if (*msg) {
6281 vwprintw(status_win, msg, args);
6282 status_empty = FALSE;
6283 } else {
6284 status_empty = TRUE;
6286 wclrtoeol(status_win);
6287 wnoutrefresh(status_win);
6289 va_end(args);
6292 update_view_title(view);
6295 /* Controls when nodelay should be in effect when polling user input. */
6296 static void
6297 set_nonblocking_input(bool loading)
6299 static unsigned int loading_views;
6301 if ((loading == FALSE && loading_views-- == 1) ||
6302 (loading == TRUE && loading_views++ == 0))
6303 nodelay(status_win, loading);
6306 static void
6307 init_display(void)
6309 const char *term;
6310 int x, y;
6312 /* Initialize the curses library */
6313 if (isatty(STDIN_FILENO)) {
6314 cursed = !!initscr();
6315 opt_tty = stdin;
6316 } else {
6317 /* Leave stdin and stdout alone when acting as a pager. */
6318 opt_tty = fopen("/dev/tty", "r+");
6319 if (!opt_tty)
6320 die("Failed to open /dev/tty");
6321 cursed = !!newterm(NULL, opt_tty, opt_tty);
6324 if (!cursed)
6325 die("Failed to initialize curses");
6327 nonl(); /* Disable conversion and detect newlines from input. */
6328 cbreak(); /* Take input chars one at a time, no wait for \n */
6329 noecho(); /* Don't echo input */
6330 leaveok(stdscr, FALSE);
6332 if (has_colors())
6333 init_colors();
6335 getmaxyx(stdscr, y, x);
6336 status_win = newwin(1, 0, y - 1, 0);
6337 if (!status_win)
6338 die("Failed to create status window");
6340 /* Enable keyboard mapping */
6341 keypad(status_win, TRUE);
6342 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6344 TABSIZE = opt_tab_size;
6345 if (opt_line_graphics) {
6346 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6349 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6350 if (term && !strcmp(term, "gnome-terminal")) {
6351 /* In the gnome-terminal-emulator, the message from
6352 * scrolling up one line when impossible followed by
6353 * scrolling down one line causes corruption of the
6354 * status line. This is fixed by calling wclear. */
6355 use_scroll_status_wclear = TRUE;
6356 use_scroll_redrawwin = FALSE;
6358 } else if (term && !strcmp(term, "xrvt-xpm")) {
6359 /* No problems with full optimizations in xrvt-(unicode)
6360 * and aterm. */
6361 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6363 } else {
6364 /* When scrolling in (u)xterm the last line in the
6365 * scrolling direction will update slowly. */
6366 use_scroll_redrawwin = TRUE;
6367 use_scroll_status_wclear = FALSE;
6371 static int
6372 get_input(int prompt_position)
6374 struct view *view;
6375 int i, key, cursor_y, cursor_x;
6377 if (prompt_position)
6378 input_mode = TRUE;
6380 while (TRUE) {
6381 foreach_view (view, i) {
6382 update_view(view);
6383 if (view_is_displayed(view) && view->has_scrolled &&
6384 use_scroll_redrawwin)
6385 redrawwin(view->win);
6386 view->has_scrolled = FALSE;
6389 /* Update the cursor position. */
6390 if (prompt_position) {
6391 getbegyx(status_win, cursor_y, cursor_x);
6392 cursor_x = prompt_position;
6393 } else {
6394 view = display[current_view];
6395 getbegyx(view->win, cursor_y, cursor_x);
6396 cursor_x = view->width - 1;
6397 cursor_y += view->lineno - view->offset;
6399 setsyx(cursor_y, cursor_x);
6401 /* Refresh, accept single keystroke of input */
6402 doupdate();
6403 key = wgetch(status_win);
6405 /* wgetch() with nodelay() enabled returns ERR when
6406 * there's no input. */
6407 if (key == ERR) {
6409 } else if (key == KEY_RESIZE) {
6410 int height, width;
6412 getmaxyx(stdscr, height, width);
6414 wresize(status_win, 1, width);
6415 mvwin(status_win, height - 1, 0);
6416 wnoutrefresh(status_win);
6417 resize_display();
6418 redraw_display(TRUE);
6420 } else {
6421 input_mode = FALSE;
6422 return key;
6427 static char *
6428 prompt_input(const char *prompt, input_handler handler, void *data)
6430 enum input_status status = INPUT_OK;
6431 static char buf[SIZEOF_STR];
6432 size_t pos = 0;
6434 buf[pos] = 0;
6436 while (status == INPUT_OK || status == INPUT_SKIP) {
6437 int key;
6439 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6440 wclrtoeol(status_win);
6442 key = get_input(pos + 1);
6443 switch (key) {
6444 case KEY_RETURN:
6445 case KEY_ENTER:
6446 case '\n':
6447 status = pos ? INPUT_STOP : INPUT_CANCEL;
6448 break;
6450 case KEY_BACKSPACE:
6451 if (pos > 0)
6452 buf[--pos] = 0;
6453 else
6454 status = INPUT_CANCEL;
6455 break;
6457 case KEY_ESC:
6458 status = INPUT_CANCEL;
6459 break;
6461 default:
6462 if (pos >= sizeof(buf)) {
6463 report("Input string too long");
6464 return NULL;
6467 status = handler(data, buf, key);
6468 if (status == INPUT_OK)
6469 buf[pos++] = (char) key;
6473 /* Clear the status window */
6474 status_empty = FALSE;
6475 report("");
6477 if (status == INPUT_CANCEL)
6478 return NULL;
6480 buf[pos++] = 0;
6482 return buf;
6485 static enum input_status
6486 prompt_yesno_handler(void *data, char *buf, int c)
6488 if (c == 'y' || c == 'Y')
6489 return INPUT_STOP;
6490 if (c == 'n' || c == 'N')
6491 return INPUT_CANCEL;
6492 return INPUT_SKIP;
6495 static bool
6496 prompt_yesno(const char *prompt)
6498 char prompt2[SIZEOF_STR];
6500 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6501 return FALSE;
6503 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6506 static enum input_status
6507 read_prompt_handler(void *data, char *buf, int c)
6509 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6512 static char *
6513 read_prompt(const char *prompt)
6515 return prompt_input(prompt, read_prompt_handler, NULL);
6519 * Repository properties
6522 static struct ref *refs = NULL;
6523 static size_t refs_alloc = 0;
6524 static size_t refs_size = 0;
6526 /* Id <-> ref store */
6527 static struct ref ***id_refs = NULL;
6528 static size_t id_refs_alloc = 0;
6529 static size_t id_refs_size = 0;
6531 static int
6532 compare_refs(const void *ref1_, const void *ref2_)
6534 const struct ref *ref1 = *(const struct ref **)ref1_;
6535 const struct ref *ref2 = *(const struct ref **)ref2_;
6537 if (ref1->tag != ref2->tag)
6538 return ref2->tag - ref1->tag;
6539 if (ref1->ltag != ref2->ltag)
6540 return ref2->ltag - ref2->ltag;
6541 if (ref1->head != ref2->head)
6542 return ref2->head - ref1->head;
6543 if (ref1->tracked != ref2->tracked)
6544 return ref2->tracked - ref1->tracked;
6545 if (ref1->remote != ref2->remote)
6546 return ref2->remote - ref1->remote;
6547 return strcmp(ref1->name, ref2->name);
6550 static struct ref **
6551 get_refs(const char *id)
6553 struct ref ***tmp_id_refs;
6554 struct ref **ref_list = NULL;
6555 size_t ref_list_alloc = 0;
6556 size_t ref_list_size = 0;
6557 size_t i;
6559 for (i = 0; i < id_refs_size; i++)
6560 if (!strcmp(id, id_refs[i][0]->id))
6561 return id_refs[i];
6563 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6564 sizeof(*id_refs));
6565 if (!tmp_id_refs)
6566 return NULL;
6568 id_refs = tmp_id_refs;
6570 for (i = 0; i < refs_size; i++) {
6571 struct ref **tmp;
6573 if (strcmp(id, refs[i].id))
6574 continue;
6576 tmp = realloc_items(ref_list, &ref_list_alloc,
6577 ref_list_size + 1, sizeof(*ref_list));
6578 if (!tmp) {
6579 if (ref_list)
6580 free(ref_list);
6581 return NULL;
6584 ref_list = tmp;
6585 ref_list[ref_list_size] = &refs[i];
6586 /* XXX: The properties of the commit chains ensures that we can
6587 * safely modify the shared ref. The repo references will
6588 * always be similar for the same id. */
6589 ref_list[ref_list_size]->next = 1;
6591 ref_list_size++;
6594 if (ref_list) {
6595 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6596 ref_list[ref_list_size - 1]->next = 0;
6597 id_refs[id_refs_size++] = ref_list;
6600 return ref_list;
6603 static int
6604 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6606 struct ref *ref;
6607 bool tag = FALSE;
6608 bool ltag = FALSE;
6609 bool remote = FALSE;
6610 bool tracked = FALSE;
6611 bool check_replace = FALSE;
6612 bool head = FALSE;
6614 if (!prefixcmp(name, "refs/tags/")) {
6615 if (!suffixcmp(name, namelen, "^{}")) {
6616 namelen -= 3;
6617 name[namelen] = 0;
6618 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6619 check_replace = TRUE;
6620 } else {
6621 ltag = TRUE;
6624 tag = TRUE;
6625 namelen -= STRING_SIZE("refs/tags/");
6626 name += STRING_SIZE("refs/tags/");
6628 } else if (!prefixcmp(name, "refs/remotes/")) {
6629 remote = TRUE;
6630 namelen -= STRING_SIZE("refs/remotes/");
6631 name += STRING_SIZE("refs/remotes/");
6632 tracked = !strcmp(opt_remote, name);
6634 } else if (!prefixcmp(name, "refs/heads/")) {
6635 namelen -= STRING_SIZE("refs/heads/");
6636 name += STRING_SIZE("refs/heads/");
6637 head = !strncmp(opt_head, name, namelen);
6639 } else if (!strcmp(name, "HEAD")) {
6640 string_ncopy(opt_head_rev, id, idlen);
6641 return OK;
6644 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6645 /* it's an annotated tag, replace the previous SHA1 with the
6646 * resolved commit id; relies on the fact git-ls-remote lists
6647 * the commit id of an annotated tag right before the commit id
6648 * it points to. */
6649 refs[refs_size - 1].ltag = ltag;
6650 string_copy_rev(refs[refs_size - 1].id, id);
6652 return OK;
6654 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6655 if (!refs)
6656 return ERR;
6658 ref = &refs[refs_size++];
6659 ref->name = malloc(namelen + 1);
6660 if (!ref->name)
6661 return ERR;
6663 strncpy(ref->name, name, namelen);
6664 ref->name[namelen] = 0;
6665 ref->head = head;
6666 ref->tag = tag;
6667 ref->ltag = ltag;
6668 ref->remote = remote;
6669 ref->tracked = tracked;
6670 string_copy_rev(ref->id, id);
6672 return OK;
6675 static int
6676 load_refs(void)
6678 static const char *ls_remote_argv[SIZEOF_ARG] = {
6679 "git", "ls-remote", opt_git_dir, NULL
6681 static bool init = FALSE;
6683 if (!init) {
6684 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6685 init = TRUE;
6688 if (!*opt_git_dir)
6689 return OK;
6691 while (refs_size > 0)
6692 free(refs[--refs_size].name);
6693 while (id_refs_size > 0)
6694 free(id_refs[--id_refs_size]);
6696 return run_io_load(ls_remote_argv, "\t", read_ref);
6699 static void
6700 set_remote_branch(const char *name, const char *value, size_t valuelen)
6702 if (!strcmp(name, ".remote")) {
6703 string_ncopy(opt_remote, value, valuelen);
6705 } else if (*opt_remote && !strcmp(name, ".merge")) {
6706 size_t from = strlen(opt_remote);
6708 if (!prefixcmp(value, "refs/heads/"))
6709 value += STRING_SIZE("refs/heads/");
6711 if (!string_format_from(opt_remote, &from, "/%s", value))
6712 opt_remote[0] = 0;
6716 static void
6717 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6719 const char *argv[SIZEOF_ARG] = { name, "=" };
6720 int argc = 1 + (cmd == option_set_command);
6721 int error = ERR;
6723 if (!argv_from_string(argv, &argc, value))
6724 config_msg = "Too many option arguments";
6725 else
6726 error = cmd(argc, argv);
6728 if (error == ERR)
6729 warn("Option 'tig.%s': %s", name, config_msg);
6732 static bool
6733 set_environment_variable(const char *name, const char *value)
6735 size_t len = strlen(name) + 1 + strlen(value) + 1;
6736 char *env = malloc(len);
6738 if (env &&
6739 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6740 putenv(env) == 0)
6741 return TRUE;
6742 free(env);
6743 return FALSE;
6746 static void
6747 set_work_tree(const char *value)
6749 char cwd[SIZEOF_STR];
6751 if (!getcwd(cwd, sizeof(cwd)))
6752 die("Failed to get cwd path: %s", strerror(errno));
6753 if (chdir(opt_git_dir) < 0)
6754 die("Failed to chdir(%s): %s", strerror(errno));
6755 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6756 die("Failed to get git path: %s", strerror(errno));
6757 if (chdir(cwd) < 0)
6758 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6759 if (chdir(value) < 0)
6760 die("Failed to chdir(%s): %s", value, strerror(errno));
6761 if (!getcwd(cwd, sizeof(cwd)))
6762 die("Failed to get cwd path: %s", strerror(errno));
6763 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6764 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6765 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6766 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6767 opt_is_inside_work_tree = TRUE;
6770 static int
6771 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6773 if (!strcmp(name, "i18n.commitencoding"))
6774 string_ncopy(opt_encoding, value, valuelen);
6776 else if (!strcmp(name, "core.editor"))
6777 string_ncopy(opt_editor, value, valuelen);
6779 else if (!strcmp(name, "core.worktree"))
6780 set_work_tree(value);
6782 else if (!prefixcmp(name, "tig.color."))
6783 set_repo_config_option(name + 10, value, option_color_command);
6785 else if (!prefixcmp(name, "tig.bind."))
6786 set_repo_config_option(name + 9, value, option_bind_command);
6788 else if (!prefixcmp(name, "tig."))
6789 set_repo_config_option(name + 4, value, option_set_command);
6791 else if (*opt_head && !prefixcmp(name, "branch.") &&
6792 !strncmp(name + 7, opt_head, strlen(opt_head)))
6793 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6795 return OK;
6798 static int
6799 load_git_config(void)
6801 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6803 return run_io_load(config_list_argv, "=", read_repo_config_option);
6806 static int
6807 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6809 if (!opt_git_dir[0]) {
6810 string_ncopy(opt_git_dir, name, namelen);
6812 } else if (opt_is_inside_work_tree == -1) {
6813 /* This can be 3 different values depending on the
6814 * version of git being used. If git-rev-parse does not
6815 * understand --is-inside-work-tree it will simply echo
6816 * the option else either "true" or "false" is printed.
6817 * Default to true for the unknown case. */
6818 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6820 } else if (*name == '.') {
6821 string_ncopy(opt_cdup, name, namelen);
6823 } else {
6824 string_ncopy(opt_prefix, name, namelen);
6827 return OK;
6830 static int
6831 load_repo_info(void)
6833 const char *head_argv[] = {
6834 "git", "symbolic-ref", "HEAD", NULL
6836 const char *rev_parse_argv[] = {
6837 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6838 "--show-cdup", "--show-prefix", NULL
6841 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6842 chomp_string(opt_head);
6843 if (!prefixcmp(opt_head, "refs/heads/")) {
6844 char *offset = opt_head + STRING_SIZE("refs/heads/");
6846 memmove(opt_head, offset, strlen(offset) + 1);
6850 return run_io_load(rev_parse_argv, "=", read_repo_info);
6855 * Main
6858 static const char usage[] =
6859 "tig " TIG_VERSION " (" __DATE__ ")\n"
6860 "\n"
6861 "Usage: tig [options] [revs] [--] [paths]\n"
6862 " or: tig show [options] [revs] [--] [paths]\n"
6863 " or: tig blame [rev] path\n"
6864 " or: tig status\n"
6865 " or: tig < [git command output]\n"
6866 "\n"
6867 "Options:\n"
6868 " -v, --version Show version and exit\n"
6869 " -h, --help Show help message and exit";
6871 static void __NORETURN
6872 quit(int sig)
6874 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6875 if (cursed)
6876 endwin();
6877 exit(0);
6880 static void __NORETURN
6881 die(const char *err, ...)
6883 va_list args;
6885 endwin();
6887 va_start(args, err);
6888 fputs("tig: ", stderr);
6889 vfprintf(stderr, err, args);
6890 fputs("\n", stderr);
6891 va_end(args);
6893 exit(1);
6896 static void
6897 warn(const char *msg, ...)
6899 va_list args;
6901 va_start(args, msg);
6902 fputs("tig warning: ", stderr);
6903 vfprintf(stderr, msg, args);
6904 fputs("\n", stderr);
6905 va_end(args);
6908 static enum request
6909 parse_options(int argc, const char *argv[])
6911 enum request request = REQ_VIEW_MAIN;
6912 const char *subcommand;
6913 bool seen_dashdash = FALSE;
6914 /* XXX: This is vulnerable to the user overriding options
6915 * required for the main view parser. */
6916 const char *custom_argv[SIZEOF_ARG] = {
6917 "git", "log", "--no-color", "--pretty=raw", "--parents",
6918 "--topo-order", NULL
6920 int i, j = 6;
6922 if (!isatty(STDIN_FILENO)) {
6923 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6924 return REQ_VIEW_PAGER;
6927 if (argc <= 1)
6928 return REQ_NONE;
6930 subcommand = argv[1];
6931 if (!strcmp(subcommand, "status")) {
6932 if (argc > 2)
6933 warn("ignoring arguments after `%s'", subcommand);
6934 return REQ_VIEW_STATUS;
6936 } else if (!strcmp(subcommand, "blame")) {
6937 if (argc <= 2 || argc > 4)
6938 die("invalid number of options to blame\n\n%s", usage);
6940 i = 2;
6941 if (argc == 4) {
6942 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6943 i++;
6946 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6947 return REQ_VIEW_BLAME;
6949 } else if (!strcmp(subcommand, "show")) {
6950 request = REQ_VIEW_DIFF;
6952 } else {
6953 subcommand = NULL;
6956 if (subcommand) {
6957 custom_argv[1] = subcommand;
6958 j = 2;
6961 for (i = 1 + !!subcommand; i < argc; i++) {
6962 const char *opt = argv[i];
6964 if (seen_dashdash || !strcmp(opt, "--")) {
6965 seen_dashdash = TRUE;
6967 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6968 printf("tig version %s\n", TIG_VERSION);
6969 quit(0);
6971 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6972 printf("%s\n", usage);
6973 quit(0);
6976 custom_argv[j++] = opt;
6977 if (j >= ARRAY_SIZE(custom_argv))
6978 die("command too long");
6981 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6982 die("Failed to format arguments");
6984 return request;
6988 main(int argc, const char *argv[])
6990 enum request request = parse_options(argc, argv);
6991 struct view *view;
6992 size_t i;
6994 signal(SIGINT, quit);
6995 signal(SIGPIPE, SIG_IGN);
6997 if (setlocale(LC_ALL, "")) {
6998 char *codeset = nl_langinfo(CODESET);
7000 string_ncopy(opt_codeset, codeset, strlen(codeset));
7003 if (load_repo_info() == ERR)
7004 die("Failed to load repo info.");
7006 if (load_options() == ERR)
7007 die("Failed to load user config.");
7009 if (load_git_config() == ERR)
7010 die("Failed to load repo config.");
7012 /* Require a git repository unless when running in pager mode. */
7013 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7014 die("Not a git repository");
7016 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7017 opt_utf8 = FALSE;
7019 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7020 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7021 if (opt_iconv == ICONV_NONE)
7022 die("Failed to initialize character set conversion");
7025 if (load_refs() == ERR)
7026 die("Failed to load refs.");
7028 foreach_view (view, i)
7029 argv_from_env(view->ops->argv, view->cmd_env);
7031 init_display();
7033 if (request != REQ_NONE)
7034 open_view(NULL, request, OPEN_PREPARED);
7035 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7037 while (view_driver(display[current_view], request)) {
7038 int key = get_input(0);
7040 view = display[current_view];
7041 request = get_keybinding(view->keymap, key);
7043 /* Some low-level request handling. This keeps access to
7044 * status_win restricted. */
7045 switch (request) {
7046 case REQ_PROMPT:
7048 char *cmd = read_prompt(":");
7050 if (cmd && isdigit(*cmd)) {
7051 int lineno = view->lineno + 1;
7053 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7054 select_view_line(view, lineno - 1);
7055 report("");
7056 } else {
7057 report("Unable to parse '%s' as a line number", cmd);
7060 } else if (cmd) {
7061 struct view *next = VIEW(REQ_VIEW_PAGER);
7062 const char *argv[SIZEOF_ARG] = { "git" };
7063 int argc = 1;
7065 /* When running random commands, initially show the
7066 * command in the title. However, it maybe later be
7067 * overwritten if a commit line is selected. */
7068 string_ncopy(next->ref, cmd, strlen(cmd));
7070 if (!argv_from_string(argv, &argc, cmd)) {
7071 report("Too many arguments");
7072 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7073 report("Failed to format command");
7074 } else {
7075 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7079 request = REQ_NONE;
7080 break;
7082 case REQ_SEARCH:
7083 case REQ_SEARCH_BACK:
7085 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7086 char *search = read_prompt(prompt);
7088 if (search)
7089 string_ncopy(opt_search, search, strlen(search));
7090 else if (*opt_search)
7091 request = request == REQ_SEARCH ?
7092 REQ_FIND_NEXT :
7093 REQ_FIND_PREV;
7094 else
7095 request = REQ_NONE;
7096 break;
7098 default:
7099 break;
7103 quit(0);
7105 return 0;