Remove line_graphic enum
[tig.git] / tig.c
blob1eb482aac4a8fbbcfbcd5e4b7ea80e884299a23a
1 /* Copyright (c) 2006-2010 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 <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 static inline unsigned char utf8_char_length(const char *string, const char *end);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define MAX(x, y) ((x) > (y) ? (x) : (y))
79 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x) (sizeof(x) - 1)
82 #define SIZEOF_STR 1024 /* Default string size. */
83 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG 32 /* Default argument array size. */
87 /* Revision graph */
89 #define REVGRAPH_INIT 'I'
90 #define REVGRAPH_MERGE 'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND '^'
95 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT (-1)
100 #define ICONV_NONE ((iconv_t) -1)
101 #ifndef ICONV_CONST
102 #define ICONV_CONST /* nothing */
103 #endif
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT "%Y-%m-%d %H:%M"
107 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define ID_COLS 8
111 #define AUTHOR_COLS 19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB '\t'
121 #define KEY_RETURN '\r'
122 #define KEY_ESC 27
125 struct ref {
126 char id[SIZEOF_REV]; /* Commit SHA1 ID */
127 unsigned int head:1; /* Is it the current HEAD? */
128 unsigned int tag:1; /* Is it a tag? */
129 unsigned int ltag:1; /* If so, is the tag local? */
130 unsigned int remote:1; /* Is it a remote ref? */
131 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132 char name[1]; /* Ref name; tag or head names are shortened. */
135 struct ref_list {
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 size_t size; /* Number of refs. */
138 struct ref **refs; /* References for this ID. */
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166 int hotkey;
167 const char *text;
168 void *data;
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
178 static type * \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 type *tmp = *mem; \
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187 if (tmp) \
188 *mem = tmp; \
191 return tmp; \
195 * String helpers
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201 if (srclen > dstlen - 1)
202 srclen = dstlen - 1;
204 strncpy(dst, src, srclen);
205 dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225 size_t size, pos;
227 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228 if (src[pos] == '\t') {
229 size_t expanded = tabsize - (size % tabsize);
231 if (expanded + size >= dstlen - 1)
232 expanded = dstlen - size - 1;
233 memcpy(dst + size, " ", expanded);
234 size += expanded;
235 } else {
236 dst[size++] = src[pos];
240 dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246 int namelen;
248 while (isspace(*name))
249 name++;
251 namelen = strlen(name) - 1;
252 while (namelen > 0 && isspace(name[namelen]))
253 name[namelen--] = 0;
255 return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261 va_list args;
262 size_t pos = bufpos ? *bufpos : 0;
264 va_start(args, fmt);
265 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266 va_end(args);
268 if (bufpos)
269 *bufpos = pos;
271 return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283 size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i = 0; i < len; i++) {
289 if (toupper(str1[i]) == toupper(str2[i]))
290 continue;
292 if (string_enum_sep(str1[i]) &&
293 string_enum_sep(str2[i]))
294 continue;
296 return str1[i] - str2[i];
299 return 0;
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306 const char *name;
307 int namelen;
308 int value;
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316 static char buf[SIZEOF_STR];
317 int bufpos;
319 for (bufpos = 0; bufpos <= namelen; bufpos++) {
320 buf[bufpos] = tolower(name[bufpos]);
321 if (buf[bufpos] == '_')
322 buf[bufpos] = '-';
325 buf[bufpos] = 0;
326 return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334 size_t namelen = strlen(name);
335 int i;
337 for (i = 0; i < map_size; i++)
338 if (enum_equals(map[i], name, namelen)) {
339 *value = map[i].value;
340 return TRUE;
343 return FALSE;
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355 size_t len = slen >= 0 ? slen : strlen(str);
356 size_t suffixlen = strlen(suffix);
358 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 #define DATE_INFO \
363 DATE_(NO), \
364 DATE_(DEFAULT), \
365 DATE_(RELATIVE), \
366 DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370 DATE_INFO
371 #undef DATE_
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376 DATE_INFO
377 #undef DATE_
380 struct time {
381 time_t sec;
382 int tz;
385 static inline int timecmp(const struct time *t1, const struct time *t2)
387 return t1->sec - t2->sec;
390 static const char *
391 string_date(const struct time *time, enum date date)
393 static char buf[DATE_COLS + 1];
394 static const struct enum_map reldate[] = {
395 { "second", 1, 60 * 2 },
396 { "minute", 60, 60 * 60 * 2 },
397 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
398 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
399 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
400 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
402 struct tm tm;
404 if (date == DATE_RELATIVE) {
405 struct timeval now;
406 time_t date = time->sec + time->tz;
407 time_t seconds;
408 int i;
410 gettimeofday(&now, NULL);
411 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
412 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
413 if (seconds >= reldate[i].value)
414 continue;
416 seconds /= reldate[i].namelen;
417 if (!string_format(buf, "%ld %s%s %s",
418 seconds, reldate[i].name,
419 seconds > 1 ? "s" : "",
420 now.tv_sec >= date ? "ago" : "ahead"))
421 break;
422 return buf;
426 gmtime_r(&time->sec, &tm);
427 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
431 #define AUTHOR_VALUES \
432 AUTHOR_(NO), \
433 AUTHOR_(FULL), \
434 AUTHOR_(ABBREVIATED)
436 enum author {
437 #define AUTHOR_(name) AUTHOR_##name
438 AUTHOR_VALUES,
439 #undef AUTHOR_
440 AUTHOR_DEFAULT = AUTHOR_FULL
443 static const struct enum_map author_map[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
445 AUTHOR_VALUES
446 #undef AUTHOR_
449 static const char *
450 get_author_initials(const char *author)
452 static char initials[AUTHOR_COLS * 6 + 1];
453 size_t pos = 0;
454 const char *end = strchr(author, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458 memset(initials, 0, sizeof(initials));
459 while (author < end) {
460 unsigned char bytes;
461 size_t i;
463 while (is_initial_sep(*author))
464 author++;
466 bytes = utf8_char_length(author, end);
467 if (bytes < sizeof(initials) - 1 - pos) {
468 while (bytes--) {
469 initials[pos++] = *author++;
473 for (i = pos; author < end && !is_initial_sep(*author); author++) {
474 if (i < sizeof(initials) - 1)
475 initials[i++] = *author;
478 initials[i++] = 0;
481 return initials;
485 static bool
486 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
488 int valuelen;
490 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
491 bool advance = cmd[valuelen] != 0;
493 cmd[valuelen] = 0;
494 argv[(*argc)++] = chomp_string(cmd);
495 cmd = chomp_string(cmd + valuelen + advance);
498 if (*argc < SIZEOF_ARG)
499 argv[*argc] = NULL;
500 return *argc < SIZEOF_ARG;
503 static void
504 argv_from_env(const char **argv, const char *name)
506 char *env = argv ? getenv(name) : NULL;
507 int argc = 0;
509 if (env && *env)
510 env = strdup(env);
511 if (env && !argv_from_string(argv, &argc, env))
512 die("Too many arguments in the `%s` environment variable", name);
517 * Executing external commands.
520 enum io_type {
521 IO_FD, /* File descriptor based IO. */
522 IO_BG, /* Execute command in the background. */
523 IO_FG, /* Execute command with same std{in,out,err}. */
524 IO_RD, /* Read only fork+exec IO. */
525 IO_WR, /* Write only fork+exec IO. */
526 IO_AP, /* Append fork+exec output to file. */
529 struct io {
530 enum io_type type; /* The requested type of pipe. */
531 const char *dir; /* Directory from which to execute. */
532 pid_t pid; /* Pipe for reading or writing. */
533 int pipe; /* Pipe end for reading or writing. */
534 int error; /* Error status. */
535 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
536 char *buf; /* Read buffer. */
537 size_t bufalloc; /* Allocated buffer size. */
538 size_t bufsize; /* Buffer content size. */
539 char *bufpos; /* Current buffer position. */
540 unsigned int eof:1; /* Has end of file been reached. */
543 static void
544 reset_io(struct io *io)
546 io->pipe = -1;
547 io->pid = 0;
548 io->buf = io->bufpos = NULL;
549 io->bufalloc = io->bufsize = 0;
550 io->error = 0;
551 io->eof = 0;
554 static void
555 init_io(struct io *io, const char *dir, enum io_type type)
557 reset_io(io);
558 io->type = type;
559 io->dir = dir;
562 static bool
563 init_io_rd(struct io *io, const char *argv[], const char *dir,
564 enum format_flags flags)
566 init_io(io, dir, IO_RD);
567 return format_argv(io->argv, argv, flags);
570 static bool
571 io_open(struct io *io, const char *fmt, ...)
573 char name[SIZEOF_STR] = "";
574 bool fits;
575 va_list args;
577 init_io(io, NULL, IO_FD);
579 va_start(args, fmt);
580 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
581 va_end(args);
583 if (!fits) {
584 io->error = ENAMETOOLONG;
585 return FALSE;
587 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
588 if (io->pipe == -1)
589 io->error = errno;
590 return io->pipe != -1;
593 static bool
594 kill_io(struct io *io)
596 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
599 static bool
600 done_io(struct io *io)
602 pid_t pid = io->pid;
604 if (io->pipe != -1)
605 close(io->pipe);
606 free(io->buf);
607 reset_io(io);
609 while (pid > 0) {
610 int status;
611 pid_t waiting = waitpid(pid, &status, 0);
613 if (waiting < 0) {
614 if (errno == EINTR)
615 continue;
616 report("waitpid failed (%s)", strerror(errno));
617 return FALSE;
620 return waiting == pid &&
621 !WIFSIGNALED(status) &&
622 WIFEXITED(status) &&
623 !WEXITSTATUS(status);
626 return TRUE;
629 static bool
630 start_io(struct io *io)
632 int pipefds[2] = { -1, -1 };
634 if (io->type == IO_FD)
635 return TRUE;
637 if ((io->type == IO_RD || io->type == IO_WR) &&
638 pipe(pipefds) < 0)
639 return FALSE;
640 else if (io->type == IO_AP)
641 pipefds[1] = io->pipe;
643 if ((io->pid = fork())) {
644 if (pipefds[!(io->type == IO_WR)] != -1)
645 close(pipefds[!(io->type == IO_WR)]);
646 if (io->pid != -1) {
647 io->pipe = pipefds[!!(io->type == IO_WR)];
648 return TRUE;
651 } else {
652 if (io->type != IO_FG) {
653 int devnull = open("/dev/null", O_RDWR);
654 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
655 int writefd = (io->type == IO_RD || io->type == IO_AP)
656 ? pipefds[1] : devnull;
658 dup2(readfd, STDIN_FILENO);
659 dup2(writefd, STDOUT_FILENO);
660 dup2(devnull, STDERR_FILENO);
662 close(devnull);
663 if (pipefds[0] != -1)
664 close(pipefds[0]);
665 if (pipefds[1] != -1)
666 close(pipefds[1]);
669 if (io->dir && *io->dir && chdir(io->dir) == -1)
670 die("Failed to change directory: %s", strerror(errno));
672 execvp(io->argv[0], (char *const*) io->argv);
673 die("Failed to execute program: %s", strerror(errno));
676 if (pipefds[!!(io->type == IO_WR)] != -1)
677 close(pipefds[!!(io->type == IO_WR)]);
678 return FALSE;
681 static bool
682 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
684 init_io(io, dir, type);
685 if (!format_argv(io->argv, argv, FORMAT_NONE))
686 return FALSE;
687 return start_io(io);
690 static int
691 run_io_do(struct io *io)
693 return start_io(io) && done_io(io);
696 static int
697 run_io_bg(const char **argv)
699 struct io io = {};
701 init_io(&io, NULL, IO_BG);
702 if (!format_argv(io.argv, argv, FORMAT_NONE))
703 return FALSE;
704 return run_io_do(&io);
707 static bool
708 run_io_fg(const char **argv, const char *dir)
710 struct io io = {};
712 init_io(&io, dir, IO_FG);
713 if (!format_argv(io.argv, argv, FORMAT_NONE))
714 return FALSE;
715 return run_io_do(&io);
718 static bool
719 run_io_append(const char **argv, enum format_flags flags, int fd)
721 struct io io = {};
723 init_io(&io, NULL, IO_AP);
724 io.pipe = fd;
725 if (format_argv(io.argv, argv, flags))
726 return run_io_do(&io);
727 close(fd);
728 return FALSE;
731 static bool
732 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
734 return init_io_rd(io, argv, dir, flags) && start_io(io);
737 static bool
738 io_eof(struct io *io)
740 return io->eof;
743 static int
744 io_error(struct io *io)
746 return io->error;
749 static char *
750 io_strerror(struct io *io)
752 return strerror(io->error);
755 static bool
756 io_can_read(struct io *io)
758 struct timeval tv = { 0, 500 };
759 fd_set fds;
761 FD_ZERO(&fds);
762 FD_SET(io->pipe, &fds);
764 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
767 static ssize_t
768 io_read(struct io *io, void *buf, size_t bufsize)
770 do {
771 ssize_t readsize = read(io->pipe, buf, bufsize);
773 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
774 continue;
775 else if (readsize == -1)
776 io->error = errno;
777 else if (readsize == 0)
778 io->eof = 1;
779 return readsize;
780 } while (1);
783 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
785 static char *
786 io_get(struct io *io, int c, bool can_read)
788 char *eol;
789 ssize_t readsize;
791 while (TRUE) {
792 if (io->bufsize > 0) {
793 eol = memchr(io->bufpos, c, io->bufsize);
794 if (eol) {
795 char *line = io->bufpos;
797 *eol = 0;
798 io->bufpos = eol + 1;
799 io->bufsize -= io->bufpos - line;
800 return line;
804 if (io_eof(io)) {
805 if (io->bufsize) {
806 io->bufpos[io->bufsize] = 0;
807 io->bufsize = 0;
808 return io->bufpos;
810 return NULL;
813 if (!can_read)
814 return NULL;
816 if (io->bufsize > 0 && io->bufpos > io->buf)
817 memmove(io->buf, io->bufpos, io->bufsize);
819 if (io->bufalloc == io->bufsize) {
820 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
821 return NULL;
822 io->bufalloc += BUFSIZ;
825 io->bufpos = io->buf;
826 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
827 if (io_error(io))
828 return NULL;
829 io->bufsize += readsize;
833 static bool
834 io_write(struct io *io, const void *buf, size_t bufsize)
836 size_t written = 0;
838 while (!io_error(io) && written < bufsize) {
839 ssize_t size;
841 size = write(io->pipe, buf + written, bufsize - written);
842 if (size < 0 && (errno == EAGAIN || errno == EINTR))
843 continue;
844 else if (size == -1)
845 io->error = errno;
846 else
847 written += size;
850 return written == bufsize;
853 static bool
854 io_read_buf(struct io *io, char buf[], size_t bufsize)
856 char *result = io_get(io, '\n', TRUE);
858 if (result) {
859 result = chomp_string(result);
860 string_ncopy_do(buf, bufsize, result, strlen(result));
863 return done_io(io) && result;
866 static bool
867 run_io_buf(const char **argv, char buf[], size_t bufsize)
869 struct io io = {};
871 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
872 && io_read_buf(&io, buf, bufsize);
875 static int
876 io_load(struct io *io, const char *separators,
877 int (*read_property)(char *, size_t, char *, size_t))
879 char *name;
880 int state = OK;
882 if (!start_io(io))
883 return ERR;
885 while (state == OK && (name = io_get(io, '\n', TRUE))) {
886 char *value;
887 size_t namelen;
888 size_t valuelen;
890 name = chomp_string(name);
891 namelen = strcspn(name, separators);
893 if (name[namelen]) {
894 name[namelen] = 0;
895 value = chomp_string(name + namelen + 1);
896 valuelen = strlen(value);
898 } else {
899 value = "";
900 valuelen = 0;
903 state = read_property(name, namelen, value, valuelen);
906 if (state != ERR && io_error(io))
907 state = ERR;
908 done_io(io);
910 return state;
913 static int
914 run_io_load(const char **argv, const char *separators,
915 int (*read_property)(char *, size_t, char *, size_t))
917 struct io io = {};
919 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
920 ? io_load(&io, separators, read_property) : ERR;
925 * User requests
928 #define REQ_INFO \
929 /* XXX: Keep the view request first and in sync with views[]. */ \
930 REQ_GROUP("View switching") \
931 REQ_(VIEW_MAIN, "Show main view"), \
932 REQ_(VIEW_DIFF, "Show diff view"), \
933 REQ_(VIEW_LOG, "Show log view"), \
934 REQ_(VIEW_TREE, "Show tree view"), \
935 REQ_(VIEW_BLOB, "Show blob view"), \
936 REQ_(VIEW_BLAME, "Show blame view"), \
937 REQ_(VIEW_BRANCH, "Show branch view"), \
938 REQ_(VIEW_HELP, "Show help page"), \
939 REQ_(VIEW_PAGER, "Show pager view"), \
940 REQ_(VIEW_STATUS, "Show status view"), \
941 REQ_(VIEW_STAGE, "Show stage view"), \
943 REQ_GROUP("View manipulation") \
944 REQ_(ENTER, "Enter current line and scroll"), \
945 REQ_(NEXT, "Move to next"), \
946 REQ_(PREVIOUS, "Move to previous"), \
947 REQ_(PARENT, "Move to parent"), \
948 REQ_(VIEW_NEXT, "Move focus to next view"), \
949 REQ_(REFRESH, "Reload and refresh"), \
950 REQ_(MAXIMIZE, "Maximize the current view"), \
951 REQ_(VIEW_CLOSE, "Close the current view"), \
952 REQ_(QUIT, "Close all views and quit"), \
954 REQ_GROUP("View specific requests") \
955 REQ_(STATUS_UPDATE, "Update file status"), \
956 REQ_(STATUS_REVERT, "Revert file changes"), \
957 REQ_(STATUS_MERGE, "Merge file using external tool"), \
958 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
960 REQ_GROUP("Cursor navigation") \
961 REQ_(MOVE_UP, "Move cursor one line up"), \
962 REQ_(MOVE_DOWN, "Move cursor one line down"), \
963 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
964 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
965 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
966 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
968 REQ_GROUP("Scrolling") \
969 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
970 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
971 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
972 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
973 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
974 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
976 REQ_GROUP("Searching") \
977 REQ_(SEARCH, "Search the view"), \
978 REQ_(SEARCH_BACK, "Search backwards in the view"), \
979 REQ_(FIND_NEXT, "Find next search match"), \
980 REQ_(FIND_PREV, "Find previous search match"), \
982 REQ_GROUP("Option manipulation") \
983 REQ_(OPTIONS, "Open option menu"), \
984 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
985 REQ_(TOGGLE_DATE, "Toggle date display"), \
986 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
988 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
989 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
990 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
993 REQ_GROUP("Misc") \
994 REQ_(PROMPT, "Bring up the prompt"), \
995 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
996 REQ_(SHOW_VERSION, "Show version information"), \
997 REQ_(STOP_LOADING, "Stop all loading views"), \
998 REQ_(EDIT, "Open in editor"), \
999 REQ_(NONE, "Do nothing")
1002 /* User action requests. */
1003 enum request {
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007 /* Offset all requests to avoid conflicts with ncurses getch values. */
1008 REQ_OFFSET = KEY_MAX + 1,
1009 REQ_INFO
1011 #undef REQ_GROUP
1012 #undef REQ_
1015 struct request_info {
1016 enum request request;
1017 const char *name;
1018 int namelen;
1019 const char *help;
1022 static const struct request_info req_info[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1025 REQ_INFO
1026 #undef REQ_GROUP
1027 #undef REQ_
1030 static enum request
1031 get_request(const char *name)
1033 int namelen = strlen(name);
1034 int i;
1036 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1037 if (enum_equals(req_info[i], name, namelen))
1038 return req_info[i].request;
1040 return REQ_NONE;
1045 * Options
1048 /* Option and state variables. */
1049 static enum date opt_date = DATE_DEFAULT;
1050 static enum author opt_author = AUTHOR_DEFAULT;
1051 static bool opt_line_number = FALSE;
1052 static bool opt_line_graphics = TRUE;
1053 static bool opt_rev_graph = FALSE;
1054 static bool opt_show_refs = TRUE;
1055 static int opt_num_interval = 5;
1056 static double opt_hscroll = 0.50;
1057 static double opt_scale_split_view = 2.0 / 3.0;
1058 static int opt_tab_size = 8;
1059 static int opt_author_cols = AUTHOR_COLS;
1060 static char opt_path[SIZEOF_STR] = "";
1061 static char opt_file[SIZEOF_STR] = "";
1062 static char opt_ref[SIZEOF_REF] = "";
1063 static char opt_head[SIZEOF_REF] = "";
1064 static char opt_head_rev[SIZEOF_REV] = "";
1065 static char opt_remote[SIZEOF_REF] = "";
1066 static char opt_encoding[20] = "UTF-8";
1067 static char opt_codeset[20] = "UTF-8";
1068 static iconv_t opt_iconv_in = ICONV_NONE;
1069 static iconv_t opt_iconv_out = ICONV_NONE;
1070 static char opt_search[SIZEOF_STR] = "";
1071 static char opt_cdup[SIZEOF_STR] = "";
1072 static char opt_prefix[SIZEOF_STR] = "";
1073 static char opt_git_dir[SIZEOF_STR] = "";
1074 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1075 static char opt_editor[SIZEOF_STR] = "";
1076 static FILE *opt_tty = NULL;
1078 #define is_initial_commit() (!*opt_head_rev)
1079 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1080 #define mkdate(time) string_date(time, opt_date)
1084 * Line-oriented content detection.
1087 #define LINE_INFO \
1088 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1102 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1103 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1104 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1105 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1109 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1110 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1111 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1113 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1114 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1117 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1118 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1119 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1122 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1124 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1125 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1133 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1134 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1135 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1137 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1139 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1143 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1144 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1146 enum line_type {
1147 #define LINE(type, line, fg, bg, attr) \
1148 LINE_##type
1149 LINE_INFO,
1150 LINE_NONE
1151 #undef LINE
1154 struct line_info {
1155 const char *name; /* Option name. */
1156 int namelen; /* Size of option name. */
1157 const char *line; /* The start of line to match. */
1158 int linelen; /* Size of string to match. */
1159 int fg, bg, attr; /* Color and text attributes for the lines. */
1162 static struct line_info line_info[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1165 LINE_INFO
1166 #undef LINE
1169 static enum line_type
1170 get_line_type(const char *line)
1172 int linelen = strlen(line);
1173 enum line_type type;
1175 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1176 /* Case insensitive search matches Signed-off-by lines better. */
1177 if (linelen >= line_info[type].linelen &&
1178 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1179 return type;
1181 return LINE_DEFAULT;
1184 static inline int
1185 get_line_attr(enum line_type type)
1187 assert(type < ARRAY_SIZE(line_info));
1188 return COLOR_PAIR(type) | line_info[type].attr;
1191 static struct line_info *
1192 get_line_info(const char *name)
1194 size_t namelen = strlen(name);
1195 enum line_type type;
1197 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1198 if (enum_equals(line_info[type], name, namelen))
1199 return &line_info[type];
1201 return NULL;
1204 static void
1205 init_colors(void)
1207 int default_bg = line_info[LINE_DEFAULT].bg;
1208 int default_fg = line_info[LINE_DEFAULT].fg;
1209 enum line_type type;
1211 start_color();
1213 if (assume_default_colors(default_fg, default_bg) == ERR) {
1214 default_bg = COLOR_BLACK;
1215 default_fg = COLOR_WHITE;
1218 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1219 struct line_info *info = &line_info[type];
1220 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1221 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1223 init_pair(type, fg, bg);
1227 struct line {
1228 enum line_type type;
1230 /* State flags */
1231 unsigned int selected:1;
1232 unsigned int dirty:1;
1233 unsigned int cleareol:1;
1234 unsigned int other:16;
1236 void *data; /* User data */
1241 * Keys
1244 struct keybinding {
1245 int alias;
1246 enum request request;
1249 static const struct keybinding default_keybindings[] = {
1250 /* View switching */
1251 { 'm', REQ_VIEW_MAIN },
1252 { 'd', REQ_VIEW_DIFF },
1253 { 'l', REQ_VIEW_LOG },
1254 { 't', REQ_VIEW_TREE },
1255 { 'f', REQ_VIEW_BLOB },
1256 { 'B', REQ_VIEW_BLAME },
1257 { 'H', REQ_VIEW_BRANCH },
1258 { 'p', REQ_VIEW_PAGER },
1259 { 'h', REQ_VIEW_HELP },
1260 { 'S', REQ_VIEW_STATUS },
1261 { 'c', REQ_VIEW_STAGE },
1263 /* View manipulation */
1264 { 'q', REQ_VIEW_CLOSE },
1265 { KEY_TAB, REQ_VIEW_NEXT },
1266 { KEY_RETURN, REQ_ENTER },
1267 { KEY_UP, REQ_PREVIOUS },
1268 { KEY_DOWN, REQ_NEXT },
1269 { 'R', REQ_REFRESH },
1270 { KEY_F(5), REQ_REFRESH },
1271 { 'O', REQ_MAXIMIZE },
1273 /* Cursor navigation */
1274 { 'k', REQ_MOVE_UP },
1275 { 'j', REQ_MOVE_DOWN },
1276 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1277 { KEY_END, REQ_MOVE_LAST_LINE },
1278 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1279 { ' ', REQ_MOVE_PAGE_DOWN },
1280 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1281 { 'b', REQ_MOVE_PAGE_UP },
1282 { '-', REQ_MOVE_PAGE_UP },
1284 /* Scrolling */
1285 { KEY_LEFT, REQ_SCROLL_LEFT },
1286 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1287 { KEY_IC, REQ_SCROLL_LINE_UP },
1288 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1289 { 'w', REQ_SCROLL_PAGE_UP },
1290 { 's', REQ_SCROLL_PAGE_DOWN },
1292 /* Searching */
1293 { '/', REQ_SEARCH },
1294 { '?', REQ_SEARCH_BACK },
1295 { 'n', REQ_FIND_NEXT },
1296 { 'N', REQ_FIND_PREV },
1298 /* Misc */
1299 { 'Q', REQ_QUIT },
1300 { 'z', REQ_STOP_LOADING },
1301 { 'v', REQ_SHOW_VERSION },
1302 { 'r', REQ_SCREEN_REDRAW },
1303 { 'o', REQ_OPTIONS },
1304 { '.', REQ_TOGGLE_LINENO },
1305 { 'D', REQ_TOGGLE_DATE },
1306 { 'A', REQ_TOGGLE_AUTHOR },
1307 { 'g', REQ_TOGGLE_REV_GRAPH },
1308 { 'F', REQ_TOGGLE_REFS },
1309 { 'I', REQ_TOGGLE_SORT_ORDER },
1310 { 'i', REQ_TOGGLE_SORT_FIELD },
1311 { ':', REQ_PROMPT },
1312 { 'u', REQ_STATUS_UPDATE },
1313 { '!', REQ_STATUS_REVERT },
1314 { 'M', REQ_STATUS_MERGE },
1315 { '@', REQ_STAGE_NEXT },
1316 { ',', REQ_PARENT },
1317 { 'e', REQ_EDIT },
1320 #define KEYMAP_INFO \
1321 KEYMAP_(GENERIC), \
1322 KEYMAP_(MAIN), \
1323 KEYMAP_(DIFF), \
1324 KEYMAP_(LOG), \
1325 KEYMAP_(TREE), \
1326 KEYMAP_(BLOB), \
1327 KEYMAP_(BLAME), \
1328 KEYMAP_(BRANCH), \
1329 KEYMAP_(PAGER), \
1330 KEYMAP_(HELP), \
1331 KEYMAP_(STATUS), \
1332 KEYMAP_(STAGE)
1334 enum keymap {
1335 #define KEYMAP_(name) KEYMAP_##name
1336 KEYMAP_INFO
1337 #undef KEYMAP_
1340 static const struct enum_map keymap_table[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1342 KEYMAP_INFO
1343 #undef KEYMAP_
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table {
1349 struct keybinding *data;
1350 size_t size;
1353 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1355 static void
1356 add_keybinding(enum keymap keymap, enum request request, int key)
1358 struct keybinding_table *table = &keybindings[keymap];
1360 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1361 if (!table->data)
1362 die("Failed to allocate keybinding");
1363 table->data[table->size].alias = key;
1364 table->data[table->size++].request = request;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368 * lastly in the default keybindings. */
1369 static enum request
1370 get_keybinding(enum keymap keymap, int key)
1372 size_t i;
1374 for (i = 0; i < keybindings[keymap].size; i++)
1375 if (keybindings[keymap].data[i].alias == key)
1376 return keybindings[keymap].data[i].request;
1378 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1379 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1380 return keybindings[KEYMAP_GENERIC].data[i].request;
1382 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1383 if (default_keybindings[i].alias == key)
1384 return default_keybindings[i].request;
1386 return (enum request) key;
1390 struct key {
1391 const char *name;
1392 int value;
1395 static const struct key key_table[] = {
1396 { "Enter", KEY_RETURN },
1397 { "Space", ' ' },
1398 { "Backspace", KEY_BACKSPACE },
1399 { "Tab", KEY_TAB },
1400 { "Escape", KEY_ESC },
1401 { "Left", KEY_LEFT },
1402 { "Right", KEY_RIGHT },
1403 { "Up", KEY_UP },
1404 { "Down", KEY_DOWN },
1405 { "Insert", KEY_IC },
1406 { "Delete", KEY_DC },
1407 { "Hash", '#' },
1408 { "Home", KEY_HOME },
1409 { "End", KEY_END },
1410 { "PageUp", KEY_PPAGE },
1411 { "PageDown", KEY_NPAGE },
1412 { "F1", KEY_F(1) },
1413 { "F2", KEY_F(2) },
1414 { "F3", KEY_F(3) },
1415 { "F4", KEY_F(4) },
1416 { "F5", KEY_F(5) },
1417 { "F6", KEY_F(6) },
1418 { "F7", KEY_F(7) },
1419 { "F8", KEY_F(8) },
1420 { "F9", KEY_F(9) },
1421 { "F10", KEY_F(10) },
1422 { "F11", KEY_F(11) },
1423 { "F12", KEY_F(12) },
1426 static int
1427 get_key_value(const char *name)
1429 int i;
1431 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1432 if (!strcasecmp(key_table[i].name, name))
1433 return key_table[i].value;
1435 if (strlen(name) == 1 && isprint(*name))
1436 return (int) *name;
1438 return ERR;
1441 static const char *
1442 get_key_name(int key_value)
1444 static char key_char[] = "'X'";
1445 const char *seq = NULL;
1446 int key;
1448 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1449 if (key_table[key].value == key_value)
1450 seq = key_table[key].name;
1452 if (seq == NULL &&
1453 key_value < 127 &&
1454 isprint(key_value)) {
1455 key_char[1] = (char) key_value;
1456 seq = key_char;
1459 return seq ? seq : "(no key)";
1462 static bool
1463 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1465 const char *sep = *pos > 0 ? ", " : "";
1466 const char *keyname = get_key_name(keybinding->alias);
1468 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1471 static bool
1472 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1473 enum keymap keymap, bool all)
1475 int i;
1477 for (i = 0; i < keybindings[keymap].size; i++) {
1478 if (keybindings[keymap].data[i].request == request) {
1479 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1480 return FALSE;
1481 if (!all)
1482 break;
1486 return TRUE;
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1491 static const char *
1492 get_keys(enum keymap keymap, enum request request, bool all)
1494 static char buf[BUFSIZ];
1495 size_t pos = 0;
1496 int i;
1498 buf[pos] = 0;
1500 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1501 return "Too many keybindings!";
1502 if (pos > 0 && !all)
1503 return buf;
1505 if (keymap != KEYMAP_GENERIC) {
1506 /* Only the generic keymap includes the default keybindings when
1507 * listing all keys. */
1508 if (all)
1509 return buf;
1511 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1512 return "Too many keybindings!";
1513 if (pos)
1514 return buf;
1517 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1518 if (default_keybindings[i].request == request) {
1519 if (!append_key(buf, &pos, &default_keybindings[i]))
1520 return "Too many keybindings!";
1521 if (!all)
1522 return buf;
1526 return buf;
1529 struct run_request {
1530 enum keymap keymap;
1531 int key;
1532 const char *argv[SIZEOF_ARG];
1535 static struct run_request *run_request;
1536 static size_t run_requests;
1538 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1540 static enum request
1541 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1543 struct run_request *req;
1545 if (argc >= ARRAY_SIZE(req->argv) - 1)
1546 return REQ_NONE;
1548 if (!realloc_run_requests(&run_request, run_requests, 1))
1549 return REQ_NONE;
1551 req = &run_request[run_requests];
1552 req->keymap = keymap;
1553 req->key = key;
1554 req->argv[0] = NULL;
1556 if (!format_argv(req->argv, argv, FORMAT_NONE))
1557 return REQ_NONE;
1559 return REQ_NONE + ++run_requests;
1562 static struct run_request *
1563 get_run_request(enum request request)
1565 if (request <= REQ_NONE)
1566 return NULL;
1567 return &run_request[request - REQ_NONE - 1];
1570 static void
1571 add_builtin_run_requests(void)
1573 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1574 const char *commit[] = { "git", "commit", NULL };
1575 const char *gc[] = { "git", "gc", NULL };
1576 struct {
1577 enum keymap keymap;
1578 int key;
1579 int argc;
1580 const char **argv;
1581 } reqs[] = {
1582 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1583 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1584 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1586 int i;
1588 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1589 enum request req;
1591 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1592 if (req != REQ_NONE)
1593 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1598 * User config file handling.
1601 static int config_lineno;
1602 static bool config_errors;
1603 static const char *config_msg;
1605 static const struct enum_map color_map[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1607 COLOR_MAP(DEFAULT),
1608 COLOR_MAP(BLACK),
1609 COLOR_MAP(BLUE),
1610 COLOR_MAP(CYAN),
1611 COLOR_MAP(GREEN),
1612 COLOR_MAP(MAGENTA),
1613 COLOR_MAP(RED),
1614 COLOR_MAP(WHITE),
1615 COLOR_MAP(YELLOW),
1618 static const struct enum_map attr_map[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1620 ATTR_MAP(NORMAL),
1621 ATTR_MAP(BLINK),
1622 ATTR_MAP(BOLD),
1623 ATTR_MAP(DIM),
1624 ATTR_MAP(REVERSE),
1625 ATTR_MAP(STANDOUT),
1626 ATTR_MAP(UNDERLINE),
1629 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt, const char *arg)
1633 *opt = atoi(arg);
1634 if (!strchr(arg, '%'))
1635 return OK;
1637 /* "Shift down" so 100% and 1 does not conflict. */
1638 *opt = (*opt - 1) / 100;
1639 if (*opt >= 1.0) {
1640 *opt = 0.99;
1641 config_msg = "Step value larger than 100%";
1642 return ERR;
1644 if (*opt < 0.0) {
1645 *opt = 1;
1646 config_msg = "Invalid step value";
1647 return ERR;
1649 return OK;
1652 static int
1653 parse_int(int *opt, const char *arg, int min, int max)
1655 int value = atoi(arg);
1657 if (min <= value && value <= max) {
1658 *opt = value;
1659 return OK;
1662 config_msg = "Integer value out of bound";
1663 return ERR;
1666 static bool
1667 set_color(int *color, const char *name)
1669 if (map_enum(color, color_map, name))
1670 return TRUE;
1671 if (!prefixcmp(name, "color"))
1672 return parse_int(color, name + 5, 0, 255) == OK;
1673 return FALSE;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1677 static int
1678 option_color_command(int argc, const char *argv[])
1680 struct line_info *info;
1682 if (argc < 3) {
1683 config_msg = "Wrong number of arguments given to color command";
1684 return ERR;
1687 info = get_line_info(argv[0]);
1688 if (!info) {
1689 static const struct enum_map obsolete[] = {
1690 ENUM_MAP("main-delim", LINE_DELIMITER),
1691 ENUM_MAP("main-date", LINE_DATE),
1692 ENUM_MAP("main-author", LINE_AUTHOR),
1694 int index;
1696 if (!map_enum(&index, obsolete, argv[0])) {
1697 config_msg = "Unknown color name";
1698 return ERR;
1700 info = &line_info[index];
1703 if (!set_color(&info->fg, argv[1]) ||
1704 !set_color(&info->bg, argv[2])) {
1705 config_msg = "Unknown color";
1706 return ERR;
1709 info->attr = 0;
1710 while (argc-- > 3) {
1711 int attr;
1713 if (!set_attribute(&attr, argv[argc])) {
1714 config_msg = "Unknown attribute";
1715 return ERR;
1717 info->attr |= attr;
1720 return OK;
1723 static int parse_bool(bool *opt, const char *arg)
1725 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1726 ? TRUE : FALSE;
1727 return OK;
1730 static int parse_enum_do(unsigned int *opt, const char *arg,
1731 const struct enum_map *map, size_t map_size)
1733 bool is_true;
1735 assert(map_size > 1);
1737 if (map_enum_do(map, map_size, (int *) opt, arg))
1738 return OK;
1740 if (parse_bool(&is_true, arg) != OK)
1741 return ERR;
1743 *opt = is_true ? map[1].value : map[0].value;
1744 return OK;
1747 #define parse_enum(opt, arg, map) \
1748 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1750 static int
1751 parse_string(char *opt, const char *arg, size_t optsize)
1753 int arglen = strlen(arg);
1755 switch (arg[0]) {
1756 case '\"':
1757 case '\'':
1758 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1759 config_msg = "Unmatched quotation";
1760 return ERR;
1762 arg += 1; arglen -= 2;
1763 default:
1764 string_ncopy_do(opt, optsize, arg, arglen);
1765 return OK;
1769 /* Wants: name = value */
1770 static int
1771 option_set_command(int argc, const char *argv[])
1773 if (argc != 3) {
1774 config_msg = "Wrong number of arguments given to set command";
1775 return ERR;
1778 if (strcmp(argv[1], "=")) {
1779 config_msg = "No value assigned";
1780 return ERR;
1783 if (!strcmp(argv[0], "show-author"))
1784 return parse_enum(&opt_author, argv[2], author_map);
1786 if (!strcmp(argv[0], "show-date"))
1787 return parse_enum(&opt_date, argv[2], date_map);
1789 if (!strcmp(argv[0], "show-rev-graph"))
1790 return parse_bool(&opt_rev_graph, argv[2]);
1792 if (!strcmp(argv[0], "show-refs"))
1793 return parse_bool(&opt_show_refs, argv[2]);
1795 if (!strcmp(argv[0], "show-line-numbers"))
1796 return parse_bool(&opt_line_number, argv[2]);
1798 if (!strcmp(argv[0], "line-graphics"))
1799 return parse_bool(&opt_line_graphics, argv[2]);
1801 if (!strcmp(argv[0], "line-number-interval"))
1802 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1804 if (!strcmp(argv[0], "author-width"))
1805 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1807 if (!strcmp(argv[0], "horizontal-scroll"))
1808 return parse_step(&opt_hscroll, argv[2]);
1810 if (!strcmp(argv[0], "split-view-height"))
1811 return parse_step(&opt_scale_split_view, argv[2]);
1813 if (!strcmp(argv[0], "tab-size"))
1814 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1816 if (!strcmp(argv[0], "commit-encoding"))
1817 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1819 config_msg = "Unknown variable name";
1820 return ERR;
1823 /* Wants: mode request key */
1824 static int
1825 option_bind_command(int argc, const char *argv[])
1827 enum request request;
1828 int keymap = -1;
1829 int key;
1831 if (argc < 3) {
1832 config_msg = "Wrong number of arguments given to bind command";
1833 return ERR;
1836 if (set_keymap(&keymap, argv[0]) == ERR) {
1837 config_msg = "Unknown key map";
1838 return ERR;
1841 key = get_key_value(argv[1]);
1842 if (key == ERR) {
1843 config_msg = "Unknown key";
1844 return ERR;
1847 request = get_request(argv[2]);
1848 if (request == REQ_NONE) {
1849 static const struct enum_map obsolete[] = {
1850 ENUM_MAP("cherry-pick", REQ_NONE),
1851 ENUM_MAP("screen-resize", REQ_NONE),
1852 ENUM_MAP("tree-parent", REQ_PARENT),
1854 int alias;
1856 if (map_enum(&alias, obsolete, argv[2])) {
1857 if (alias != REQ_NONE)
1858 add_keybinding(keymap, alias, key);
1859 config_msg = "Obsolete request name";
1860 return ERR;
1863 if (request == REQ_NONE && *argv[2]++ == '!')
1864 request = add_run_request(keymap, key, argc - 2, argv + 2);
1865 if (request == REQ_NONE) {
1866 config_msg = "Unknown request name";
1867 return ERR;
1870 add_keybinding(keymap, request, key);
1872 return OK;
1875 static int
1876 set_option(const char *opt, char *value)
1878 const char *argv[SIZEOF_ARG];
1879 int argc = 0;
1881 if (!argv_from_string(argv, &argc, value)) {
1882 config_msg = "Too many option arguments";
1883 return ERR;
1886 if (!strcmp(opt, "color"))
1887 return option_color_command(argc, argv);
1889 if (!strcmp(opt, "set"))
1890 return option_set_command(argc, argv);
1892 if (!strcmp(opt, "bind"))
1893 return option_bind_command(argc, argv);
1895 config_msg = "Unknown option command";
1896 return ERR;
1899 static int
1900 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1902 int status = OK;
1904 config_lineno++;
1905 config_msg = "Internal error";
1907 /* Check for comment markers, since read_properties() will
1908 * only ensure opt and value are split at first " \t". */
1909 optlen = strcspn(opt, "#");
1910 if (optlen == 0)
1911 return OK;
1913 if (opt[optlen] != 0) {
1914 config_msg = "No option value";
1915 status = ERR;
1917 } else {
1918 /* Look for comment endings in the value. */
1919 size_t len = strcspn(value, "#");
1921 if (len < valuelen) {
1922 valuelen = len;
1923 value[valuelen] = 0;
1926 status = set_option(opt, value);
1929 if (status == ERR) {
1930 warn("Error on line %d, near '%.*s': %s",
1931 config_lineno, (int) optlen, opt, config_msg);
1932 config_errors = TRUE;
1935 /* Always keep going if errors are encountered. */
1936 return OK;
1939 static void
1940 load_option_file(const char *path)
1942 struct io io = {};
1944 /* It's OK that the file doesn't exist. */
1945 if (!io_open(&io, "%s", path))
1946 return;
1948 config_lineno = 0;
1949 config_errors = FALSE;
1951 if (io_load(&io, " \t", read_option) == ERR ||
1952 config_errors == TRUE)
1953 warn("Errors while loading %s.", path);
1956 static int
1957 load_options(void)
1959 const char *home = getenv("HOME");
1960 const char *tigrc_user = getenv("TIGRC_USER");
1961 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1962 char buf[SIZEOF_STR];
1964 add_builtin_run_requests();
1966 if (!tigrc_system)
1967 tigrc_system = SYSCONFDIR "/tigrc";
1968 load_option_file(tigrc_system);
1970 if (!tigrc_user) {
1971 if (!home || !string_format(buf, "%s/.tigrc", home))
1972 return ERR;
1973 tigrc_user = buf;
1975 load_option_file(tigrc_user);
1977 return OK;
1982 * The viewer
1985 struct view;
1986 struct view_ops;
1988 /* The display array of active views and the index of the current view. */
1989 static struct view *display[2];
1990 static unsigned int current_view;
1992 #define foreach_displayed_view(view, i) \
1993 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views() (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob[SIZEOF_REF] = "";
1999 static char ref_commit[SIZEOF_REF] = "HEAD";
2000 static char ref_head[SIZEOF_REF] = "HEAD";
2002 struct view {
2003 const char *name; /* View name */
2004 const char *cmd_env; /* Command line set via environment */
2005 const char *id; /* Points to either of ref_{head,commit,blob} */
2007 struct view_ops *ops; /* View operations */
2009 enum keymap keymap; /* What keymap does this view have */
2010 bool git_dir; /* Whether the view requires a git directory. */
2012 char ref[SIZEOF_REF]; /* Hovered commit reference */
2013 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2015 int height, width; /* The width and height of the main window */
2016 WINDOW *win; /* The main window */
2017 WINDOW *title; /* The title window living below the main window */
2019 /* Navigation */
2020 unsigned long offset; /* Offset of the window top */
2021 unsigned long yoffset; /* Offset from the window side. */
2022 unsigned long lineno; /* Current line number */
2023 unsigned long p_offset; /* Previous offset of the window top */
2024 unsigned long p_yoffset;/* Previous offset from the window side */
2025 unsigned long p_lineno; /* Previous current line number */
2026 bool p_restore; /* Should the previous position be restored. */
2028 /* Searching */
2029 char grep[SIZEOF_STR]; /* Search string */
2030 regex_t *regex; /* Pre-compiled regexp */
2032 /* If non-NULL, points to the view that opened this view. If this view
2033 * is closed tig will switch back to the parent view. */
2034 struct view *parent;
2036 /* Buffering */
2037 size_t lines; /* Total number of lines */
2038 struct line *line; /* Line index */
2039 unsigned int digits; /* Number of digits in the lines member. */
2041 /* Drawing */
2042 struct line *curline; /* Line currently being drawn. */
2043 enum line_type curtype; /* Attribute currently used for drawing. */
2044 unsigned long col; /* Column when drawing. */
2045 bool has_scrolled; /* View was scrolled. */
2047 /* Loading */
2048 struct io io;
2049 struct io *pipe;
2050 time_t start_time;
2051 time_t update_secs;
2054 struct view_ops {
2055 /* What type of content being displayed. Used in the title bar. */
2056 const char *type;
2057 /* Default command arguments. */
2058 const char **argv;
2059 /* Open and reads in all view content. */
2060 bool (*open)(struct view *view);
2061 /* Read one line; updates view->line. */
2062 bool (*read)(struct view *view, char *data);
2063 /* Draw one line; @lineno must be < view->height. */
2064 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2065 /* Depending on view handle a special requests. */
2066 enum request (*request)(struct view *view, enum request request, struct line *line);
2067 /* Search for regexp in a line. */
2068 bool (*grep)(struct view *view, struct line *line);
2069 /* Select line */
2070 void (*select)(struct view *view, struct line *line);
2071 /* Prepare view for loading */
2072 bool (*prepare)(struct view *view);
2075 static struct view_ops blame_ops;
2076 static struct view_ops blob_ops;
2077 static struct view_ops diff_ops;
2078 static struct view_ops help_ops;
2079 static struct view_ops log_ops;
2080 static struct view_ops main_ops;
2081 static struct view_ops pager_ops;
2082 static struct view_ops stage_ops;
2083 static struct view_ops status_ops;
2084 static struct view_ops tree_ops;
2085 static struct view_ops branch_ops;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088 { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views[] = {
2095 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2096 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2097 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2098 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2099 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2100 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2101 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2102 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2103 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2104 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2105 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2108 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115 (view == display[0] || view == display[1])
2118 static inline void
2119 set_view_attr(struct view *view, enum line_type type)
2121 if (!view->curline->selected && view->curtype != type) {
2122 (void) wattrset(view->win, get_line_attr(type));
2123 wchgat(view->win, -1, 0, type, NULL);
2124 view->curtype = type;
2128 static int
2129 draw_chars(struct view *view, enum line_type type, const char *string,
2130 int max_len, bool use_tilde)
2132 static char out_buffer[BUFSIZ * 2];
2133 int len = 0;
2134 int col = 0;
2135 int trimmed = FALSE;
2136 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2138 if (max_len <= 0)
2139 return 0;
2141 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2143 set_view_attr(view, type);
2144 if (len > 0) {
2145 if (opt_iconv_out != ICONV_NONE) {
2146 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2147 size_t inlen = len + 1;
2149 char *outbuf = out_buffer;
2150 size_t outlen = sizeof(out_buffer);
2152 size_t ret;
2154 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2155 if (ret != (size_t) -1) {
2156 string = out_buffer;
2157 len = sizeof(out_buffer) - outlen;
2161 waddnstr(view->win, string, len);
2163 if (trimmed && use_tilde) {
2164 set_view_attr(view, LINE_DELIMITER);
2165 waddch(view->win, '~');
2166 col++;
2169 return col;
2172 static int
2173 draw_space(struct view *view, enum line_type type, int max, int spaces)
2175 static char space[] = " ";
2176 int col = 0;
2178 spaces = MIN(max, spaces);
2180 while (spaces > 0) {
2181 int len = MIN(spaces, sizeof(space) - 1);
2183 col += draw_chars(view, type, space, len, FALSE);
2184 spaces -= len;
2187 return col;
2190 static bool
2191 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2193 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2194 return view->width + view->yoffset <= view->col;
2197 static bool
2198 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2200 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2201 int max = view->width + view->yoffset - view->col;
2202 int i;
2204 if (max < size)
2205 size = max;
2207 set_view_attr(view, type);
2208 /* Using waddch() instead of waddnstr() ensures that
2209 * they'll be rendered correctly for the cursor line. */
2210 for (i = skip; i < size; i++)
2211 waddch(view->win, graphic[i]);
2213 view->col += size;
2214 if (size < max && skip <= size)
2215 waddch(view->win, ' ');
2216 view->col++;
2218 return view->width + view->yoffset <= view->col;
2221 static bool
2222 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2224 int max = MIN(view->width + view->yoffset - view->col, len);
2225 int col;
2227 if (text)
2228 col = draw_chars(view, type, text, max - 1, trim);
2229 else
2230 col = draw_space(view, type, max - 1, max - 1);
2232 view->col += col;
2233 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2234 return view->width + view->yoffset <= view->col;
2237 static bool
2238 draw_date(struct view *view, struct time *time)
2240 const char *date = time && time->sec ? mkdate(time) : "";
2241 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2243 return draw_field(view, LINE_DATE, date, cols, FALSE);
2246 static bool
2247 draw_author(struct view *view, const char *author)
2249 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2250 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2252 if (abbreviate && author)
2253 author = get_author_initials(author);
2255 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2258 static bool
2259 draw_mode(struct view *view, mode_t mode)
2261 const char *str;
2263 if (S_ISDIR(mode))
2264 str = "drwxr-xr-x";
2265 else if (S_ISLNK(mode))
2266 str = "lrwxrwxrwx";
2267 else if (S_ISGITLINK(mode))
2268 str = "m---------";
2269 else if (S_ISREG(mode) && mode & S_IXUSR)
2270 str = "-rwxr-xr-x";
2271 else if (S_ISREG(mode))
2272 str = "-rw-r--r--";
2273 else
2274 str = "----------";
2276 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2279 static bool
2280 draw_lineno(struct view *view, unsigned int lineno)
2282 char number[10];
2283 int digits3 = view->digits < 3 ? 3 : view->digits;
2284 int max = MIN(view->width + view->yoffset - view->col, digits3);
2285 char *text = NULL;
2286 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2288 lineno += view->offset + 1;
2289 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2290 static char fmt[] = "%1ld";
2292 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2293 if (string_format(number, fmt, lineno))
2294 text = number;
2296 if (text)
2297 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2298 else
2299 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2300 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2303 static bool
2304 draw_view_line(struct view *view, unsigned int lineno)
2306 struct line *line;
2307 bool selected = (view->offset + lineno == view->lineno);
2309 assert(view_is_displayed(view));
2311 if (view->offset + lineno >= view->lines)
2312 return FALSE;
2314 line = &view->line[view->offset + lineno];
2316 wmove(view->win, lineno, 0);
2317 if (line->cleareol)
2318 wclrtoeol(view->win);
2319 view->col = 0;
2320 view->curline = line;
2321 view->curtype = LINE_NONE;
2322 line->selected = FALSE;
2323 line->dirty = line->cleareol = 0;
2325 if (selected) {
2326 set_view_attr(view, LINE_CURSOR);
2327 line->selected = TRUE;
2328 view->ops->select(view, line);
2331 return view->ops->draw(view, line, lineno);
2334 static void
2335 redraw_view_dirty(struct view *view)
2337 bool dirty = FALSE;
2338 int lineno;
2340 for (lineno = 0; lineno < view->height; lineno++) {
2341 if (view->offset + lineno >= view->lines)
2342 break;
2343 if (!view->line[view->offset + lineno].dirty)
2344 continue;
2345 dirty = TRUE;
2346 if (!draw_view_line(view, lineno))
2347 break;
2350 if (!dirty)
2351 return;
2352 wnoutrefresh(view->win);
2355 static void
2356 redraw_view_from(struct view *view, int lineno)
2358 assert(0 <= lineno && lineno < view->height);
2360 for (; lineno < view->height; lineno++) {
2361 if (!draw_view_line(view, lineno))
2362 break;
2365 wnoutrefresh(view->win);
2368 static void
2369 redraw_view(struct view *view)
2371 werase(view->win);
2372 redraw_view_from(view, 0);
2376 static void
2377 update_view_title(struct view *view)
2379 char buf[SIZEOF_STR];
2380 char state[SIZEOF_STR];
2381 size_t bufpos = 0, statelen = 0;
2383 assert(view_is_displayed(view));
2385 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2386 unsigned int view_lines = view->offset + view->height;
2387 unsigned int lines = view->lines
2388 ? MIN(view_lines, view->lines) * 100 / view->lines
2389 : 0;
2391 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2392 view->ops->type,
2393 view->lineno + 1,
2394 view->lines,
2395 lines);
2399 if (view->pipe) {
2400 time_t secs = time(NULL) - view->start_time;
2402 /* Three git seconds are a long time ... */
2403 if (secs > 2)
2404 string_format_from(state, &statelen, " loading %lds", secs);
2407 string_format_from(buf, &bufpos, "[%s]", view->name);
2408 if (*view->ref && bufpos < view->width) {
2409 size_t refsize = strlen(view->ref);
2410 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2412 if (minsize < view->width)
2413 refsize = view->width - minsize + 7;
2414 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2417 if (statelen && bufpos < view->width) {
2418 string_format_from(buf, &bufpos, "%s", state);
2421 if (view == display[current_view])
2422 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2423 else
2424 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2426 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2427 wclrtoeol(view->title);
2428 wnoutrefresh(view->title);
2431 static int
2432 apply_step(double step, int value)
2434 if (step >= 1)
2435 return (int) step;
2436 value *= step + 0.01;
2437 return value ? value : 1;
2440 static void
2441 resize_display(void)
2443 int offset, i;
2444 struct view *base = display[0];
2445 struct view *view = display[1] ? display[1] : display[0];
2447 /* Setup window dimensions */
2449 getmaxyx(stdscr, base->height, base->width);
2451 /* Make room for the status window. */
2452 base->height -= 1;
2454 if (view != base) {
2455 /* Horizontal split. */
2456 view->width = base->width;
2457 view->height = apply_step(opt_scale_split_view, base->height);
2458 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2459 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2460 base->height -= view->height;
2462 /* Make room for the title bar. */
2463 view->height -= 1;
2466 /* Make room for the title bar. */
2467 base->height -= 1;
2469 offset = 0;
2471 foreach_displayed_view (view, i) {
2472 if (!view->win) {
2473 view->win = newwin(view->height, 0, offset, 0);
2474 if (!view->win)
2475 die("Failed to create %s view", view->name);
2477 scrollok(view->win, FALSE);
2479 view->title = newwin(1, 0, offset + view->height, 0);
2480 if (!view->title)
2481 die("Failed to create title window");
2483 } else {
2484 wresize(view->win, view->height, view->width);
2485 mvwin(view->win, offset, 0);
2486 mvwin(view->title, offset + view->height, 0);
2489 offset += view->height + 1;
2493 static void
2494 redraw_display(bool clear)
2496 struct view *view;
2497 int i;
2499 foreach_displayed_view (view, i) {
2500 if (clear)
2501 wclear(view->win);
2502 redraw_view(view);
2503 update_view_title(view);
2507 static void
2508 toggle_enum_option_do(unsigned int *opt, const char *help,
2509 const struct enum_map *map, size_t size)
2511 *opt = (*opt + 1) % size;
2512 redraw_display(FALSE);
2513 report("Displaying %s %s", enum_name(map[*opt]), help);
2516 #define toggle_enum_option(opt, help, map) \
2517 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2519 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2520 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2522 static void
2523 toggle_view_option(bool *option, const char *help)
2525 *option = !*option;
2526 redraw_display(FALSE);
2527 report("%sabling %s", *option ? "En" : "Dis", help);
2530 static void
2531 open_option_menu(void)
2533 const struct menu_item menu[] = {
2534 { '.', "line numbers", &opt_line_number },
2535 { 'D', "date display", &opt_date },
2536 { 'A', "author display", &opt_author },
2537 { 'g', "revision graph display", &opt_rev_graph },
2538 { 'F', "reference display", &opt_show_refs },
2539 { 0 }
2541 int selected = 0;
2543 if (prompt_menu("Toggle option", menu, &selected)) {
2544 if (menu[selected].data == &opt_date)
2545 toggle_date();
2546 else if (menu[selected].data == &opt_author)
2547 toggle_author();
2548 else
2549 toggle_view_option(menu[selected].data, menu[selected].text);
2553 static void
2554 maximize_view(struct view *view)
2556 memset(display, 0, sizeof(display));
2557 current_view = 0;
2558 display[current_view] = view;
2559 resize_display();
2560 redraw_display(FALSE);
2561 report("");
2566 * Navigation
2569 static bool
2570 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2572 if (lineno >= view->lines)
2573 lineno = view->lines > 0 ? view->lines - 1 : 0;
2575 if (offset > lineno || offset + view->height <= lineno) {
2576 unsigned long half = view->height / 2;
2578 if (lineno > half)
2579 offset = lineno - half;
2580 else
2581 offset = 0;
2584 if (offset != view->offset || lineno != view->lineno) {
2585 view->offset = offset;
2586 view->lineno = lineno;
2587 return TRUE;
2590 return FALSE;
2593 /* Scrolling backend */
2594 static void
2595 do_scroll_view(struct view *view, int lines)
2597 bool redraw_current_line = FALSE;
2599 /* The rendering expects the new offset. */
2600 view->offset += lines;
2602 assert(0 <= view->offset && view->offset < view->lines);
2603 assert(lines);
2605 /* Move current line into the view. */
2606 if (view->lineno < view->offset) {
2607 view->lineno = view->offset;
2608 redraw_current_line = TRUE;
2609 } else if (view->lineno >= view->offset + view->height) {
2610 view->lineno = view->offset + view->height - 1;
2611 redraw_current_line = TRUE;
2614 assert(view->offset <= view->lineno && view->lineno < view->lines);
2616 /* Redraw the whole screen if scrolling is pointless. */
2617 if (view->height < ABS(lines)) {
2618 redraw_view(view);
2620 } else {
2621 int line = lines > 0 ? view->height - lines : 0;
2622 int end = line + ABS(lines);
2624 scrollok(view->win, TRUE);
2625 wscrl(view->win, lines);
2626 scrollok(view->win, FALSE);
2628 while (line < end && draw_view_line(view, line))
2629 line++;
2631 if (redraw_current_line)
2632 draw_view_line(view, view->lineno - view->offset);
2633 wnoutrefresh(view->win);
2636 view->has_scrolled = TRUE;
2637 report("");
2640 /* Scroll frontend */
2641 static void
2642 scroll_view(struct view *view, enum request request)
2644 int lines = 1;
2646 assert(view_is_displayed(view));
2648 switch (request) {
2649 case REQ_SCROLL_LEFT:
2650 if (view->yoffset == 0) {
2651 report("Cannot scroll beyond the first column");
2652 return;
2654 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2655 view->yoffset = 0;
2656 else
2657 view->yoffset -= apply_step(opt_hscroll, view->width);
2658 redraw_view_from(view, 0);
2659 report("");
2660 return;
2661 case REQ_SCROLL_RIGHT:
2662 view->yoffset += apply_step(opt_hscroll, view->width);
2663 redraw_view(view);
2664 report("");
2665 return;
2666 case REQ_SCROLL_PAGE_DOWN:
2667 lines = view->height;
2668 case REQ_SCROLL_LINE_DOWN:
2669 if (view->offset + lines > view->lines)
2670 lines = view->lines - view->offset;
2672 if (lines == 0 || view->offset + view->height >= view->lines) {
2673 report("Cannot scroll beyond the last line");
2674 return;
2676 break;
2678 case REQ_SCROLL_PAGE_UP:
2679 lines = view->height;
2680 case REQ_SCROLL_LINE_UP:
2681 if (lines > view->offset)
2682 lines = view->offset;
2684 if (lines == 0) {
2685 report("Cannot scroll beyond the first line");
2686 return;
2689 lines = -lines;
2690 break;
2692 default:
2693 die("request %d not handled in switch", request);
2696 do_scroll_view(view, lines);
2699 /* Cursor moving */
2700 static void
2701 move_view(struct view *view, enum request request)
2703 int scroll_steps = 0;
2704 int steps;
2706 switch (request) {
2707 case REQ_MOVE_FIRST_LINE:
2708 steps = -view->lineno;
2709 break;
2711 case REQ_MOVE_LAST_LINE:
2712 steps = view->lines - view->lineno - 1;
2713 break;
2715 case REQ_MOVE_PAGE_UP:
2716 steps = view->height > view->lineno
2717 ? -view->lineno : -view->height;
2718 break;
2720 case REQ_MOVE_PAGE_DOWN:
2721 steps = view->lineno + view->height >= view->lines
2722 ? view->lines - view->lineno - 1 : view->height;
2723 break;
2725 case REQ_MOVE_UP:
2726 steps = -1;
2727 break;
2729 case REQ_MOVE_DOWN:
2730 steps = 1;
2731 break;
2733 default:
2734 die("request %d not handled in switch", request);
2737 if (steps <= 0 && view->lineno == 0) {
2738 report("Cannot move beyond the first line");
2739 return;
2741 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2742 report("Cannot move beyond the last line");
2743 return;
2746 /* Move the current line */
2747 view->lineno += steps;
2748 assert(0 <= view->lineno && view->lineno < view->lines);
2750 /* Check whether the view needs to be scrolled */
2751 if (view->lineno < view->offset ||
2752 view->lineno >= view->offset + view->height) {
2753 scroll_steps = steps;
2754 if (steps < 0 && -steps > view->offset) {
2755 scroll_steps = -view->offset;
2757 } else if (steps > 0) {
2758 if (view->lineno == view->lines - 1 &&
2759 view->lines > view->height) {
2760 scroll_steps = view->lines - view->offset - 1;
2761 if (scroll_steps >= view->height)
2762 scroll_steps -= view->height - 1;
2767 if (!view_is_displayed(view)) {
2768 view->offset += scroll_steps;
2769 assert(0 <= view->offset && view->offset < view->lines);
2770 view->ops->select(view, &view->line[view->lineno]);
2771 return;
2774 /* Repaint the old "current" line if we be scrolling */
2775 if (ABS(steps) < view->height)
2776 draw_view_line(view, view->lineno - steps - view->offset);
2778 if (scroll_steps) {
2779 do_scroll_view(view, scroll_steps);
2780 return;
2783 /* Draw the current line */
2784 draw_view_line(view, view->lineno - view->offset);
2786 wnoutrefresh(view->win);
2787 report("");
2792 * Searching
2795 static void search_view(struct view *view, enum request request);
2797 static bool
2798 grep_text(struct view *view, const char *text[])
2800 regmatch_t pmatch;
2801 size_t i;
2803 for (i = 0; text[i]; i++)
2804 if (*text[i] &&
2805 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2806 return TRUE;
2807 return FALSE;
2810 static void
2811 select_view_line(struct view *view, unsigned long lineno)
2813 unsigned long old_lineno = view->lineno;
2814 unsigned long old_offset = view->offset;
2816 if (goto_view_line(view, view->offset, lineno)) {
2817 if (view_is_displayed(view)) {
2818 if (old_offset != view->offset) {
2819 redraw_view(view);
2820 } else {
2821 draw_view_line(view, old_lineno - view->offset);
2822 draw_view_line(view, view->lineno - view->offset);
2823 wnoutrefresh(view->win);
2825 } else {
2826 view->ops->select(view, &view->line[view->lineno]);
2831 static void
2832 find_next(struct view *view, enum request request)
2834 unsigned long lineno = view->lineno;
2835 int direction;
2837 if (!*view->grep) {
2838 if (!*opt_search)
2839 report("No previous search");
2840 else
2841 search_view(view, request);
2842 return;
2845 switch (request) {
2846 case REQ_SEARCH:
2847 case REQ_FIND_NEXT:
2848 direction = 1;
2849 break;
2851 case REQ_SEARCH_BACK:
2852 case REQ_FIND_PREV:
2853 direction = -1;
2854 break;
2856 default:
2857 return;
2860 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2861 lineno += direction;
2863 /* Note, lineno is unsigned long so will wrap around in which case it
2864 * will become bigger than view->lines. */
2865 for (; lineno < view->lines; lineno += direction) {
2866 if (view->ops->grep(view, &view->line[lineno])) {
2867 select_view_line(view, lineno);
2868 report("Line %ld matches '%s'", lineno + 1, view->grep);
2869 return;
2873 report("No match found for '%s'", view->grep);
2876 static void
2877 search_view(struct view *view, enum request request)
2879 int regex_err;
2881 if (view->regex) {
2882 regfree(view->regex);
2883 *view->grep = 0;
2884 } else {
2885 view->regex = calloc(1, sizeof(*view->regex));
2886 if (!view->regex)
2887 return;
2890 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2891 if (regex_err != 0) {
2892 char buf[SIZEOF_STR] = "unknown error";
2894 regerror(regex_err, view->regex, buf, sizeof(buf));
2895 report("Search failed: %s", buf);
2896 return;
2899 string_copy(view->grep, opt_search);
2901 find_next(view, request);
2905 * Incremental updating
2908 static void
2909 reset_view(struct view *view)
2911 int i;
2913 for (i = 0; i < view->lines; i++)
2914 free(view->line[i].data);
2915 free(view->line);
2917 view->p_offset = view->offset;
2918 view->p_yoffset = view->yoffset;
2919 view->p_lineno = view->lineno;
2921 view->line = NULL;
2922 view->offset = 0;
2923 view->yoffset = 0;
2924 view->lines = 0;
2925 view->lineno = 0;
2926 view->vid[0] = 0;
2927 view->update_secs = 0;
2930 static void
2931 free_argv(const char *argv[])
2933 int argc;
2935 for (argc = 0; argv[argc]; argc++)
2936 free((void *) argv[argc]);
2939 static const char *
2940 format_arg(const char *name)
2942 static struct {
2943 const char *name;
2944 size_t namelen;
2945 const char *value;
2946 const char *value_if_empty;
2947 } vars[] = {
2948 #define FORMAT_VAR(name, value, value_if_empty) \
2949 { name, STRING_SIZE(name), value, value_if_empty }
2950 FORMAT_VAR("%(directory)", opt_path, ""),
2951 FORMAT_VAR("%(file)", opt_file, ""),
2952 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2953 FORMAT_VAR("%(head)", ref_head, ""),
2954 FORMAT_VAR("%(commit)", ref_commit, ""),
2955 FORMAT_VAR("%(blob)", ref_blob, ""),
2957 int i;
2959 for (i = 0; i < ARRAY_SIZE(vars); i++)
2960 if (!strncmp(name, vars[i].name, vars[i].namelen))
2961 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2963 return NULL;
2965 static bool
2966 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2968 char buf[SIZEOF_STR];
2969 int argc;
2970 bool noreplace = flags == FORMAT_NONE;
2972 free_argv(dst_argv);
2974 for (argc = 0; src_argv[argc]; argc++) {
2975 const char *arg = src_argv[argc];
2976 size_t bufpos = 0;
2978 while (arg) {
2979 char *next = strstr(arg, "%(");
2980 int len = next - arg;
2981 const char *value;
2983 if (!next || noreplace) {
2984 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2985 noreplace = TRUE;
2986 len = strlen(arg);
2987 value = "";
2989 } else {
2990 value = format_arg(next);
2992 if (!value) {
2993 report("Unknown replacement: `%s`", next);
2994 return FALSE;
2998 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2999 return FALSE;
3001 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3004 dst_argv[argc] = strdup(buf);
3005 if (!dst_argv[argc])
3006 break;
3009 dst_argv[argc] = NULL;
3011 return src_argv[argc] == NULL;
3014 static bool
3015 restore_view_position(struct view *view)
3017 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3018 return FALSE;
3020 /* Changing the view position cancels the restoring. */
3021 /* FIXME: Changing back to the first line is not detected. */
3022 if (view->offset != 0 || view->lineno != 0) {
3023 view->p_restore = FALSE;
3024 return FALSE;
3027 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3028 view_is_displayed(view))
3029 werase(view->win);
3031 view->yoffset = view->p_yoffset;
3032 view->p_restore = FALSE;
3034 return TRUE;
3037 static void
3038 end_update(struct view *view, bool force)
3040 if (!view->pipe)
3041 return;
3042 while (!view->ops->read(view, NULL))
3043 if (!force)
3044 return;
3045 set_nonblocking_input(FALSE);
3046 if (force)
3047 kill_io(view->pipe);
3048 done_io(view->pipe);
3049 view->pipe = NULL;
3052 static void
3053 setup_update(struct view *view, const char *vid)
3055 set_nonblocking_input(TRUE);
3056 reset_view(view);
3057 string_copy_rev(view->vid, vid);
3058 view->pipe = &view->io;
3059 view->start_time = time(NULL);
3062 static bool
3063 prepare_update(struct view *view, const char *argv[], const char *dir,
3064 enum format_flags flags)
3066 if (view->pipe)
3067 end_update(view, TRUE);
3068 return init_io_rd(&view->io, argv, dir, flags);
3071 static bool
3072 prepare_update_file(struct view *view, const char *name)
3074 if (view->pipe)
3075 end_update(view, TRUE);
3076 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3079 static bool
3080 begin_update(struct view *view, bool refresh)
3082 if (view->pipe)
3083 end_update(view, TRUE);
3085 if (!refresh) {
3086 if (view->ops->prepare) {
3087 if (!view->ops->prepare(view))
3088 return FALSE;
3089 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3090 return FALSE;
3093 /* Put the current ref_* value to the view title ref
3094 * member. This is needed by the blob view. Most other
3095 * views sets it automatically after loading because the
3096 * first line is a commit line. */
3097 string_copy_rev(view->ref, view->id);
3100 if (!start_io(&view->io))
3101 return FALSE;
3103 setup_update(view, view->id);
3105 return TRUE;
3108 static bool
3109 update_view(struct view *view)
3111 char out_buffer[BUFSIZ * 2];
3112 char *line;
3113 /* Clear the view and redraw everything since the tree sorting
3114 * might have rearranged things. */
3115 bool redraw = view->lines == 0;
3116 bool can_read = TRUE;
3118 if (!view->pipe)
3119 return TRUE;
3121 if (!io_can_read(view->pipe)) {
3122 if (view->lines == 0 && view_is_displayed(view)) {
3123 time_t secs = time(NULL) - view->start_time;
3125 if (secs > 1 && secs > view->update_secs) {
3126 if (view->update_secs == 0)
3127 redraw_view(view);
3128 update_view_title(view);
3129 view->update_secs = secs;
3132 return TRUE;
3135 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3136 if (opt_iconv_in != ICONV_NONE) {
3137 ICONV_CONST char *inbuf = line;
3138 size_t inlen = strlen(line) + 1;
3140 char *outbuf = out_buffer;
3141 size_t outlen = sizeof(out_buffer);
3143 size_t ret;
3145 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3146 if (ret != (size_t) -1)
3147 line = out_buffer;
3150 if (!view->ops->read(view, line)) {
3151 report("Allocation failure");
3152 end_update(view, TRUE);
3153 return FALSE;
3158 unsigned long lines = view->lines;
3159 int digits;
3161 for (digits = 0; lines; digits++)
3162 lines /= 10;
3164 /* Keep the displayed view in sync with line number scaling. */
3165 if (digits != view->digits) {
3166 view->digits = digits;
3167 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3168 redraw = TRUE;
3172 if (io_error(view->pipe)) {
3173 report("Failed to read: %s", io_strerror(view->pipe));
3174 end_update(view, TRUE);
3176 } else if (io_eof(view->pipe)) {
3177 report("");
3178 end_update(view, FALSE);
3181 if (restore_view_position(view))
3182 redraw = TRUE;
3184 if (!view_is_displayed(view))
3185 return TRUE;
3187 if (redraw)
3188 redraw_view_from(view, 0);
3189 else
3190 redraw_view_dirty(view);
3192 /* Update the title _after_ the redraw so that if the redraw picks up a
3193 * commit reference in view->ref it'll be available here. */
3194 update_view_title(view);
3195 return TRUE;
3198 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3200 static struct line *
3201 add_line_data(struct view *view, void *data, enum line_type type)
3203 struct line *line;
3205 if (!realloc_lines(&view->line, view->lines, 1))
3206 return NULL;
3208 line = &view->line[view->lines++];
3209 memset(line, 0, sizeof(*line));
3210 line->type = type;
3211 line->data = data;
3212 line->dirty = 1;
3214 return line;
3217 static struct line *
3218 add_line_text(struct view *view, const char *text, enum line_type type)
3220 char *data = text ? strdup(text) : NULL;
3222 return data ? add_line_data(view, data, type) : NULL;
3225 static struct line *
3226 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3228 char buf[SIZEOF_STR];
3229 va_list args;
3231 va_start(args, fmt);
3232 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3233 buf[0] = 0;
3234 va_end(args);
3236 return buf[0] ? add_line_text(view, buf, type) : NULL;
3240 * View opening
3243 enum open_flags {
3244 OPEN_DEFAULT = 0, /* Use default view switching. */
3245 OPEN_SPLIT = 1, /* Split current view. */
3246 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3247 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3248 OPEN_PREPARED = 32, /* Open already prepared command. */
3251 static void
3252 open_view(struct view *prev, enum request request, enum open_flags flags)
3254 bool split = !!(flags & OPEN_SPLIT);
3255 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3256 bool nomaximize = !!(flags & OPEN_REFRESH);
3257 struct view *view = VIEW(request);
3258 int nviews = displayed_views();
3259 struct view *base_view = display[0];
3261 if (view == prev && nviews == 1 && !reload) {
3262 report("Already in %s view", view->name);
3263 return;
3266 if (view->git_dir && !opt_git_dir[0]) {
3267 report("The %s view is disabled in pager view", view->name);
3268 return;
3271 if (split) {
3272 display[1] = view;
3273 current_view = 1;
3274 } else if (!nomaximize) {
3275 /* Maximize the current view. */
3276 memset(display, 0, sizeof(display));
3277 current_view = 0;
3278 display[current_view] = view;
3281 /* No parent signals that this is the first loaded view. */
3282 if (prev && view != prev) {
3283 view->parent = prev;
3286 /* Resize the view when switching between split- and full-screen,
3287 * or when switching between two different full-screen views. */
3288 if (nviews != displayed_views() ||
3289 (nviews == 1 && base_view != display[0]))
3290 resize_display();
3292 if (view->ops->open) {
3293 if (view->pipe)
3294 end_update(view, TRUE);
3295 if (!view->ops->open(view)) {
3296 report("Failed to load %s view", view->name);
3297 return;
3299 restore_view_position(view);
3301 } else if ((reload || strcmp(view->vid, view->id)) &&
3302 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3303 report("Failed to load %s view", view->name);
3304 return;
3307 if (split && prev->lineno - prev->offset >= prev->height) {
3308 /* Take the title line into account. */
3309 int lines = prev->lineno - prev->offset - prev->height + 1;
3311 /* Scroll the view that was split if the current line is
3312 * outside the new limited view. */
3313 do_scroll_view(prev, lines);
3316 if (prev && view != prev && split && view_is_displayed(prev)) {
3317 /* "Blur" the previous view. */
3318 update_view_title(prev);
3321 if (view->pipe && view->lines == 0) {
3322 /* Clear the old view and let the incremental updating refill
3323 * the screen. */
3324 werase(view->win);
3325 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3326 report("");
3327 } else if (view_is_displayed(view)) {
3328 redraw_view(view);
3329 report("");
3333 static void
3334 open_external_viewer(const char *argv[], const char *dir)
3336 def_prog_mode(); /* save current tty modes */
3337 endwin(); /* restore original tty modes */
3338 run_io_fg(argv, dir);
3339 fprintf(stderr, "Press Enter to continue");
3340 getc(opt_tty);
3341 reset_prog_mode();
3342 redraw_display(TRUE);
3345 static void
3346 open_mergetool(const char *file)
3348 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3350 open_external_viewer(mergetool_argv, opt_cdup);
3353 static void
3354 open_editor(const char *file)
3356 const char *editor_argv[] = { "vi", file, NULL };
3357 const char *editor;
3359 editor = getenv("GIT_EDITOR");
3360 if (!editor && *opt_editor)
3361 editor = opt_editor;
3362 if (!editor)
3363 editor = getenv("VISUAL");
3364 if (!editor)
3365 editor = getenv("EDITOR");
3366 if (!editor)
3367 editor = "vi";
3369 editor_argv[0] = editor;
3370 open_external_viewer(editor_argv, opt_cdup);
3373 static void
3374 open_run_request(enum request request)
3376 struct run_request *req = get_run_request(request);
3377 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3379 if (!req) {
3380 report("Unknown run request");
3381 return;
3384 if (format_argv(argv, req->argv, FORMAT_ALL))
3385 open_external_viewer(argv, NULL);
3386 free_argv(argv);
3390 * User request switch noodle
3393 static int
3394 view_driver(struct view *view, enum request request)
3396 int i;
3398 if (request == REQ_NONE)
3399 return TRUE;
3401 if (request > REQ_NONE) {
3402 open_run_request(request);
3403 /* FIXME: When all views can refresh always do this. */
3404 if (view == VIEW(REQ_VIEW_STATUS) ||
3405 view == VIEW(REQ_VIEW_MAIN) ||
3406 view == VIEW(REQ_VIEW_LOG) ||
3407 view == VIEW(REQ_VIEW_BRANCH) ||
3408 view == VIEW(REQ_VIEW_STAGE))
3409 request = REQ_REFRESH;
3410 else
3411 return TRUE;
3414 if (view && view->lines) {
3415 request = view->ops->request(view, request, &view->line[view->lineno]);
3416 if (request == REQ_NONE)
3417 return TRUE;
3420 switch (request) {
3421 case REQ_MOVE_UP:
3422 case REQ_MOVE_DOWN:
3423 case REQ_MOVE_PAGE_UP:
3424 case REQ_MOVE_PAGE_DOWN:
3425 case REQ_MOVE_FIRST_LINE:
3426 case REQ_MOVE_LAST_LINE:
3427 move_view(view, request);
3428 break;
3430 case REQ_SCROLL_LEFT:
3431 case REQ_SCROLL_RIGHT:
3432 case REQ_SCROLL_LINE_DOWN:
3433 case REQ_SCROLL_LINE_UP:
3434 case REQ_SCROLL_PAGE_DOWN:
3435 case REQ_SCROLL_PAGE_UP:
3436 scroll_view(view, request);
3437 break;
3439 case REQ_VIEW_BLAME:
3440 if (!opt_file[0]) {
3441 report("No file chosen, press %s to open tree view",
3442 get_key(view->keymap, REQ_VIEW_TREE));
3443 break;
3445 open_view(view, request, OPEN_DEFAULT);
3446 break;
3448 case REQ_VIEW_BLOB:
3449 if (!ref_blob[0]) {
3450 report("No file chosen, press %s to open tree view",
3451 get_key(view->keymap, REQ_VIEW_TREE));
3452 break;
3454 open_view(view, request, OPEN_DEFAULT);
3455 break;
3457 case REQ_VIEW_PAGER:
3458 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3459 report("No pager content, press %s to run command from prompt",
3460 get_key(view->keymap, REQ_PROMPT));
3461 break;
3463 open_view(view, request, OPEN_DEFAULT);
3464 break;
3466 case REQ_VIEW_STAGE:
3467 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3468 report("No stage content, press %s to open the status view and choose file",
3469 get_key(view->keymap, REQ_VIEW_STATUS));
3470 break;
3472 open_view(view, request, OPEN_DEFAULT);
3473 break;
3475 case REQ_VIEW_STATUS:
3476 if (opt_is_inside_work_tree == FALSE) {
3477 report("The status view requires a working tree");
3478 break;
3480 open_view(view, request, OPEN_DEFAULT);
3481 break;
3483 case REQ_VIEW_MAIN:
3484 case REQ_VIEW_DIFF:
3485 case REQ_VIEW_LOG:
3486 case REQ_VIEW_TREE:
3487 case REQ_VIEW_HELP:
3488 case REQ_VIEW_BRANCH:
3489 open_view(view, request, OPEN_DEFAULT);
3490 break;
3492 case REQ_NEXT:
3493 case REQ_PREVIOUS:
3494 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3496 if ((view == VIEW(REQ_VIEW_DIFF) &&
3497 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3498 (view == VIEW(REQ_VIEW_DIFF) &&
3499 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3500 (view == VIEW(REQ_VIEW_STAGE) &&
3501 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3502 (view == VIEW(REQ_VIEW_BLOB) &&
3503 view->parent == VIEW(REQ_VIEW_TREE)) ||
3504 (view == VIEW(REQ_VIEW_MAIN) &&
3505 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3506 int line;
3508 view = view->parent;
3509 line = view->lineno;
3510 move_view(view, request);
3511 if (view_is_displayed(view))
3512 update_view_title(view);
3513 if (line != view->lineno)
3514 view->ops->request(view, REQ_ENTER,
3515 &view->line[view->lineno]);
3517 } else {
3518 move_view(view, request);
3520 break;
3522 case REQ_VIEW_NEXT:
3524 int nviews = displayed_views();
3525 int next_view = (current_view + 1) % nviews;
3527 if (next_view == current_view) {
3528 report("Only one view is displayed");
3529 break;
3532 current_view = next_view;
3533 /* Blur out the title of the previous view. */
3534 update_view_title(view);
3535 report("");
3536 break;
3538 case REQ_REFRESH:
3539 report("Refreshing is not yet supported for the %s view", view->name);
3540 break;
3542 case REQ_MAXIMIZE:
3543 if (displayed_views() == 2)
3544 maximize_view(view);
3545 break;
3547 case REQ_OPTIONS:
3548 open_option_menu();
3549 break;
3551 case REQ_TOGGLE_LINENO:
3552 toggle_view_option(&opt_line_number, "line numbers");
3553 break;
3555 case REQ_TOGGLE_DATE:
3556 toggle_date();
3557 break;
3559 case REQ_TOGGLE_AUTHOR:
3560 toggle_author();
3561 break;
3563 case REQ_TOGGLE_REV_GRAPH:
3564 toggle_view_option(&opt_rev_graph, "revision graph display");
3565 break;
3567 case REQ_TOGGLE_REFS:
3568 toggle_view_option(&opt_show_refs, "reference display");
3569 break;
3571 case REQ_TOGGLE_SORT_FIELD:
3572 case REQ_TOGGLE_SORT_ORDER:
3573 report("Sorting is not yet supported for the %s view", view->name);
3574 break;
3576 case REQ_SEARCH:
3577 case REQ_SEARCH_BACK:
3578 search_view(view, request);
3579 break;
3581 case REQ_FIND_NEXT:
3582 case REQ_FIND_PREV:
3583 find_next(view, request);
3584 break;
3586 case REQ_STOP_LOADING:
3587 for (i = 0; i < ARRAY_SIZE(views); i++) {
3588 view = &views[i];
3589 if (view->pipe)
3590 report("Stopped loading the %s view", view->name),
3591 end_update(view, TRUE);
3593 break;
3595 case REQ_SHOW_VERSION:
3596 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3597 return TRUE;
3599 case REQ_SCREEN_REDRAW:
3600 redraw_display(TRUE);
3601 break;
3603 case REQ_EDIT:
3604 report("Nothing to edit");
3605 break;
3607 case REQ_ENTER:
3608 report("Nothing to enter");
3609 break;
3611 case REQ_VIEW_CLOSE:
3612 /* XXX: Mark closed views by letting view->parent point to the
3613 * view itself. Parents to closed view should never be
3614 * followed. */
3615 if (view->parent &&
3616 view->parent->parent != view->parent) {
3617 maximize_view(view->parent);
3618 view->parent = view;
3619 break;
3621 /* Fall-through */
3622 case REQ_QUIT:
3623 return FALSE;
3625 default:
3626 report("Unknown key, press %s for help",
3627 get_key(view->keymap, REQ_VIEW_HELP));
3628 return TRUE;
3631 return TRUE;
3636 * View backend utilities
3639 enum sort_field {
3640 ORDERBY_NAME,
3641 ORDERBY_DATE,
3642 ORDERBY_AUTHOR,
3645 struct sort_state {
3646 const enum sort_field *fields;
3647 size_t size, current;
3648 bool reverse;
3651 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3652 #define get_sort_field(state) ((state).fields[(state).current])
3653 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3655 static void
3656 sort_view(struct view *view, enum request request, struct sort_state *state,
3657 int (*compare)(const void *, const void *))
3659 switch (request) {
3660 case REQ_TOGGLE_SORT_FIELD:
3661 state->current = (state->current + 1) % state->size;
3662 break;
3664 case REQ_TOGGLE_SORT_ORDER:
3665 state->reverse = !state->reverse;
3666 break;
3667 default:
3668 die("Not a sort request");
3671 qsort(view->line, view->lines, sizeof(*view->line), compare);
3672 redraw_view(view);
3675 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3677 /* Small author cache to reduce memory consumption. It uses binary
3678 * search to lookup or find place to position new entries. No entries
3679 * are ever freed. */
3680 static const char *
3681 get_author(const char *name)
3683 static const char **authors;
3684 static size_t authors_size;
3685 int from = 0, to = authors_size - 1;
3687 while (from <= to) {
3688 size_t pos = (to + from) / 2;
3689 int cmp = strcmp(name, authors[pos]);
3691 if (!cmp)
3692 return authors[pos];
3694 if (cmp < 0)
3695 to = pos - 1;
3696 else
3697 from = pos + 1;
3700 if (!realloc_authors(&authors, authors_size, 1))
3701 return NULL;
3702 name = strdup(name);
3703 if (!name)
3704 return NULL;
3706 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3707 authors[from] = name;
3708 authors_size++;
3710 return name;
3713 static void
3714 parse_timesec(struct time *time, const char *sec)
3716 time->sec = (time_t) atol(sec);
3719 static void
3720 parse_timezone(struct time *time, const char *zone)
3722 long tz;
3724 tz = ('0' - zone[1]) * 60 * 60 * 10;
3725 tz += ('0' - zone[2]) * 60 * 60;
3726 tz += ('0' - zone[3]) * 60;
3727 tz += ('0' - zone[4]);
3729 if (zone[0] == '-')
3730 tz = -tz;
3732 time->tz = tz;
3733 time->sec -= tz;
3736 /* Parse author lines where the name may be empty:
3737 * author <email@address.tld> 1138474660 +0100
3739 static void
3740 parse_author_line(char *ident, const char **author, struct time *time)
3742 char *nameend = strchr(ident, '<');
3743 char *emailend = strchr(ident, '>');
3745 if (nameend && emailend)
3746 *nameend = *emailend = 0;
3747 ident = chomp_string(ident);
3748 if (!*ident) {
3749 if (nameend)
3750 ident = chomp_string(nameend + 1);
3751 if (!*ident)
3752 ident = "Unknown";
3755 *author = get_author(ident);
3757 /* Parse epoch and timezone */
3758 if (emailend && emailend[1] == ' ') {
3759 char *secs = emailend + 2;
3760 char *zone = strchr(secs, ' ');
3762 parse_timesec(time, secs);
3764 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3765 parse_timezone(time, zone + 1);
3769 static bool
3770 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3772 char rev[SIZEOF_REV];
3773 const char *revlist_argv[] = {
3774 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3776 struct menu_item *items;
3777 char text[SIZEOF_STR];
3778 bool ok = TRUE;
3779 int i;
3781 items = calloc(*parents + 1, sizeof(*items));
3782 if (!items)
3783 return FALSE;
3785 for (i = 0; i < *parents; i++) {
3786 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3787 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3788 !(items[i].text = strdup(text))) {
3789 ok = FALSE;
3790 break;
3794 if (ok) {
3795 *parents = 0;
3796 ok = prompt_menu("Select parent", items, parents);
3798 for (i = 0; items[i].text; i++)
3799 free((char *) items[i].text);
3800 free(items);
3801 return ok;
3804 static bool
3805 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3807 char buf[SIZEOF_STR * 4];
3808 const char *revlist_argv[] = {
3809 "git", "log", "--no-color", "-1",
3810 "--pretty=format:%P", id, "--", path, NULL
3812 int parents;
3814 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3815 (parents = strlen(buf) / 40) < 0) {
3816 report("Failed to get parent information");
3817 return FALSE;
3819 } else if (parents == 0) {
3820 if (path)
3821 report("Path '%s' does not exist in the parent", path);
3822 else
3823 report("The selected commit has no parents");
3824 return FALSE;
3827 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3828 return FALSE;
3830 string_copy_rev(rev, &buf[41 * parents]);
3831 return TRUE;
3835 * Pager backend
3838 static bool
3839 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3841 char text[SIZEOF_STR];
3843 if (opt_line_number && draw_lineno(view, lineno))
3844 return TRUE;
3846 string_expand(text, sizeof(text), line->data, opt_tab_size);
3847 draw_text(view, line->type, text, TRUE);
3848 return TRUE;
3851 static bool
3852 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3854 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3855 char ref[SIZEOF_STR];
3857 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3858 return TRUE;
3860 /* This is the only fatal call, since it can "corrupt" the buffer. */
3861 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3862 return FALSE;
3864 return TRUE;
3867 static void
3868 add_pager_refs(struct view *view, struct line *line)
3870 char buf[SIZEOF_STR];
3871 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3872 struct ref_list *list;
3873 size_t bufpos = 0, i;
3874 const char *sep = "Refs: ";
3875 bool is_tag = FALSE;
3877 assert(line->type == LINE_COMMIT);
3879 list = get_ref_list(commit_id);
3880 if (!list) {
3881 if (view == VIEW(REQ_VIEW_DIFF))
3882 goto try_add_describe_ref;
3883 return;
3886 for (i = 0; i < list->size; i++) {
3887 struct ref *ref = list->refs[i];
3888 const char *fmt = ref->tag ? "%s[%s]" :
3889 ref->remote ? "%s<%s>" : "%s%s";
3891 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3892 return;
3893 sep = ", ";
3894 if (ref->tag)
3895 is_tag = TRUE;
3898 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3899 try_add_describe_ref:
3900 /* Add <tag>-g<commit_id> "fake" reference. */
3901 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3902 return;
3905 if (bufpos == 0)
3906 return;
3908 add_line_text(view, buf, LINE_PP_REFS);
3911 static bool
3912 pager_read(struct view *view, char *data)
3914 struct line *line;
3916 if (!data)
3917 return TRUE;
3919 line = add_line_text(view, data, get_line_type(data));
3920 if (!line)
3921 return FALSE;
3923 if (line->type == LINE_COMMIT &&
3924 (view == VIEW(REQ_VIEW_DIFF) ||
3925 view == VIEW(REQ_VIEW_LOG)))
3926 add_pager_refs(view, line);
3928 return TRUE;
3931 static enum request
3932 pager_request(struct view *view, enum request request, struct line *line)
3934 int split = 0;
3936 if (request != REQ_ENTER)
3937 return request;
3939 if (line->type == LINE_COMMIT &&
3940 (view == VIEW(REQ_VIEW_LOG) ||
3941 view == VIEW(REQ_VIEW_PAGER))) {
3942 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3943 split = 1;
3946 /* Always scroll the view even if it was split. That way
3947 * you can use Enter to scroll through the log view and
3948 * split open each commit diff. */
3949 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3951 /* FIXME: A minor workaround. Scrolling the view will call report("")
3952 * but if we are scrolling a non-current view this won't properly
3953 * update the view title. */
3954 if (split)
3955 update_view_title(view);
3957 return REQ_NONE;
3960 static bool
3961 pager_grep(struct view *view, struct line *line)
3963 const char *text[] = { line->data, NULL };
3965 return grep_text(view, text);
3968 static void
3969 pager_select(struct view *view, struct line *line)
3971 if (line->type == LINE_COMMIT) {
3972 char *text = (char *)line->data + STRING_SIZE("commit ");
3974 if (view != VIEW(REQ_VIEW_PAGER))
3975 string_copy_rev(view->ref, text);
3976 string_copy_rev(ref_commit, text);
3980 static struct view_ops pager_ops = {
3981 "line",
3982 NULL,
3983 NULL,
3984 pager_read,
3985 pager_draw,
3986 pager_request,
3987 pager_grep,
3988 pager_select,
3991 static const char *log_argv[SIZEOF_ARG] = {
3992 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3995 static enum request
3996 log_request(struct view *view, enum request request, struct line *line)
3998 switch (request) {
3999 case REQ_REFRESH:
4000 load_refs();
4001 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4002 return REQ_NONE;
4003 default:
4004 return pager_request(view, request, line);
4008 static struct view_ops log_ops = {
4009 "line",
4010 log_argv,
4011 NULL,
4012 pager_read,
4013 pager_draw,
4014 log_request,
4015 pager_grep,
4016 pager_select,
4019 static const char *diff_argv[SIZEOF_ARG] = {
4020 "git", "show", "--pretty=fuller", "--no-color", "--root",
4021 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4024 static struct view_ops diff_ops = {
4025 "line",
4026 diff_argv,
4027 NULL,
4028 pager_read,
4029 pager_draw,
4030 pager_request,
4031 pager_grep,
4032 pager_select,
4036 * Help backend
4039 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4041 static bool
4042 help_open_keymap_title(struct view *view, enum keymap keymap)
4044 struct line *line;
4046 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4047 help_keymap_hidden[keymap] ? '+' : '-',
4048 enum_name(keymap_table[keymap]));
4049 if (line)
4050 line->other = keymap;
4052 return help_keymap_hidden[keymap];
4055 static void
4056 help_open_keymap(struct view *view, enum keymap keymap)
4058 const char *group = NULL;
4059 char buf[SIZEOF_STR];
4060 size_t bufpos;
4061 bool add_title = TRUE;
4062 int i;
4064 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4065 const char *key = NULL;
4067 if (req_info[i].request == REQ_NONE)
4068 continue;
4070 if (!req_info[i].request) {
4071 group = req_info[i].help;
4072 continue;
4075 key = get_keys(keymap, req_info[i].request, TRUE);
4076 if (!key || !*key)
4077 continue;
4079 if (add_title && help_open_keymap_title(view, keymap))
4080 return;
4081 add_title = FALSE;
4083 if (group) {
4084 add_line_text(view, group, LINE_HELP_GROUP);
4085 group = NULL;
4088 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4089 enum_name(req_info[i]), req_info[i].help);
4092 group = "External commands:";
4094 for (i = 0; i < run_requests; i++) {
4095 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4096 const char *key;
4097 int argc;
4099 if (!req || req->keymap != keymap)
4100 continue;
4102 key = get_key_name(req->key);
4103 if (!*key)
4104 key = "(no key defined)";
4106 if (add_title && help_open_keymap_title(view, keymap))
4107 return;
4108 if (group) {
4109 add_line_text(view, group, LINE_HELP_GROUP);
4110 group = NULL;
4113 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4114 if (!string_format_from(buf, &bufpos, "%s%s",
4115 argc ? " " : "", req->argv[argc]))
4116 return;
4118 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4122 static bool
4123 help_open(struct view *view)
4125 enum keymap keymap;
4127 reset_view(view);
4128 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4129 add_line_text(view, "", LINE_DEFAULT);
4131 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4132 help_open_keymap(view, keymap);
4134 return TRUE;
4137 static enum request
4138 help_request(struct view *view, enum request request, struct line *line)
4140 switch (request) {
4141 case REQ_ENTER:
4142 if (line->type == LINE_HELP_KEYMAP) {
4143 help_keymap_hidden[line->other] =
4144 !help_keymap_hidden[line->other];
4145 view->p_restore = TRUE;
4146 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4149 return REQ_NONE;
4150 default:
4151 return pager_request(view, request, line);
4155 static struct view_ops help_ops = {
4156 "line",
4157 NULL,
4158 help_open,
4159 NULL,
4160 pager_draw,
4161 help_request,
4162 pager_grep,
4163 pager_select,
4168 * Tree backend
4171 struct tree_stack_entry {
4172 struct tree_stack_entry *prev; /* Entry below this in the stack */
4173 unsigned long lineno; /* Line number to restore */
4174 char *name; /* Position of name in opt_path */
4177 /* The top of the path stack. */
4178 static struct tree_stack_entry *tree_stack = NULL;
4179 unsigned long tree_lineno = 0;
4181 static void
4182 pop_tree_stack_entry(void)
4184 struct tree_stack_entry *entry = tree_stack;
4186 tree_lineno = entry->lineno;
4187 entry->name[0] = 0;
4188 tree_stack = entry->prev;
4189 free(entry);
4192 static void
4193 push_tree_stack_entry(const char *name, unsigned long lineno)
4195 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4196 size_t pathlen = strlen(opt_path);
4198 if (!entry)
4199 return;
4201 entry->prev = tree_stack;
4202 entry->name = opt_path + pathlen;
4203 tree_stack = entry;
4205 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4206 pop_tree_stack_entry();
4207 return;
4210 /* Move the current line to the first tree entry. */
4211 tree_lineno = 1;
4212 entry->lineno = lineno;
4215 /* Parse output from git-ls-tree(1):
4217 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4220 #define SIZEOF_TREE_ATTR \
4221 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4223 #define SIZEOF_TREE_MODE \
4224 STRING_SIZE("100644 ")
4226 #define TREE_ID_OFFSET \
4227 STRING_SIZE("100644 blob ")
4229 struct tree_entry {
4230 char id[SIZEOF_REV];
4231 mode_t mode;
4232 struct time time; /* Date from the author ident. */
4233 const char *author; /* Author of the commit. */
4234 char name[1];
4237 static const char *
4238 tree_path(const struct line *line)
4240 return ((struct tree_entry *) line->data)->name;
4243 static int
4244 tree_compare_entry(const struct line *line1, const struct line *line2)
4246 if (line1->type != line2->type)
4247 return line1->type == LINE_TREE_DIR ? -1 : 1;
4248 return strcmp(tree_path(line1), tree_path(line2));
4251 static const enum sort_field tree_sort_fields[] = {
4252 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4254 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4256 static int
4257 tree_compare(const void *l1, const void *l2)
4259 const struct line *line1 = (const struct line *) l1;
4260 const struct line *line2 = (const struct line *) l2;
4261 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4262 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4264 if (line1->type == LINE_TREE_HEAD)
4265 return -1;
4266 if (line2->type == LINE_TREE_HEAD)
4267 return 1;
4269 switch (get_sort_field(tree_sort_state)) {
4270 case ORDERBY_DATE:
4271 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4273 case ORDERBY_AUTHOR:
4274 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4276 case ORDERBY_NAME:
4277 default:
4278 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4283 static struct line *
4284 tree_entry(struct view *view, enum line_type type, const char *path,
4285 const char *mode, const char *id)
4287 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4288 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4290 if (!entry || !line) {
4291 free(entry);
4292 return NULL;
4295 strncpy(entry->name, path, strlen(path));
4296 if (mode)
4297 entry->mode = strtoul(mode, NULL, 8);
4298 if (id)
4299 string_copy_rev(entry->id, id);
4301 return line;
4304 static bool
4305 tree_read_date(struct view *view, char *text, bool *read_date)
4307 static const char *author_name;
4308 static struct time author_time;
4310 if (!text && *read_date) {
4311 *read_date = FALSE;
4312 return TRUE;
4314 } else if (!text) {
4315 char *path = *opt_path ? opt_path : ".";
4316 /* Find next entry to process */
4317 const char *log_file[] = {
4318 "git", "log", "--no-color", "--pretty=raw",
4319 "--cc", "--raw", view->id, "--", path, NULL
4321 struct io io = {};
4323 if (!view->lines) {
4324 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4325 report("Tree is empty");
4326 return TRUE;
4329 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4330 report("Failed to load tree data");
4331 return TRUE;
4334 done_io(view->pipe);
4335 view->io = io;
4336 *read_date = TRUE;
4337 return FALSE;
4339 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4340 parse_author_line(text + STRING_SIZE("author "),
4341 &author_name, &author_time);
4343 } else if (*text == ':') {
4344 char *pos;
4345 size_t annotated = 1;
4346 size_t i;
4348 pos = strchr(text, '\t');
4349 if (!pos)
4350 return TRUE;
4351 text = pos + 1;
4352 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4353 text += strlen(opt_path);
4354 pos = strchr(text, '/');
4355 if (pos)
4356 *pos = 0;
4358 for (i = 1; i < view->lines; i++) {
4359 struct line *line = &view->line[i];
4360 struct tree_entry *entry = line->data;
4362 annotated += !!entry->author;
4363 if (entry->author || strcmp(entry->name, text))
4364 continue;
4366 entry->author = author_name;
4367 entry->time = author_time;
4368 line->dirty = 1;
4369 break;
4372 if (annotated == view->lines)
4373 kill_io(view->pipe);
4375 return TRUE;
4378 static bool
4379 tree_read(struct view *view, char *text)
4381 static bool read_date = FALSE;
4382 struct tree_entry *data;
4383 struct line *entry, *line;
4384 enum line_type type;
4385 size_t textlen = text ? strlen(text) : 0;
4386 char *path = text + SIZEOF_TREE_ATTR;
4388 if (read_date || !text)
4389 return tree_read_date(view, text, &read_date);
4391 if (textlen <= SIZEOF_TREE_ATTR)
4392 return FALSE;
4393 if (view->lines == 0 &&
4394 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4395 return FALSE;
4397 /* Strip the path part ... */
4398 if (*opt_path) {
4399 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4400 size_t striplen = strlen(opt_path);
4402 if (pathlen > striplen)
4403 memmove(path, path + striplen,
4404 pathlen - striplen + 1);
4406 /* Insert "link" to parent directory. */
4407 if (view->lines == 1 &&
4408 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4409 return FALSE;
4412 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4413 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4414 if (!entry)
4415 return FALSE;
4416 data = entry->data;
4418 /* Skip "Directory ..." and ".." line. */
4419 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4420 if (tree_compare_entry(line, entry) <= 0)
4421 continue;
4423 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4425 line->data = data;
4426 line->type = type;
4427 for (; line <= entry; line++)
4428 line->dirty = line->cleareol = 1;
4429 return TRUE;
4432 if (tree_lineno > view->lineno) {
4433 view->lineno = tree_lineno;
4434 tree_lineno = 0;
4437 return TRUE;
4440 static bool
4441 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4443 struct tree_entry *entry = line->data;
4445 if (line->type == LINE_TREE_HEAD) {
4446 if (draw_text(view, line->type, "Directory path /", TRUE))
4447 return TRUE;
4448 } else {
4449 if (draw_mode(view, entry->mode))
4450 return TRUE;
4452 if (opt_author && draw_author(view, entry->author))
4453 return TRUE;
4455 if (opt_date && draw_date(view, &entry->time))
4456 return TRUE;
4458 if (draw_text(view, line->type, entry->name, TRUE))
4459 return TRUE;
4460 return TRUE;
4463 static void
4464 open_blob_editor()
4466 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4467 int fd = mkstemp(file);
4469 if (fd == -1)
4470 report("Failed to create temporary file");
4471 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4472 report("Failed to save blob data to file");
4473 else
4474 open_editor(file);
4475 if (fd != -1)
4476 unlink(file);
4479 static enum request
4480 tree_request(struct view *view, enum request request, struct line *line)
4482 enum open_flags flags;
4484 switch (request) {
4485 case REQ_VIEW_BLAME:
4486 if (line->type != LINE_TREE_FILE) {
4487 report("Blame only supported for files");
4488 return REQ_NONE;
4491 string_copy(opt_ref, view->vid);
4492 return request;
4494 case REQ_EDIT:
4495 if (line->type != LINE_TREE_FILE) {
4496 report("Edit only supported for files");
4497 } else if (!is_head_commit(view->vid)) {
4498 open_blob_editor();
4499 } else {
4500 open_editor(opt_file);
4502 return REQ_NONE;
4504 case REQ_TOGGLE_SORT_FIELD:
4505 case REQ_TOGGLE_SORT_ORDER:
4506 sort_view(view, request, &tree_sort_state, tree_compare);
4507 return REQ_NONE;
4509 case REQ_PARENT:
4510 if (!*opt_path) {
4511 /* quit view if at top of tree */
4512 return REQ_VIEW_CLOSE;
4514 /* fake 'cd ..' */
4515 line = &view->line[1];
4516 break;
4518 case REQ_ENTER:
4519 break;
4521 default:
4522 return request;
4525 /* Cleanup the stack if the tree view is at a different tree. */
4526 while (!*opt_path && tree_stack)
4527 pop_tree_stack_entry();
4529 switch (line->type) {
4530 case LINE_TREE_DIR:
4531 /* Depending on whether it is a subdirectory or parent link
4532 * mangle the path buffer. */
4533 if (line == &view->line[1] && *opt_path) {
4534 pop_tree_stack_entry();
4536 } else {
4537 const char *basename = tree_path(line);
4539 push_tree_stack_entry(basename, view->lineno);
4542 /* Trees and subtrees share the same ID, so they are not not
4543 * unique like blobs. */
4544 flags = OPEN_RELOAD;
4545 request = REQ_VIEW_TREE;
4546 break;
4548 case LINE_TREE_FILE:
4549 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4550 request = REQ_VIEW_BLOB;
4551 break;
4553 default:
4554 return REQ_NONE;
4557 open_view(view, request, flags);
4558 if (request == REQ_VIEW_TREE)
4559 view->lineno = tree_lineno;
4561 return REQ_NONE;
4564 static bool
4565 tree_grep(struct view *view, struct line *line)
4567 struct tree_entry *entry = line->data;
4568 const char *text[] = {
4569 entry->name,
4570 opt_author ? entry->author : "",
4571 opt_date ? mkdate(&entry->time) : "",
4572 NULL
4575 return grep_text(view, text);
4578 static void
4579 tree_select(struct view *view, struct line *line)
4581 struct tree_entry *entry = line->data;
4583 if (line->type == LINE_TREE_FILE) {
4584 string_copy_rev(ref_blob, entry->id);
4585 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4587 } else if (line->type != LINE_TREE_DIR) {
4588 return;
4591 string_copy_rev(view->ref, entry->id);
4594 static bool
4595 tree_prepare(struct view *view)
4597 if (view->lines == 0 && opt_prefix[0]) {
4598 char *pos = opt_prefix;
4600 while (pos && *pos) {
4601 char *end = strchr(pos, '/');
4603 if (end)
4604 *end = 0;
4605 push_tree_stack_entry(pos, 0);
4606 pos = end;
4607 if (end) {
4608 *end = '/';
4609 pos++;
4613 } else if (strcmp(view->vid, view->id)) {
4614 opt_path[0] = 0;
4617 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4620 static const char *tree_argv[SIZEOF_ARG] = {
4621 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4624 static struct view_ops tree_ops = {
4625 "file",
4626 tree_argv,
4627 NULL,
4628 tree_read,
4629 tree_draw,
4630 tree_request,
4631 tree_grep,
4632 tree_select,
4633 tree_prepare,
4636 static bool
4637 blob_read(struct view *view, char *line)
4639 if (!line)
4640 return TRUE;
4641 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4644 static enum request
4645 blob_request(struct view *view, enum request request, struct line *line)
4647 switch (request) {
4648 case REQ_EDIT:
4649 open_blob_editor();
4650 return REQ_NONE;
4651 default:
4652 return pager_request(view, request, line);
4656 static const char *blob_argv[SIZEOF_ARG] = {
4657 "git", "cat-file", "blob", "%(blob)", NULL
4660 static struct view_ops blob_ops = {
4661 "line",
4662 blob_argv,
4663 NULL,
4664 blob_read,
4665 pager_draw,
4666 blob_request,
4667 pager_grep,
4668 pager_select,
4672 * Blame backend
4674 * Loading the blame view is a two phase job:
4676 * 1. File content is read either using opt_file from the
4677 * filesystem or using git-cat-file.
4678 * 2. Then blame information is incrementally added by
4679 * reading output from git-blame.
4682 static const char *blame_head_argv[] = {
4683 "git", "blame", "--incremental", "--", "%(file)", NULL
4686 static const char *blame_ref_argv[] = {
4687 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4690 static const char *blame_cat_file_argv[] = {
4691 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4694 struct blame_commit {
4695 char id[SIZEOF_REV]; /* SHA1 ID. */
4696 char title[128]; /* First line of the commit message. */
4697 const char *author; /* Author of the commit. */
4698 struct time time; /* Date from the author ident. */
4699 char filename[128]; /* Name of file. */
4700 bool has_previous; /* Was a "previous" line detected. */
4703 struct blame {
4704 struct blame_commit *commit;
4705 unsigned long lineno;
4706 char text[1];
4709 static bool
4710 blame_open(struct view *view)
4712 char path[SIZEOF_STR];
4714 if (!view->parent && *opt_prefix) {
4715 string_copy(path, opt_file);
4716 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4717 return FALSE;
4720 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4721 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4722 return FALSE;
4725 setup_update(view, opt_file);
4726 string_format(view->ref, "%s ...", opt_file);
4728 return TRUE;
4731 static struct blame_commit *
4732 get_blame_commit(struct view *view, const char *id)
4734 size_t i;
4736 for (i = 0; i < view->lines; i++) {
4737 struct blame *blame = view->line[i].data;
4739 if (!blame->commit)
4740 continue;
4742 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4743 return blame->commit;
4747 struct blame_commit *commit = calloc(1, sizeof(*commit));
4749 if (commit)
4750 string_ncopy(commit->id, id, SIZEOF_REV);
4751 return commit;
4755 static bool
4756 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4758 const char *pos = *posref;
4760 *posref = NULL;
4761 pos = strchr(pos + 1, ' ');
4762 if (!pos || !isdigit(pos[1]))
4763 return FALSE;
4764 *number = atoi(pos + 1);
4765 if (*number < min || *number > max)
4766 return FALSE;
4768 *posref = pos;
4769 return TRUE;
4772 static struct blame_commit *
4773 parse_blame_commit(struct view *view, const char *text, int *blamed)
4775 struct blame_commit *commit;
4776 struct blame *blame;
4777 const char *pos = text + SIZEOF_REV - 2;
4778 size_t orig_lineno = 0;
4779 size_t lineno;
4780 size_t group;
4782 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4783 return NULL;
4785 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4786 !parse_number(&pos, &lineno, 1, view->lines) ||
4787 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4788 return NULL;
4790 commit = get_blame_commit(view, text);
4791 if (!commit)
4792 return NULL;
4794 *blamed += group;
4795 while (group--) {
4796 struct line *line = &view->line[lineno + group - 1];
4798 blame = line->data;
4799 blame->commit = commit;
4800 blame->lineno = orig_lineno + group - 1;
4801 line->dirty = 1;
4804 return commit;
4807 static bool
4808 blame_read_file(struct view *view, const char *line, bool *read_file)
4810 if (!line) {
4811 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4812 struct io io = {};
4814 if (view->lines == 0 && !view->parent)
4815 die("No blame exist for %s", view->vid);
4817 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4818 report("Failed to load blame data");
4819 return TRUE;
4822 done_io(view->pipe);
4823 view->io = io;
4824 *read_file = FALSE;
4825 return FALSE;
4827 } else {
4828 size_t linelen = strlen(line);
4829 struct blame *blame = malloc(sizeof(*blame) + linelen);
4831 if (!blame)
4832 return FALSE;
4834 blame->commit = NULL;
4835 strncpy(blame->text, line, linelen);
4836 blame->text[linelen] = 0;
4837 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4841 static bool
4842 match_blame_header(const char *name, char **line)
4844 size_t namelen = strlen(name);
4845 bool matched = !strncmp(name, *line, namelen);
4847 if (matched)
4848 *line += namelen;
4850 return matched;
4853 static bool
4854 blame_read(struct view *view, char *line)
4856 static struct blame_commit *commit = NULL;
4857 static int blamed = 0;
4858 static bool read_file = TRUE;
4860 if (read_file)
4861 return blame_read_file(view, line, &read_file);
4863 if (!line) {
4864 /* Reset all! */
4865 commit = NULL;
4866 blamed = 0;
4867 read_file = TRUE;
4868 string_format(view->ref, "%s", view->vid);
4869 if (view_is_displayed(view)) {
4870 update_view_title(view);
4871 redraw_view_from(view, 0);
4873 return TRUE;
4876 if (!commit) {
4877 commit = parse_blame_commit(view, line, &blamed);
4878 string_format(view->ref, "%s %2d%%", view->vid,
4879 view->lines ? blamed * 100 / view->lines : 0);
4881 } else if (match_blame_header("author ", &line)) {
4882 commit->author = get_author(line);
4884 } else if (match_blame_header("author-time ", &line)) {
4885 parse_timesec(&commit->time, line);
4887 } else if (match_blame_header("author-tz ", &line)) {
4888 parse_timezone(&commit->time, line);
4890 } else if (match_blame_header("summary ", &line)) {
4891 string_ncopy(commit->title, line, strlen(line));
4893 } else if (match_blame_header("previous ", &line)) {
4894 commit->has_previous = TRUE;
4896 } else if (match_blame_header("filename ", &line)) {
4897 string_ncopy(commit->filename, line, strlen(line));
4898 commit = NULL;
4901 return TRUE;
4904 static bool
4905 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4907 struct blame *blame = line->data;
4908 struct time *time = NULL;
4909 const char *id = NULL, *author = NULL;
4910 char text[SIZEOF_STR];
4912 if (blame->commit && *blame->commit->filename) {
4913 id = blame->commit->id;
4914 author = blame->commit->author;
4915 time = &blame->commit->time;
4918 if (opt_date && draw_date(view, time))
4919 return TRUE;
4921 if (opt_author && draw_author(view, author))
4922 return TRUE;
4924 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4925 return TRUE;
4927 if (draw_lineno(view, lineno))
4928 return TRUE;
4930 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4931 draw_text(view, LINE_DEFAULT, text, TRUE);
4932 return TRUE;
4935 static bool
4936 check_blame_commit(struct blame *blame, bool check_null_id)
4938 if (!blame->commit)
4939 report("Commit data not loaded yet");
4940 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4941 report("No commit exist for the selected line");
4942 else
4943 return TRUE;
4944 return FALSE;
4947 static void
4948 setup_blame_parent_line(struct view *view, struct blame *blame)
4950 const char *diff_tree_argv[] = {
4951 "git", "diff-tree", "-U0", blame->commit->id,
4952 "--", blame->commit->filename, NULL
4954 struct io io = {};
4955 int parent_lineno = -1;
4956 int blamed_lineno = -1;
4957 char *line;
4959 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4960 return;
4962 while ((line = io_get(&io, '\n', TRUE))) {
4963 if (*line == '@') {
4964 char *pos = strchr(line, '+');
4966 parent_lineno = atoi(line + 4);
4967 if (pos)
4968 blamed_lineno = atoi(pos + 1);
4970 } else if (*line == '+' && parent_lineno != -1) {
4971 if (blame->lineno == blamed_lineno - 1 &&
4972 !strcmp(blame->text, line + 1)) {
4973 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4974 break;
4976 blamed_lineno++;
4980 done_io(&io);
4983 static enum request
4984 blame_request(struct view *view, enum request request, struct line *line)
4986 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4987 struct blame *blame = line->data;
4989 switch (request) {
4990 case REQ_VIEW_BLAME:
4991 if (check_blame_commit(blame, TRUE)) {
4992 string_copy(opt_ref, blame->commit->id);
4993 string_copy(opt_file, blame->commit->filename);
4994 if (blame->lineno)
4995 view->lineno = blame->lineno;
4996 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4998 break;
5000 case REQ_PARENT:
5001 if (check_blame_commit(blame, TRUE) &&
5002 select_commit_parent(blame->commit->id, opt_ref,
5003 blame->commit->filename)) {
5004 string_copy(opt_file, blame->commit->filename);
5005 setup_blame_parent_line(view, blame);
5006 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5008 break;
5010 case REQ_ENTER:
5011 if (!check_blame_commit(blame, FALSE))
5012 break;
5014 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5015 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5016 break;
5018 if (!strcmp(blame->commit->id, NULL_ID)) {
5019 struct view *diff = VIEW(REQ_VIEW_DIFF);
5020 const char *diff_index_argv[] = {
5021 "git", "diff-index", "--root", "--patch-with-stat",
5022 "-C", "-M", "HEAD", "--", view->vid, NULL
5025 if (!blame->commit->has_previous) {
5026 diff_index_argv[1] = "diff";
5027 diff_index_argv[2] = "--no-color";
5028 diff_index_argv[6] = "--";
5029 diff_index_argv[7] = "/dev/null";
5032 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5033 report("Failed to allocate diff command");
5034 break;
5036 flags |= OPEN_PREPARED;
5039 open_view(view, REQ_VIEW_DIFF, flags);
5040 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5041 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5042 break;
5044 default:
5045 return request;
5048 return REQ_NONE;
5051 static bool
5052 blame_grep(struct view *view, struct line *line)
5054 struct blame *blame = line->data;
5055 struct blame_commit *commit = blame->commit;
5056 const char *text[] = {
5057 blame->text,
5058 commit ? commit->title : "",
5059 commit ? commit->id : "",
5060 commit && opt_author ? commit->author : "",
5061 commit && opt_date ? mkdate(&commit->time) : "",
5062 NULL
5065 return grep_text(view, text);
5068 static void
5069 blame_select(struct view *view, struct line *line)
5071 struct blame *blame = line->data;
5072 struct blame_commit *commit = blame->commit;
5074 if (!commit)
5075 return;
5077 if (!strcmp(commit->id, NULL_ID))
5078 string_ncopy(ref_commit, "HEAD", 4);
5079 else
5080 string_copy_rev(ref_commit, commit->id);
5083 static struct view_ops blame_ops = {
5084 "line",
5085 NULL,
5086 blame_open,
5087 blame_read,
5088 blame_draw,
5089 blame_request,
5090 blame_grep,
5091 blame_select,
5095 * Branch backend
5098 struct branch {
5099 const char *author; /* Author of the last commit. */
5100 struct time time; /* Date of the last activity. */
5101 const struct ref *ref; /* Name and commit ID information. */
5104 static const struct ref branch_all;
5106 static const enum sort_field branch_sort_fields[] = {
5107 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5109 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5111 static int
5112 branch_compare(const void *l1, const void *l2)
5114 const struct branch *branch1 = ((const struct line *) l1)->data;
5115 const struct branch *branch2 = ((const struct line *) l2)->data;
5117 switch (get_sort_field(branch_sort_state)) {
5118 case ORDERBY_DATE:
5119 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5121 case ORDERBY_AUTHOR:
5122 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5124 case ORDERBY_NAME:
5125 default:
5126 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5130 static bool
5131 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5133 struct branch *branch = line->data;
5134 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5136 if (opt_date && draw_date(view, &branch->time))
5137 return TRUE;
5139 if (opt_author && draw_author(view, branch->author))
5140 return TRUE;
5142 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5143 return TRUE;
5146 static enum request
5147 branch_request(struct view *view, enum request request, struct line *line)
5149 struct branch *branch = line->data;
5151 switch (request) {
5152 case REQ_REFRESH:
5153 load_refs();
5154 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5155 return REQ_NONE;
5157 case REQ_TOGGLE_SORT_FIELD:
5158 case REQ_TOGGLE_SORT_ORDER:
5159 sort_view(view, request, &branch_sort_state, branch_compare);
5160 return REQ_NONE;
5162 case REQ_ENTER:
5163 if (branch->ref == &branch_all) {
5164 const char *all_branches_argv[] = {
5165 "git", "log", "--no-color", "--pretty=raw", "--parents",
5166 "--topo-order", "--all", NULL
5168 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5170 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5171 report("Failed to load view of all branches");
5172 return REQ_NONE;
5174 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5175 } else {
5176 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5178 return REQ_NONE;
5180 default:
5181 return request;
5185 static bool
5186 branch_read(struct view *view, char *line)
5188 static char id[SIZEOF_REV];
5189 struct branch *reference;
5190 size_t i;
5192 if (!line)
5193 return TRUE;
5195 switch (get_line_type(line)) {
5196 case LINE_COMMIT:
5197 string_copy_rev(id, line + STRING_SIZE("commit "));
5198 return TRUE;
5200 case LINE_AUTHOR:
5201 for (i = 0, reference = NULL; i < view->lines; i++) {
5202 struct branch *branch = view->line[i].data;
5204 if (strcmp(branch->ref->id, id))
5205 continue;
5207 view->line[i].dirty = TRUE;
5208 if (reference) {
5209 branch->author = reference->author;
5210 branch->time = reference->time;
5211 continue;
5214 parse_author_line(line + STRING_SIZE("author "),
5215 &branch->author, &branch->time);
5216 reference = branch;
5218 return TRUE;
5220 default:
5221 return TRUE;
5226 static bool
5227 branch_open_visitor(void *data, const struct ref *ref)
5229 struct view *view = data;
5230 struct branch *branch;
5232 if (ref->tag || ref->ltag || ref->remote)
5233 return TRUE;
5235 branch = calloc(1, sizeof(*branch));
5236 if (!branch)
5237 return FALSE;
5239 branch->ref = ref;
5240 return !!add_line_data(view, branch, LINE_DEFAULT);
5243 static bool
5244 branch_open(struct view *view)
5246 const char *branch_log[] = {
5247 "git", "log", "--no-color", "--pretty=raw",
5248 "--simplify-by-decoration", "--all", NULL
5251 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5252 report("Failed to load branch data");
5253 return TRUE;
5256 setup_update(view, view->id);
5257 branch_open_visitor(view, &branch_all);
5258 foreach_ref(branch_open_visitor, view);
5259 view->p_restore = TRUE;
5261 return TRUE;
5264 static bool
5265 branch_grep(struct view *view, struct line *line)
5267 struct branch *branch = line->data;
5268 const char *text[] = {
5269 branch->ref->name,
5270 branch->author,
5271 NULL
5274 return grep_text(view, text);
5277 static void
5278 branch_select(struct view *view, struct line *line)
5280 struct branch *branch = line->data;
5282 string_copy_rev(view->ref, branch->ref->id);
5283 string_copy_rev(ref_commit, branch->ref->id);
5284 string_copy_rev(ref_head, branch->ref->id);
5287 static struct view_ops branch_ops = {
5288 "branch",
5289 NULL,
5290 branch_open,
5291 branch_read,
5292 branch_draw,
5293 branch_request,
5294 branch_grep,
5295 branch_select,
5299 * Status backend
5302 struct status {
5303 char status;
5304 struct {
5305 mode_t mode;
5306 char rev[SIZEOF_REV];
5307 char name[SIZEOF_STR];
5308 } old;
5309 struct {
5310 mode_t mode;
5311 char rev[SIZEOF_REV];
5312 char name[SIZEOF_STR];
5313 } new;
5316 static char status_onbranch[SIZEOF_STR];
5317 static struct status stage_status;
5318 static enum line_type stage_line_type;
5319 static size_t stage_chunks;
5320 static int *stage_chunk;
5322 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5324 /* This should work even for the "On branch" line. */
5325 static inline bool
5326 status_has_none(struct view *view, struct line *line)
5328 return line < view->line + view->lines && !line[1].data;
5331 /* Get fields from the diff line:
5332 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5334 static inline bool
5335 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5337 const char *old_mode = buf + 1;
5338 const char *new_mode = buf + 8;
5339 const char *old_rev = buf + 15;
5340 const char *new_rev = buf + 56;
5341 const char *status = buf + 97;
5343 if (bufsize < 98 ||
5344 old_mode[-1] != ':' ||
5345 new_mode[-1] != ' ' ||
5346 old_rev[-1] != ' ' ||
5347 new_rev[-1] != ' ' ||
5348 status[-1] != ' ')
5349 return FALSE;
5351 file->status = *status;
5353 string_copy_rev(file->old.rev, old_rev);
5354 string_copy_rev(file->new.rev, new_rev);
5356 file->old.mode = strtoul(old_mode, NULL, 8);
5357 file->new.mode = strtoul(new_mode, NULL, 8);
5359 file->old.name[0] = file->new.name[0] = 0;
5361 return TRUE;
5364 static bool
5365 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5367 struct status *unmerged = NULL;
5368 char *buf;
5369 struct io io = {};
5371 if (!run_io(&io, argv, opt_cdup, IO_RD))
5372 return FALSE;
5374 add_line_data(view, NULL, type);
5376 while ((buf = io_get(&io, 0, TRUE))) {
5377 struct status *file = unmerged;
5379 if (!file) {
5380 file = calloc(1, sizeof(*file));
5381 if (!file || !add_line_data(view, file, type))
5382 goto error_out;
5385 /* Parse diff info part. */
5386 if (status) {
5387 file->status = status;
5388 if (status == 'A')
5389 string_copy(file->old.rev, NULL_ID);
5391 } else if (!file->status || file == unmerged) {
5392 if (!status_get_diff(file, buf, strlen(buf)))
5393 goto error_out;
5395 buf = io_get(&io, 0, TRUE);
5396 if (!buf)
5397 break;
5399 /* Collapse all modified entries that follow an
5400 * associated unmerged entry. */
5401 if (unmerged == file) {
5402 unmerged->status = 'U';
5403 unmerged = NULL;
5404 } else if (file->status == 'U') {
5405 unmerged = file;
5409 /* Grab the old name for rename/copy. */
5410 if (!*file->old.name &&
5411 (file->status == 'R' || file->status == 'C')) {
5412 string_ncopy(file->old.name, buf, strlen(buf));
5414 buf = io_get(&io, 0, TRUE);
5415 if (!buf)
5416 break;
5419 /* git-ls-files just delivers a NUL separated list of
5420 * file names similar to the second half of the
5421 * git-diff-* output. */
5422 string_ncopy(file->new.name, buf, strlen(buf));
5423 if (!*file->old.name)
5424 string_copy(file->old.name, file->new.name);
5425 file = NULL;
5428 if (io_error(&io)) {
5429 error_out:
5430 done_io(&io);
5431 return FALSE;
5434 if (!view->line[view->lines - 1].data)
5435 add_line_data(view, NULL, LINE_STAT_NONE);
5437 done_io(&io);
5438 return TRUE;
5441 /* Don't show unmerged entries in the staged section. */
5442 static const char *status_diff_index_argv[] = {
5443 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5444 "--cached", "-M", "HEAD", NULL
5447 static const char *status_diff_files_argv[] = {
5448 "git", "diff-files", "-z", NULL
5451 static const char *status_list_other_argv[] = {
5452 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5455 static const char *status_list_no_head_argv[] = {
5456 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5459 static const char *update_index_argv[] = {
5460 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5463 /* Restore the previous line number to stay in the context or select a
5464 * line with something that can be updated. */
5465 static void
5466 status_restore(struct view *view)
5468 if (view->p_lineno >= view->lines)
5469 view->p_lineno = view->lines - 1;
5470 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5471 view->p_lineno++;
5472 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5473 view->p_lineno--;
5475 /* If the above fails, always skip the "On branch" line. */
5476 if (view->p_lineno < view->lines)
5477 view->lineno = view->p_lineno;
5478 else
5479 view->lineno = 1;
5481 if (view->lineno < view->offset)
5482 view->offset = view->lineno;
5483 else if (view->offset + view->height <= view->lineno)
5484 view->offset = view->lineno - view->height + 1;
5486 view->p_restore = FALSE;
5489 static void
5490 status_update_onbranch(void)
5492 static const char *paths[][2] = {
5493 { "rebase-apply/rebasing", "Rebasing" },
5494 { "rebase-apply/applying", "Applying mailbox" },
5495 { "rebase-apply/", "Rebasing mailbox" },
5496 { "rebase-merge/interactive", "Interactive rebase" },
5497 { "rebase-merge/", "Rebase merge" },
5498 { "MERGE_HEAD", "Merging" },
5499 { "BISECT_LOG", "Bisecting" },
5500 { "HEAD", "On branch" },
5502 char buf[SIZEOF_STR];
5503 struct stat stat;
5504 int i;
5506 if (is_initial_commit()) {
5507 string_copy(status_onbranch, "Initial commit");
5508 return;
5511 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5512 char *head = opt_head;
5514 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5515 lstat(buf, &stat) < 0)
5516 continue;
5518 if (!*opt_head) {
5519 struct io io = {};
5521 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5522 io_read_buf(&io, buf, sizeof(buf))) {
5523 head = buf;
5524 if (!prefixcmp(head, "refs/heads/"))
5525 head += STRING_SIZE("refs/heads/");
5529 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5530 string_copy(status_onbranch, opt_head);
5531 return;
5534 string_copy(status_onbranch, "Not currently on any branch");
5537 /* First parse staged info using git-diff-index(1), then parse unstaged
5538 * info using git-diff-files(1), and finally untracked files using
5539 * git-ls-files(1). */
5540 static bool
5541 status_open(struct view *view)
5543 reset_view(view);
5545 add_line_data(view, NULL, LINE_STAT_HEAD);
5546 status_update_onbranch();
5548 run_io_bg(update_index_argv);
5550 if (is_initial_commit()) {
5551 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5552 return FALSE;
5553 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5554 return FALSE;
5557 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5558 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5559 return FALSE;
5561 /* Restore the exact position or use the specialized restore
5562 * mode? */
5563 if (!view->p_restore)
5564 status_restore(view);
5565 return TRUE;
5568 static bool
5569 status_draw(struct view *view, struct line *line, unsigned int lineno)
5571 struct status *status = line->data;
5572 enum line_type type;
5573 const char *text;
5575 if (!status) {
5576 switch (line->type) {
5577 case LINE_STAT_STAGED:
5578 type = LINE_STAT_SECTION;
5579 text = "Changes to be committed:";
5580 break;
5582 case LINE_STAT_UNSTAGED:
5583 type = LINE_STAT_SECTION;
5584 text = "Changed but not updated:";
5585 break;
5587 case LINE_STAT_UNTRACKED:
5588 type = LINE_STAT_SECTION;
5589 text = "Untracked files:";
5590 break;
5592 case LINE_STAT_NONE:
5593 type = LINE_DEFAULT;
5594 text = " (no files)";
5595 break;
5597 case LINE_STAT_HEAD:
5598 type = LINE_STAT_HEAD;
5599 text = status_onbranch;
5600 break;
5602 default:
5603 return FALSE;
5605 } else {
5606 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5608 buf[0] = status->status;
5609 if (draw_text(view, line->type, buf, TRUE))
5610 return TRUE;
5611 type = LINE_DEFAULT;
5612 text = status->new.name;
5615 draw_text(view, type, text, TRUE);
5616 return TRUE;
5619 static enum request
5620 status_load_error(struct view *view, struct view *stage, const char *path)
5622 if (displayed_views() == 2 || display[current_view] != view)
5623 maximize_view(view);
5624 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5625 return REQ_NONE;
5628 static enum request
5629 status_enter(struct view *view, struct line *line)
5631 struct status *status = line->data;
5632 const char *oldpath = status ? status->old.name : NULL;
5633 /* Diffs for unmerged entries are empty when passing the new
5634 * path, so leave it empty. */
5635 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5636 const char *info;
5637 enum open_flags split;
5638 struct view *stage = VIEW(REQ_VIEW_STAGE);
5640 if (line->type == LINE_STAT_NONE ||
5641 (!status && line[1].type == LINE_STAT_NONE)) {
5642 report("No file to diff");
5643 return REQ_NONE;
5646 switch (line->type) {
5647 case LINE_STAT_STAGED:
5648 if (is_initial_commit()) {
5649 const char *no_head_diff_argv[] = {
5650 "git", "diff", "--no-color", "--patch-with-stat",
5651 "--", "/dev/null", newpath, NULL
5654 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5655 return status_load_error(view, stage, newpath);
5656 } else {
5657 const char *index_show_argv[] = {
5658 "git", "diff-index", "--root", "--patch-with-stat",
5659 "-C", "-M", "--cached", "HEAD", "--",
5660 oldpath, newpath, NULL
5663 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5664 return status_load_error(view, stage, newpath);
5667 if (status)
5668 info = "Staged changes to %s";
5669 else
5670 info = "Staged changes";
5671 break;
5673 case LINE_STAT_UNSTAGED:
5675 const char *files_show_argv[] = {
5676 "git", "diff-files", "--root", "--patch-with-stat",
5677 "-C", "-M", "--", oldpath, newpath, NULL
5680 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5681 return status_load_error(view, stage, newpath);
5682 if (status)
5683 info = "Unstaged changes to %s";
5684 else
5685 info = "Unstaged changes";
5686 break;
5688 case LINE_STAT_UNTRACKED:
5689 if (!newpath) {
5690 report("No file to show");
5691 return REQ_NONE;
5694 if (!suffixcmp(status->new.name, -1, "/")) {
5695 report("Cannot display a directory");
5696 return REQ_NONE;
5699 if (!prepare_update_file(stage, newpath))
5700 return status_load_error(view, stage, newpath);
5701 info = "Untracked file %s";
5702 break;
5704 case LINE_STAT_HEAD:
5705 return REQ_NONE;
5707 default:
5708 die("line type %d not handled in switch", line->type);
5711 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5712 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5713 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5714 if (status) {
5715 stage_status = *status;
5716 } else {
5717 memset(&stage_status, 0, sizeof(stage_status));
5720 stage_line_type = line->type;
5721 stage_chunks = 0;
5722 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5725 return REQ_NONE;
5728 static bool
5729 status_exists(struct status *status, enum line_type type)
5731 struct view *view = VIEW(REQ_VIEW_STATUS);
5732 unsigned long lineno;
5734 for (lineno = 0; lineno < view->lines; lineno++) {
5735 struct line *line = &view->line[lineno];
5736 struct status *pos = line->data;
5738 if (line->type != type)
5739 continue;
5740 if (!pos && (!status || !status->status) && line[1].data) {
5741 select_view_line(view, lineno);
5742 return TRUE;
5744 if (pos && !strcmp(status->new.name, pos->new.name)) {
5745 select_view_line(view, lineno);
5746 return TRUE;
5750 return FALSE;
5754 static bool
5755 status_update_prepare(struct io *io, enum line_type type)
5757 const char *staged_argv[] = {
5758 "git", "update-index", "-z", "--index-info", NULL
5760 const char *others_argv[] = {
5761 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5764 switch (type) {
5765 case LINE_STAT_STAGED:
5766 return run_io(io, staged_argv, opt_cdup, IO_WR);
5768 case LINE_STAT_UNSTAGED:
5769 case LINE_STAT_UNTRACKED:
5770 return run_io(io, others_argv, opt_cdup, IO_WR);
5772 default:
5773 die("line type %d not handled in switch", type);
5774 return FALSE;
5778 static bool
5779 status_update_write(struct io *io, struct status *status, enum line_type type)
5781 char buf[SIZEOF_STR];
5782 size_t bufsize = 0;
5784 switch (type) {
5785 case LINE_STAT_STAGED:
5786 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5787 status->old.mode,
5788 status->old.rev,
5789 status->old.name, 0))
5790 return FALSE;
5791 break;
5793 case LINE_STAT_UNSTAGED:
5794 case LINE_STAT_UNTRACKED:
5795 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5796 return FALSE;
5797 break;
5799 default:
5800 die("line type %d not handled in switch", type);
5803 return io_write(io, buf, bufsize);
5806 static bool
5807 status_update_file(struct status *status, enum line_type type)
5809 struct io io = {};
5810 bool result;
5812 if (!status_update_prepare(&io, type))
5813 return FALSE;
5815 result = status_update_write(&io, status, type);
5816 return done_io(&io) && result;
5819 static bool
5820 status_update_files(struct view *view, struct line *line)
5822 char buf[sizeof(view->ref)];
5823 struct io io = {};
5824 bool result = TRUE;
5825 struct line *pos = view->line + view->lines;
5826 int files = 0;
5827 int file, done;
5828 int cursor_y = -1, cursor_x = -1;
5830 if (!status_update_prepare(&io, line->type))
5831 return FALSE;
5833 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5834 files++;
5836 string_copy(buf, view->ref);
5837 getsyx(cursor_y, cursor_x);
5838 for (file = 0, done = 5; result && file < files; line++, file++) {
5839 int almost_done = file * 100 / files;
5841 if (almost_done > done) {
5842 done = almost_done;
5843 string_format(view->ref, "updating file %u of %u (%d%% done)",
5844 file, files, done);
5845 update_view_title(view);
5846 setsyx(cursor_y, cursor_x);
5847 doupdate();
5849 result = status_update_write(&io, line->data, line->type);
5851 string_copy(view->ref, buf);
5853 return done_io(&io) && result;
5856 static bool
5857 status_update(struct view *view)
5859 struct line *line = &view->line[view->lineno];
5861 assert(view->lines);
5863 if (!line->data) {
5864 /* This should work even for the "On branch" line. */
5865 if (line < view->line + view->lines && !line[1].data) {
5866 report("Nothing to update");
5867 return FALSE;
5870 if (!status_update_files(view, line + 1)) {
5871 report("Failed to update file status");
5872 return FALSE;
5875 } else if (!status_update_file(line->data, line->type)) {
5876 report("Failed to update file status");
5877 return FALSE;
5880 return TRUE;
5883 static bool
5884 status_revert(struct status *status, enum line_type type, bool has_none)
5886 if (!status || type != LINE_STAT_UNSTAGED) {
5887 if (type == LINE_STAT_STAGED) {
5888 report("Cannot revert changes to staged files");
5889 } else if (type == LINE_STAT_UNTRACKED) {
5890 report("Cannot revert changes to untracked files");
5891 } else if (has_none) {
5892 report("Nothing to revert");
5893 } else {
5894 report("Cannot revert changes to multiple files");
5897 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5898 char mode[10] = "100644";
5899 const char *reset_argv[] = {
5900 "git", "update-index", "--cacheinfo", mode,
5901 status->old.rev, status->old.name, NULL
5903 const char *checkout_argv[] = {
5904 "git", "checkout", "--", status->old.name, NULL
5907 if (status->status == 'U') {
5908 string_format(mode, "%5o", status->old.mode);
5910 if (status->old.mode == 0 && status->new.mode == 0) {
5911 reset_argv[2] = "--force-remove";
5912 reset_argv[3] = status->old.name;
5913 reset_argv[4] = NULL;
5916 if (!run_io_fg(reset_argv, opt_cdup))
5917 return FALSE;
5918 if (status->old.mode == 0 && status->new.mode == 0)
5919 return TRUE;
5922 return run_io_fg(checkout_argv, opt_cdup);
5925 return FALSE;
5928 static enum request
5929 status_request(struct view *view, enum request request, struct line *line)
5931 struct status *status = line->data;
5933 switch (request) {
5934 case REQ_STATUS_UPDATE:
5935 if (!status_update(view))
5936 return REQ_NONE;
5937 break;
5939 case REQ_STATUS_REVERT:
5940 if (!status_revert(status, line->type, status_has_none(view, line)))
5941 return REQ_NONE;
5942 break;
5944 case REQ_STATUS_MERGE:
5945 if (!status || status->status != 'U') {
5946 report("Merging only possible for files with unmerged status ('U').");
5947 return REQ_NONE;
5949 open_mergetool(status->new.name);
5950 break;
5952 case REQ_EDIT:
5953 if (!status)
5954 return request;
5955 if (status->status == 'D') {
5956 report("File has been deleted.");
5957 return REQ_NONE;
5960 open_editor(status->new.name);
5961 break;
5963 case REQ_VIEW_BLAME:
5964 if (status)
5965 opt_ref[0] = 0;
5966 return request;
5968 case REQ_ENTER:
5969 /* After returning the status view has been split to
5970 * show the stage view. No further reloading is
5971 * necessary. */
5972 return status_enter(view, line);
5974 case REQ_REFRESH:
5975 /* Simply reload the view. */
5976 break;
5978 default:
5979 return request;
5982 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5984 return REQ_NONE;
5987 static void
5988 status_select(struct view *view, struct line *line)
5990 struct status *status = line->data;
5991 char file[SIZEOF_STR] = "all files";
5992 const char *text;
5993 const char *key;
5995 if (status && !string_format(file, "'%s'", status->new.name))
5996 return;
5998 if (!status && line[1].type == LINE_STAT_NONE)
5999 line++;
6001 switch (line->type) {
6002 case LINE_STAT_STAGED:
6003 text = "Press %s to unstage %s for commit";
6004 break;
6006 case LINE_STAT_UNSTAGED:
6007 text = "Press %s to stage %s for commit";
6008 break;
6010 case LINE_STAT_UNTRACKED:
6011 text = "Press %s to stage %s for addition";
6012 break;
6014 case LINE_STAT_HEAD:
6015 case LINE_STAT_NONE:
6016 text = "Nothing to update";
6017 break;
6019 default:
6020 die("line type %d not handled in switch", line->type);
6023 if (status && status->status == 'U') {
6024 text = "Press %s to resolve conflict in %s";
6025 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6027 } else {
6028 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6031 string_format(view->ref, text, key, file);
6032 if (status)
6033 string_copy(opt_file, status->new.name);
6036 static bool
6037 status_grep(struct view *view, struct line *line)
6039 struct status *status = line->data;
6041 if (status) {
6042 const char buf[2] = { status->status, 0 };
6043 const char *text[] = { status->new.name, buf, NULL };
6045 return grep_text(view, text);
6048 return FALSE;
6051 static struct view_ops status_ops = {
6052 "file",
6053 NULL,
6054 status_open,
6055 NULL,
6056 status_draw,
6057 status_request,
6058 status_grep,
6059 status_select,
6063 static bool
6064 stage_diff_write(struct io *io, struct line *line, struct line *end)
6066 while (line < end) {
6067 if (!io_write(io, line->data, strlen(line->data)) ||
6068 !io_write(io, "\n", 1))
6069 return FALSE;
6070 line++;
6071 if (line->type == LINE_DIFF_CHUNK ||
6072 line->type == LINE_DIFF_HEADER)
6073 break;
6076 return TRUE;
6079 static struct line *
6080 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6082 for (; view->line < line; line--)
6083 if (line->type == type)
6084 return line;
6086 return NULL;
6089 static bool
6090 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6092 const char *apply_argv[SIZEOF_ARG] = {
6093 "git", "apply", "--whitespace=nowarn", NULL
6095 struct line *diff_hdr;
6096 struct io io = {};
6097 int argc = 3;
6099 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6100 if (!diff_hdr)
6101 return FALSE;
6103 if (!revert)
6104 apply_argv[argc++] = "--cached";
6105 if (revert || stage_line_type == LINE_STAT_STAGED)
6106 apply_argv[argc++] = "-R";
6107 apply_argv[argc++] = "-";
6108 apply_argv[argc++] = NULL;
6109 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6110 return FALSE;
6112 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6113 !stage_diff_write(&io, chunk, view->line + view->lines))
6114 chunk = NULL;
6116 done_io(&io);
6117 run_io_bg(update_index_argv);
6119 return chunk ? TRUE : FALSE;
6122 static bool
6123 stage_update(struct view *view, struct line *line)
6125 struct line *chunk = NULL;
6127 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6128 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6130 if (chunk) {
6131 if (!stage_apply_chunk(view, chunk, FALSE)) {
6132 report("Failed to apply chunk");
6133 return FALSE;
6136 } else if (!stage_status.status) {
6137 view = VIEW(REQ_VIEW_STATUS);
6139 for (line = view->line; line < view->line + view->lines; line++)
6140 if (line->type == stage_line_type)
6141 break;
6143 if (!status_update_files(view, line + 1)) {
6144 report("Failed to update files");
6145 return FALSE;
6148 } else if (!status_update_file(&stage_status, stage_line_type)) {
6149 report("Failed to update file");
6150 return FALSE;
6153 return TRUE;
6156 static bool
6157 stage_revert(struct view *view, struct line *line)
6159 struct line *chunk = NULL;
6161 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6162 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6164 if (chunk) {
6165 if (!prompt_yesno("Are you sure you want to revert changes?"))
6166 return FALSE;
6168 if (!stage_apply_chunk(view, chunk, TRUE)) {
6169 report("Failed to revert chunk");
6170 return FALSE;
6172 return TRUE;
6174 } else {
6175 return status_revert(stage_status.status ? &stage_status : NULL,
6176 stage_line_type, FALSE);
6181 static void
6182 stage_next(struct view *view, struct line *line)
6184 int i;
6186 if (!stage_chunks) {
6187 for (line = view->line; line < view->line + view->lines; line++) {
6188 if (line->type != LINE_DIFF_CHUNK)
6189 continue;
6191 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6192 report("Allocation failure");
6193 return;
6196 stage_chunk[stage_chunks++] = line - view->line;
6200 for (i = 0; i < stage_chunks; i++) {
6201 if (stage_chunk[i] > view->lineno) {
6202 do_scroll_view(view, stage_chunk[i] - view->lineno);
6203 report("Chunk %d of %d", i + 1, stage_chunks);
6204 return;
6208 report("No next chunk found");
6211 static enum request
6212 stage_request(struct view *view, enum request request, struct line *line)
6214 switch (request) {
6215 case REQ_STATUS_UPDATE:
6216 if (!stage_update(view, line))
6217 return REQ_NONE;
6218 break;
6220 case REQ_STATUS_REVERT:
6221 if (!stage_revert(view, line))
6222 return REQ_NONE;
6223 break;
6225 case REQ_STAGE_NEXT:
6226 if (stage_line_type == LINE_STAT_UNTRACKED) {
6227 report("File is untracked; press %s to add",
6228 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6229 return REQ_NONE;
6231 stage_next(view, line);
6232 return REQ_NONE;
6234 case REQ_EDIT:
6235 if (!stage_status.new.name[0])
6236 return request;
6237 if (stage_status.status == 'D') {
6238 report("File has been deleted.");
6239 return REQ_NONE;
6242 open_editor(stage_status.new.name);
6243 break;
6245 case REQ_REFRESH:
6246 /* Reload everything ... */
6247 break;
6249 case REQ_VIEW_BLAME:
6250 if (stage_status.new.name[0]) {
6251 string_copy(opt_file, stage_status.new.name);
6252 opt_ref[0] = 0;
6254 return request;
6256 case REQ_ENTER:
6257 return pager_request(view, request, line);
6259 default:
6260 return request;
6263 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6264 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6266 /* Check whether the staged entry still exists, and close the
6267 * stage view if it doesn't. */
6268 if (!status_exists(&stage_status, stage_line_type)) {
6269 status_restore(VIEW(REQ_VIEW_STATUS));
6270 return REQ_VIEW_CLOSE;
6273 if (stage_line_type == LINE_STAT_UNTRACKED) {
6274 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6275 report("Cannot display a directory");
6276 return REQ_NONE;
6279 if (!prepare_update_file(view, stage_status.new.name)) {
6280 report("Failed to open file: %s", strerror(errno));
6281 return REQ_NONE;
6284 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6286 return REQ_NONE;
6289 static struct view_ops stage_ops = {
6290 "line",
6291 NULL,
6292 NULL,
6293 pager_read,
6294 pager_draw,
6295 stage_request,
6296 pager_grep,
6297 pager_select,
6302 * Revision graph
6305 struct commit {
6306 char id[SIZEOF_REV]; /* SHA1 ID. */
6307 char title[128]; /* First line of the commit message. */
6308 const char *author; /* Author of the commit. */
6309 struct time time; /* Date from the author ident. */
6310 struct ref_list *refs; /* Repository references. */
6311 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6312 size_t graph_size; /* The width of the graph array. */
6313 bool has_parents; /* Rewritten --parents seen. */
6316 /* Size of rev graph with no "padding" columns */
6317 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6319 struct rev_graph {
6320 struct rev_graph *prev, *next, *parents;
6321 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6322 size_t size;
6323 struct commit *commit;
6324 size_t pos;
6325 unsigned int boundary:1;
6328 /* Parents of the commit being visualized. */
6329 static struct rev_graph graph_parents[4];
6331 /* The current stack of revisions on the graph. */
6332 static struct rev_graph graph_stacks[4] = {
6333 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6334 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6335 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6336 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6339 static inline bool
6340 graph_parent_is_merge(struct rev_graph *graph)
6342 return graph->parents->size > 1;
6345 static inline void
6346 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6348 struct commit *commit = graph->commit;
6350 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6351 commit->graph[commit->graph_size++] = symbol;
6354 static void
6355 clear_rev_graph(struct rev_graph *graph)
6357 graph->boundary = 0;
6358 graph->size = graph->pos = 0;
6359 graph->commit = NULL;
6360 memset(graph->parents, 0, sizeof(*graph->parents));
6363 static void
6364 done_rev_graph(struct rev_graph *graph)
6366 if (graph_parent_is_merge(graph) &&
6367 graph->pos < graph->size - 1 &&
6368 graph->next->size == graph->size + graph->parents->size - 1) {
6369 size_t i = graph->pos + graph->parents->size - 1;
6371 graph->commit->graph_size = i * 2;
6372 while (i < graph->next->size - 1) {
6373 append_to_rev_graph(graph, ' ');
6374 append_to_rev_graph(graph, '\\');
6375 i++;
6379 clear_rev_graph(graph);
6382 static void
6383 push_rev_graph(struct rev_graph *graph, const char *parent)
6385 int i;
6387 /* "Collapse" duplicate parents lines.
6389 * FIXME: This needs to also update update the drawn graph but
6390 * for now it just serves as a method for pruning graph lines. */
6391 for (i = 0; i < graph->size; i++)
6392 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6393 return;
6395 if (graph->size < SIZEOF_REVITEMS) {
6396 string_copy_rev(graph->rev[graph->size++], parent);
6400 static chtype
6401 get_rev_graph_symbol(struct rev_graph *graph)
6403 chtype symbol;
6405 if (graph->boundary)
6406 symbol = REVGRAPH_BOUND;
6407 else if (graph->parents->size == 0)
6408 symbol = REVGRAPH_INIT;
6409 else if (graph_parent_is_merge(graph))
6410 symbol = REVGRAPH_MERGE;
6411 else if (graph->pos >= graph->size)
6412 symbol = REVGRAPH_BRANCH;
6413 else
6414 symbol = REVGRAPH_COMMIT;
6416 return symbol;
6419 static void
6420 draw_rev_graph(struct rev_graph *graph)
6422 struct rev_filler {
6423 chtype separator, line;
6425 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6426 static struct rev_filler fillers[] = {
6427 { ' ', '|' },
6428 { '`', '.' },
6429 { '\'', ' ' },
6430 { '/', ' ' },
6432 chtype symbol = get_rev_graph_symbol(graph);
6433 struct rev_filler *filler;
6434 size_t i;
6436 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6437 filler = &fillers[DEFAULT];
6439 for (i = 0; i < graph->pos; i++) {
6440 append_to_rev_graph(graph, filler->line);
6441 if (graph_parent_is_merge(graph->prev) &&
6442 graph->prev->pos == i)
6443 filler = &fillers[RSHARP];
6445 append_to_rev_graph(graph, filler->separator);
6448 /* Place the symbol for this revision. */
6449 append_to_rev_graph(graph, symbol);
6451 if (graph->prev->size > graph->size)
6452 filler = &fillers[RDIAG];
6453 else
6454 filler = &fillers[DEFAULT];
6456 i++;
6458 for (; i < graph->size; i++) {
6459 append_to_rev_graph(graph, filler->separator);
6460 append_to_rev_graph(graph, filler->line);
6461 if (graph_parent_is_merge(graph->prev) &&
6462 i < graph->prev->pos + graph->parents->size)
6463 filler = &fillers[RSHARP];
6464 if (graph->prev->size > graph->size)
6465 filler = &fillers[LDIAG];
6468 if (graph->prev->size > graph->size) {
6469 append_to_rev_graph(graph, filler->separator);
6470 if (filler->line != ' ')
6471 append_to_rev_graph(graph, filler->line);
6475 /* Prepare the next rev graph */
6476 static void
6477 prepare_rev_graph(struct rev_graph *graph)
6479 size_t i;
6481 /* First, traverse all lines of revisions up to the active one. */
6482 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6483 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6484 break;
6486 push_rev_graph(graph->next, graph->rev[graph->pos]);
6489 /* Interleave the new revision parent(s). */
6490 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6491 push_rev_graph(graph->next, graph->parents->rev[i]);
6493 /* Lastly, put any remaining revisions. */
6494 for (i = graph->pos + 1; i < graph->size; i++)
6495 push_rev_graph(graph->next, graph->rev[i]);
6498 static void
6499 update_rev_graph(struct view *view, struct rev_graph *graph)
6501 /* If this is the finalizing update ... */
6502 if (graph->commit)
6503 prepare_rev_graph(graph);
6505 /* Graph visualization needs a one rev look-ahead,
6506 * so the first update doesn't visualize anything. */
6507 if (!graph->prev->commit)
6508 return;
6510 if (view->lines > 2)
6511 view->line[view->lines - 3].dirty = 1;
6512 if (view->lines > 1)
6513 view->line[view->lines - 2].dirty = 1;
6514 draw_rev_graph(graph->prev);
6515 done_rev_graph(graph->prev->prev);
6520 * Main view backend
6523 static const char *main_argv[SIZEOF_ARG] = {
6524 "git", "log", "--no-color", "--pretty=raw", "--parents",
6525 "--topo-order", "%(head)", NULL
6528 static bool
6529 main_draw(struct view *view, struct line *line, unsigned int lineno)
6531 struct commit *commit = line->data;
6533 if (!commit->author)
6534 return FALSE;
6536 if (opt_date && draw_date(view, &commit->time))
6537 return TRUE;
6539 if (opt_author && draw_author(view, commit->author))
6540 return TRUE;
6542 if (opt_rev_graph && commit->graph_size &&
6543 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6544 return TRUE;
6546 if (opt_show_refs && commit->refs) {
6547 size_t i;
6549 for (i = 0; i < commit->refs->size; i++) {
6550 struct ref *ref = commit->refs->refs[i];
6551 enum line_type type;
6553 if (ref->head)
6554 type = LINE_MAIN_HEAD;
6555 else if (ref->ltag)
6556 type = LINE_MAIN_LOCAL_TAG;
6557 else if (ref->tag)
6558 type = LINE_MAIN_TAG;
6559 else if (ref->tracked)
6560 type = LINE_MAIN_TRACKED;
6561 else if (ref->remote)
6562 type = LINE_MAIN_REMOTE;
6563 else
6564 type = LINE_MAIN_REF;
6566 if (draw_text(view, type, "[", TRUE) ||
6567 draw_text(view, type, ref->name, TRUE) ||
6568 draw_text(view, type, "]", TRUE))
6569 return TRUE;
6571 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6572 return TRUE;
6576 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6577 return TRUE;
6580 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6581 static bool
6582 main_read(struct view *view, char *line)
6584 static struct rev_graph *graph = graph_stacks;
6585 enum line_type type;
6586 struct commit *commit;
6588 if (!line) {
6589 int i;
6591 if (!view->lines && !view->parent)
6592 die("No revisions match the given arguments.");
6593 if (view->lines > 0) {
6594 commit = view->line[view->lines - 1].data;
6595 view->line[view->lines - 1].dirty = 1;
6596 if (!commit->author) {
6597 view->lines--;
6598 free(commit);
6599 graph->commit = NULL;
6602 update_rev_graph(view, graph);
6604 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6605 clear_rev_graph(&graph_stacks[i]);
6606 return TRUE;
6609 type = get_line_type(line);
6610 if (type == LINE_COMMIT) {
6611 commit = calloc(1, sizeof(struct commit));
6612 if (!commit)
6613 return FALSE;
6615 line += STRING_SIZE("commit ");
6616 if (*line == '-') {
6617 graph->boundary = 1;
6618 line++;
6621 string_copy_rev(commit->id, line);
6622 commit->refs = get_ref_list(commit->id);
6623 graph->commit = commit;
6624 add_line_data(view, commit, LINE_MAIN_COMMIT);
6626 while ((line = strchr(line, ' '))) {
6627 line++;
6628 push_rev_graph(graph->parents, line);
6629 commit->has_parents = TRUE;
6631 return TRUE;
6634 if (!view->lines)
6635 return TRUE;
6636 commit = view->line[view->lines - 1].data;
6638 switch (type) {
6639 case LINE_PARENT:
6640 if (commit->has_parents)
6641 break;
6642 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6643 break;
6645 case LINE_AUTHOR:
6646 parse_author_line(line + STRING_SIZE("author "),
6647 &commit->author, &commit->time);
6648 update_rev_graph(view, graph);
6649 graph = graph->next;
6650 break;
6652 default:
6653 /* Fill in the commit title if it has not already been set. */
6654 if (commit->title[0])
6655 break;
6657 /* Require titles to start with a non-space character at the
6658 * offset used by git log. */
6659 if (strncmp(line, " ", 4))
6660 break;
6661 line += 4;
6662 /* Well, if the title starts with a whitespace character,
6663 * try to be forgiving. Otherwise we end up with no title. */
6664 while (isspace(*line))
6665 line++;
6666 if (*line == '\0')
6667 break;
6668 /* FIXME: More graceful handling of titles; append "..." to
6669 * shortened titles, etc. */
6671 string_expand(commit->title, sizeof(commit->title), line, 1);
6672 view->line[view->lines - 1].dirty = 1;
6675 return TRUE;
6678 static enum request
6679 main_request(struct view *view, enum request request, struct line *line)
6681 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6683 switch (request) {
6684 case REQ_ENTER:
6685 open_view(view, REQ_VIEW_DIFF, flags);
6686 break;
6687 case REQ_REFRESH:
6688 load_refs();
6689 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6690 break;
6691 default:
6692 return request;
6695 return REQ_NONE;
6698 static bool
6699 grep_refs(struct ref_list *list, regex_t *regex)
6701 regmatch_t pmatch;
6702 size_t i;
6704 if (!opt_show_refs || !list)
6705 return FALSE;
6707 for (i = 0; i < list->size; i++) {
6708 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6709 return TRUE;
6712 return FALSE;
6715 static bool
6716 main_grep(struct view *view, struct line *line)
6718 struct commit *commit = line->data;
6719 const char *text[] = {
6720 commit->title,
6721 opt_author ? commit->author : "",
6722 opt_date ? mkdate(&commit->time) : "",
6723 NULL
6726 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6729 static void
6730 main_select(struct view *view, struct line *line)
6732 struct commit *commit = line->data;
6734 string_copy_rev(view->ref, commit->id);
6735 string_copy_rev(ref_commit, view->ref);
6738 static struct view_ops main_ops = {
6739 "commit",
6740 main_argv,
6741 NULL,
6742 main_read,
6743 main_draw,
6744 main_request,
6745 main_grep,
6746 main_select,
6751 * Unicode / UTF-8 handling
6753 * NOTE: Much of the following code for dealing with Unicode is derived from
6754 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6755 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6758 static inline int
6759 unicode_width(unsigned long c)
6761 if (c >= 0x1100 &&
6762 (c <= 0x115f /* Hangul Jamo */
6763 || c == 0x2329
6764 || c == 0x232a
6765 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6766 /* CJK ... Yi */
6767 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6768 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6769 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6770 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6771 || (c >= 0xffe0 && c <= 0xffe6)
6772 || (c >= 0x20000 && c <= 0x2fffd)
6773 || (c >= 0x30000 && c <= 0x3fffd)))
6774 return 2;
6776 if (c == '\t')
6777 return opt_tab_size;
6779 return 1;
6782 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6783 * Illegal bytes are set one. */
6784 static const unsigned char utf8_bytes[256] = {
6785 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,
6786 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,
6787 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,
6788 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,
6789 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,
6790 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,
6791 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,
6792 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,
6795 static inline unsigned char
6796 utf8_char_length(const char *string, const char *end)
6798 int c = *(unsigned char *) string;
6800 return utf8_bytes[c];
6803 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6804 static inline unsigned long
6805 utf8_to_unicode(const char *string, size_t length)
6807 unsigned long unicode;
6809 switch (length) {
6810 case 1:
6811 unicode = string[0];
6812 break;
6813 case 2:
6814 unicode = (string[0] & 0x1f) << 6;
6815 unicode += (string[1] & 0x3f);
6816 break;
6817 case 3:
6818 unicode = (string[0] & 0x0f) << 12;
6819 unicode += ((string[1] & 0x3f) << 6);
6820 unicode += (string[2] & 0x3f);
6821 break;
6822 case 4:
6823 unicode = (string[0] & 0x0f) << 18;
6824 unicode += ((string[1] & 0x3f) << 12);
6825 unicode += ((string[2] & 0x3f) << 6);
6826 unicode += (string[3] & 0x3f);
6827 break;
6828 case 5:
6829 unicode = (string[0] & 0x0f) << 24;
6830 unicode += ((string[1] & 0x3f) << 18);
6831 unicode += ((string[2] & 0x3f) << 12);
6832 unicode += ((string[3] & 0x3f) << 6);
6833 unicode += (string[4] & 0x3f);
6834 break;
6835 case 6:
6836 unicode = (string[0] & 0x01) << 30;
6837 unicode += ((string[1] & 0x3f) << 24);
6838 unicode += ((string[2] & 0x3f) << 18);
6839 unicode += ((string[3] & 0x3f) << 12);
6840 unicode += ((string[4] & 0x3f) << 6);
6841 unicode += (string[5] & 0x3f);
6842 break;
6843 default:
6844 die("Invalid Unicode length");
6847 /* Invalid characters could return the special 0xfffd value but NUL
6848 * should be just as good. */
6849 return unicode > 0xffff ? 0 : unicode;
6852 /* Calculates how much of string can be shown within the given maximum width
6853 * and sets trimmed parameter to non-zero value if all of string could not be
6854 * shown. If the reserve flag is TRUE, it will reserve at least one
6855 * trailing character, which can be useful when drawing a delimiter.
6857 * Returns the number of bytes to output from string to satisfy max_width. */
6858 static size_t
6859 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6861 const char *string = *start;
6862 const char *end = strchr(string, '\0');
6863 unsigned char last_bytes = 0;
6864 size_t last_ucwidth = 0;
6866 *width = 0;
6867 *trimmed = 0;
6869 while (string < end) {
6870 unsigned char bytes = utf8_char_length(string, end);
6871 size_t ucwidth;
6872 unsigned long unicode;
6874 if (string + bytes > end)
6875 break;
6877 /* Change representation to figure out whether
6878 * it is a single- or double-width character. */
6880 unicode = utf8_to_unicode(string, bytes);
6881 /* FIXME: Graceful handling of invalid Unicode character. */
6882 if (!unicode)
6883 break;
6885 ucwidth = unicode_width(unicode);
6886 if (skip > 0) {
6887 skip -= ucwidth <= skip ? ucwidth : skip;
6888 *start += bytes;
6890 *width += ucwidth;
6891 if (*width > max_width) {
6892 *trimmed = 1;
6893 *width -= ucwidth;
6894 if (reserve && *width == max_width) {
6895 string -= last_bytes;
6896 *width -= last_ucwidth;
6898 break;
6901 string += bytes;
6902 last_bytes = ucwidth ? bytes : 0;
6903 last_ucwidth = ucwidth;
6906 return string - *start;
6911 * Status management
6914 /* Whether or not the curses interface has been initialized. */
6915 static bool cursed = FALSE;
6917 /* Terminal hacks and workarounds. */
6918 static bool use_scroll_redrawwin;
6919 static bool use_scroll_status_wclear;
6921 /* The status window is used for polling keystrokes. */
6922 static WINDOW *status_win;
6924 /* Reading from the prompt? */
6925 static bool input_mode = FALSE;
6927 static bool status_empty = FALSE;
6929 /* Update status and title window. */
6930 static void
6931 report(const char *msg, ...)
6933 struct view *view = display[current_view];
6935 if (input_mode)
6936 return;
6938 if (!view) {
6939 char buf[SIZEOF_STR];
6940 va_list args;
6942 va_start(args, msg);
6943 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6944 buf[sizeof(buf) - 1] = 0;
6945 buf[sizeof(buf) - 2] = '.';
6946 buf[sizeof(buf) - 3] = '.';
6947 buf[sizeof(buf) - 4] = '.';
6949 va_end(args);
6950 die("%s", buf);
6953 if (!status_empty || *msg) {
6954 va_list args;
6956 va_start(args, msg);
6958 wmove(status_win, 0, 0);
6959 if (view->has_scrolled && use_scroll_status_wclear)
6960 wclear(status_win);
6961 if (*msg) {
6962 vwprintw(status_win, msg, args);
6963 status_empty = FALSE;
6964 } else {
6965 status_empty = TRUE;
6967 wclrtoeol(status_win);
6968 wnoutrefresh(status_win);
6970 va_end(args);
6973 update_view_title(view);
6976 /* Controls when nodelay should be in effect when polling user input. */
6977 static void
6978 set_nonblocking_input(bool loading)
6980 static unsigned int loading_views;
6982 if ((loading == FALSE && loading_views-- == 1) ||
6983 (loading == TRUE && loading_views++ == 0))
6984 nodelay(status_win, loading);
6987 static void
6988 init_display(void)
6990 const char *term;
6991 int x, y;
6993 /* Initialize the curses library */
6994 if (isatty(STDIN_FILENO)) {
6995 cursed = !!initscr();
6996 opt_tty = stdin;
6997 } else {
6998 /* Leave stdin and stdout alone when acting as a pager. */
6999 opt_tty = fopen("/dev/tty", "r+");
7000 if (!opt_tty)
7001 die("Failed to open /dev/tty");
7002 cursed = !!newterm(NULL, opt_tty, opt_tty);
7005 if (!cursed)
7006 die("Failed to initialize curses");
7008 nonl(); /* Disable conversion and detect newlines from input. */
7009 cbreak(); /* Take input chars one at a time, no wait for \n */
7010 noecho(); /* Don't echo input */
7011 leaveok(stdscr, FALSE);
7013 if (has_colors())
7014 init_colors();
7016 getmaxyx(stdscr, y, x);
7017 status_win = newwin(1, 0, y - 1, 0);
7018 if (!status_win)
7019 die("Failed to create status window");
7021 /* Enable keyboard mapping */
7022 keypad(status_win, TRUE);
7023 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7025 TABSIZE = opt_tab_size;
7027 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7028 if (term && !strcmp(term, "gnome-terminal")) {
7029 /* In the gnome-terminal-emulator, the message from
7030 * scrolling up one line when impossible followed by
7031 * scrolling down one line causes corruption of the
7032 * status line. This is fixed by calling wclear. */
7033 use_scroll_status_wclear = TRUE;
7034 use_scroll_redrawwin = FALSE;
7036 } else if (term && !strcmp(term, "xrvt-xpm")) {
7037 /* No problems with full optimizations in xrvt-(unicode)
7038 * and aterm. */
7039 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7041 } else {
7042 /* When scrolling in (u)xterm the last line in the
7043 * scrolling direction will update slowly. */
7044 use_scroll_redrawwin = TRUE;
7045 use_scroll_status_wclear = FALSE;
7049 static int
7050 get_input(int prompt_position)
7052 struct view *view;
7053 int i, key, cursor_y, cursor_x;
7055 if (prompt_position)
7056 input_mode = TRUE;
7058 while (TRUE) {
7059 foreach_view (view, i) {
7060 update_view(view);
7061 if (view_is_displayed(view) && view->has_scrolled &&
7062 use_scroll_redrawwin)
7063 redrawwin(view->win);
7064 view->has_scrolled = FALSE;
7067 /* Update the cursor position. */
7068 if (prompt_position) {
7069 getbegyx(status_win, cursor_y, cursor_x);
7070 cursor_x = prompt_position;
7071 } else {
7072 view = display[current_view];
7073 getbegyx(view->win, cursor_y, cursor_x);
7074 cursor_x = view->width - 1;
7075 cursor_y += view->lineno - view->offset;
7077 setsyx(cursor_y, cursor_x);
7079 /* Refresh, accept single keystroke of input */
7080 doupdate();
7081 key = wgetch(status_win);
7083 /* wgetch() with nodelay() enabled returns ERR when
7084 * there's no input. */
7085 if (key == ERR) {
7087 } else if (key == KEY_RESIZE) {
7088 int height, width;
7090 getmaxyx(stdscr, height, width);
7092 wresize(status_win, 1, width);
7093 mvwin(status_win, height - 1, 0);
7094 wnoutrefresh(status_win);
7095 resize_display();
7096 redraw_display(TRUE);
7098 } else {
7099 input_mode = FALSE;
7100 return key;
7105 static char *
7106 prompt_input(const char *prompt, input_handler handler, void *data)
7108 enum input_status status = INPUT_OK;
7109 static char buf[SIZEOF_STR];
7110 size_t pos = 0;
7112 buf[pos] = 0;
7114 while (status == INPUT_OK || status == INPUT_SKIP) {
7115 int key;
7117 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7118 wclrtoeol(status_win);
7120 key = get_input(pos + 1);
7121 switch (key) {
7122 case KEY_RETURN:
7123 case KEY_ENTER:
7124 case '\n':
7125 status = pos ? INPUT_STOP : INPUT_CANCEL;
7126 break;
7128 case KEY_BACKSPACE:
7129 if (pos > 0)
7130 buf[--pos] = 0;
7131 else
7132 status = INPUT_CANCEL;
7133 break;
7135 case KEY_ESC:
7136 status = INPUT_CANCEL;
7137 break;
7139 default:
7140 if (pos >= sizeof(buf)) {
7141 report("Input string too long");
7142 return NULL;
7145 status = handler(data, buf, key);
7146 if (status == INPUT_OK)
7147 buf[pos++] = (char) key;
7151 /* Clear the status window */
7152 status_empty = FALSE;
7153 report("");
7155 if (status == INPUT_CANCEL)
7156 return NULL;
7158 buf[pos++] = 0;
7160 return buf;
7163 static enum input_status
7164 prompt_yesno_handler(void *data, char *buf, int c)
7166 if (c == 'y' || c == 'Y')
7167 return INPUT_STOP;
7168 if (c == 'n' || c == 'N')
7169 return INPUT_CANCEL;
7170 return INPUT_SKIP;
7173 static bool
7174 prompt_yesno(const char *prompt)
7176 char prompt2[SIZEOF_STR];
7178 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7179 return FALSE;
7181 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7184 static enum input_status
7185 read_prompt_handler(void *data, char *buf, int c)
7187 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7190 static char *
7191 read_prompt(const char *prompt)
7193 return prompt_input(prompt, read_prompt_handler, NULL);
7196 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7198 enum input_status status = INPUT_OK;
7199 int size = 0;
7201 while (items[size].text)
7202 size++;
7204 while (status == INPUT_OK) {
7205 const struct menu_item *item = &items[*selected];
7206 int key;
7207 int i;
7209 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7210 prompt, *selected + 1, size);
7211 if (item->hotkey)
7212 wprintw(status_win, "[%c] ", (char) item->hotkey);
7213 wprintw(status_win, "%s", item->text);
7214 wclrtoeol(status_win);
7216 key = get_input(COLS - 1);
7217 switch (key) {
7218 case KEY_RETURN:
7219 case KEY_ENTER:
7220 case '\n':
7221 status = INPUT_STOP;
7222 break;
7224 case KEY_LEFT:
7225 case KEY_UP:
7226 *selected = *selected - 1;
7227 if (*selected < 0)
7228 *selected = size - 1;
7229 break;
7231 case KEY_RIGHT:
7232 case KEY_DOWN:
7233 *selected = (*selected + 1) % size;
7234 break;
7236 case KEY_ESC:
7237 status = INPUT_CANCEL;
7238 break;
7240 default:
7241 for (i = 0; items[i].text; i++)
7242 if (items[i].hotkey == key) {
7243 *selected = i;
7244 status = INPUT_STOP;
7245 break;
7250 /* Clear the status window */
7251 status_empty = FALSE;
7252 report("");
7254 return status != INPUT_CANCEL;
7258 * Repository properties
7261 static struct ref **refs = NULL;
7262 static size_t refs_size = 0;
7264 static struct ref_list **ref_lists = NULL;
7265 static size_t ref_lists_size = 0;
7267 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7268 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7269 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7271 static int
7272 compare_refs(const void *ref1_, const void *ref2_)
7274 const struct ref *ref1 = *(const struct ref **)ref1_;
7275 const struct ref *ref2 = *(const struct ref **)ref2_;
7277 if (ref1->tag != ref2->tag)
7278 return ref2->tag - ref1->tag;
7279 if (ref1->ltag != ref2->ltag)
7280 return ref2->ltag - ref2->ltag;
7281 if (ref1->head != ref2->head)
7282 return ref2->head - ref1->head;
7283 if (ref1->tracked != ref2->tracked)
7284 return ref2->tracked - ref1->tracked;
7285 if (ref1->remote != ref2->remote)
7286 return ref2->remote - ref1->remote;
7287 return strcmp(ref1->name, ref2->name);
7290 static void
7291 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7293 size_t i;
7295 for (i = 0; i < refs_size; i++)
7296 if (!visitor(data, refs[i]))
7297 break;
7300 static struct ref_list *
7301 get_ref_list(const char *id)
7303 struct ref_list *list;
7304 size_t i;
7306 for (i = 0; i < ref_lists_size; i++)
7307 if (!strcmp(id, ref_lists[i]->id))
7308 return ref_lists[i];
7310 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7311 return NULL;
7312 list = calloc(1, sizeof(*list));
7313 if (!list)
7314 return NULL;
7316 for (i = 0; i < refs_size; i++) {
7317 if (!strcmp(id, refs[i]->id) &&
7318 realloc_refs_list(&list->refs, list->size, 1))
7319 list->refs[list->size++] = refs[i];
7322 if (!list->refs) {
7323 free(list);
7324 return NULL;
7327 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7328 ref_lists[ref_lists_size++] = list;
7329 return list;
7332 static int
7333 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7335 struct ref *ref = NULL;
7336 bool tag = FALSE;
7337 bool ltag = FALSE;
7338 bool remote = FALSE;
7339 bool tracked = FALSE;
7340 bool head = FALSE;
7341 int from = 0, to = refs_size - 1;
7343 if (!prefixcmp(name, "refs/tags/")) {
7344 if (!suffixcmp(name, namelen, "^{}")) {
7345 namelen -= 3;
7346 name[namelen] = 0;
7347 } else {
7348 ltag = TRUE;
7351 tag = TRUE;
7352 namelen -= STRING_SIZE("refs/tags/");
7353 name += STRING_SIZE("refs/tags/");
7355 } else if (!prefixcmp(name, "refs/remotes/")) {
7356 remote = TRUE;
7357 namelen -= STRING_SIZE("refs/remotes/");
7358 name += STRING_SIZE("refs/remotes/");
7359 tracked = !strcmp(opt_remote, name);
7361 } else if (!prefixcmp(name, "refs/heads/")) {
7362 namelen -= STRING_SIZE("refs/heads/");
7363 name += STRING_SIZE("refs/heads/");
7364 head = !strncmp(opt_head, name, namelen);
7366 } else if (!strcmp(name, "HEAD")) {
7367 string_ncopy(opt_head_rev, id, idlen);
7368 return OK;
7371 /* If we are reloading or it's an annotated tag, replace the
7372 * previous SHA1 with the resolved commit id; relies on the fact
7373 * git-ls-remote lists the commit id of an annotated tag right
7374 * before the commit id it points to. */
7375 while (from <= to) {
7376 size_t pos = (to + from) / 2;
7377 int cmp = strcmp(name, refs[pos]->name);
7379 if (!cmp) {
7380 ref = refs[pos];
7381 break;
7384 if (cmp < 0)
7385 to = pos - 1;
7386 else
7387 from = pos + 1;
7390 if (!ref) {
7391 if (!realloc_refs(&refs, refs_size, 1))
7392 return ERR;
7393 ref = calloc(1, sizeof(*ref) + namelen);
7394 if (!ref)
7395 return ERR;
7396 memmove(refs + from + 1, refs + from,
7397 (refs_size - from) * sizeof(*refs));
7398 refs[from] = ref;
7399 strncpy(ref->name, name, namelen);
7400 refs_size++;
7403 ref->head = head;
7404 ref->tag = tag;
7405 ref->ltag = ltag;
7406 ref->remote = remote;
7407 ref->tracked = tracked;
7408 string_copy_rev(ref->id, id);
7410 return OK;
7413 static int
7414 load_refs(void)
7416 const char *head_argv[] = {
7417 "git", "symbolic-ref", "HEAD", NULL
7419 static const char *ls_remote_argv[SIZEOF_ARG] = {
7420 "git", "ls-remote", opt_git_dir, NULL
7422 static bool init = FALSE;
7423 size_t i;
7425 if (!init) {
7426 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7427 init = TRUE;
7430 if (!*opt_git_dir)
7431 return OK;
7433 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7434 !prefixcmp(opt_head, "refs/heads/")) {
7435 char *offset = opt_head + STRING_SIZE("refs/heads/");
7437 memmove(opt_head, offset, strlen(offset) + 1);
7440 for (i = 0; i < refs_size; i++)
7441 refs[i]->id[0] = 0;
7443 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7444 return ERR;
7446 /* Update the ref lists to reflect changes. */
7447 for (i = 0; i < ref_lists_size; i++) {
7448 struct ref_list *list = ref_lists[i];
7449 size_t old, new;
7451 for (old = new = 0; old < list->size; old++)
7452 if (!strcmp(list->id, list->refs[old]->id))
7453 list->refs[new++] = list->refs[old];
7454 list->size = new;
7457 return OK;
7460 static void
7461 set_remote_branch(const char *name, const char *value, size_t valuelen)
7463 if (!strcmp(name, ".remote")) {
7464 string_ncopy(opt_remote, value, valuelen);
7466 } else if (*opt_remote && !strcmp(name, ".merge")) {
7467 size_t from = strlen(opt_remote);
7469 if (!prefixcmp(value, "refs/heads/"))
7470 value += STRING_SIZE("refs/heads/");
7472 if (!string_format_from(opt_remote, &from, "/%s", value))
7473 opt_remote[0] = 0;
7477 static void
7478 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7480 const char *argv[SIZEOF_ARG] = { name, "=" };
7481 int argc = 1 + (cmd == option_set_command);
7482 int error = ERR;
7484 if (!argv_from_string(argv, &argc, value))
7485 config_msg = "Too many option arguments";
7486 else
7487 error = cmd(argc, argv);
7489 if (error == ERR)
7490 warn("Option 'tig.%s': %s", name, config_msg);
7493 static bool
7494 set_environment_variable(const char *name, const char *value)
7496 size_t len = strlen(name) + 1 + strlen(value) + 1;
7497 char *env = malloc(len);
7499 if (env &&
7500 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7501 putenv(env) == 0)
7502 return TRUE;
7503 free(env);
7504 return FALSE;
7507 static void
7508 set_work_tree(const char *value)
7510 char cwd[SIZEOF_STR];
7512 if (!getcwd(cwd, sizeof(cwd)))
7513 die("Failed to get cwd path: %s", strerror(errno));
7514 if (chdir(opt_git_dir) < 0)
7515 die("Failed to chdir(%s): %s", strerror(errno));
7516 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7517 die("Failed to get git path: %s", strerror(errno));
7518 if (chdir(cwd) < 0)
7519 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7520 if (chdir(value) < 0)
7521 die("Failed to chdir(%s): %s", value, strerror(errno));
7522 if (!getcwd(cwd, sizeof(cwd)))
7523 die("Failed to get cwd path: %s", strerror(errno));
7524 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7525 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7526 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7527 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7528 opt_is_inside_work_tree = TRUE;
7531 static int
7532 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7534 if (!strcmp(name, "i18n.commitencoding"))
7535 string_ncopy(opt_encoding, value, valuelen);
7537 else if (!strcmp(name, "core.editor"))
7538 string_ncopy(opt_editor, value, valuelen);
7540 else if (!strcmp(name, "core.worktree"))
7541 set_work_tree(value);
7543 else if (!prefixcmp(name, "tig.color."))
7544 set_repo_config_option(name + 10, value, option_color_command);
7546 else if (!prefixcmp(name, "tig.bind."))
7547 set_repo_config_option(name + 9, value, option_bind_command);
7549 else if (!prefixcmp(name, "tig."))
7550 set_repo_config_option(name + 4, value, option_set_command);
7552 else if (*opt_head && !prefixcmp(name, "branch.") &&
7553 !strncmp(name + 7, opt_head, strlen(opt_head)))
7554 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7556 return OK;
7559 static int
7560 load_git_config(void)
7562 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7564 return run_io_load(config_list_argv, "=", read_repo_config_option);
7567 static int
7568 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7570 if (!opt_git_dir[0]) {
7571 string_ncopy(opt_git_dir, name, namelen);
7573 } else if (opt_is_inside_work_tree == -1) {
7574 /* This can be 3 different values depending on the
7575 * version of git being used. If git-rev-parse does not
7576 * understand --is-inside-work-tree it will simply echo
7577 * the option else either "true" or "false" is printed.
7578 * Default to true for the unknown case. */
7579 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7581 } else if (*name == '.') {
7582 string_ncopy(opt_cdup, name, namelen);
7584 } else {
7585 string_ncopy(opt_prefix, name, namelen);
7588 return OK;
7591 static int
7592 load_repo_info(void)
7594 const char *rev_parse_argv[] = {
7595 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7596 "--show-cdup", "--show-prefix", NULL
7599 return run_io_load(rev_parse_argv, "=", read_repo_info);
7604 * Main
7607 static const char usage[] =
7608 "tig " TIG_VERSION " (" __DATE__ ")\n"
7609 "\n"
7610 "Usage: tig [options] [revs] [--] [paths]\n"
7611 " or: tig show [options] [revs] [--] [paths]\n"
7612 " or: tig blame [rev] path\n"
7613 " or: tig status\n"
7614 " or: tig < [git command output]\n"
7615 "\n"
7616 "Options:\n"
7617 " -v, --version Show version and exit\n"
7618 " -h, --help Show help message and exit";
7620 static void __NORETURN
7621 quit(int sig)
7623 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7624 if (cursed)
7625 endwin();
7626 exit(0);
7629 static void __NORETURN
7630 die(const char *err, ...)
7632 va_list args;
7634 endwin();
7636 va_start(args, err);
7637 fputs("tig: ", stderr);
7638 vfprintf(stderr, err, args);
7639 fputs("\n", stderr);
7640 va_end(args);
7642 exit(1);
7645 static void
7646 warn(const char *msg, ...)
7648 va_list args;
7650 va_start(args, msg);
7651 fputs("tig warning: ", stderr);
7652 vfprintf(stderr, msg, args);
7653 fputs("\n", stderr);
7654 va_end(args);
7657 static enum request
7658 parse_options(int argc, const char *argv[])
7660 enum request request = REQ_VIEW_MAIN;
7661 const char *subcommand;
7662 bool seen_dashdash = FALSE;
7663 /* XXX: This is vulnerable to the user overriding options
7664 * required for the main view parser. */
7665 const char *custom_argv[SIZEOF_ARG] = {
7666 "git", "log", "--no-color", "--pretty=raw", "--parents",
7667 "--topo-order", NULL
7669 int i, j = 6;
7671 if (!isatty(STDIN_FILENO)) {
7672 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7673 return REQ_VIEW_PAGER;
7676 if (argc <= 1)
7677 return REQ_NONE;
7679 subcommand = argv[1];
7680 if (!strcmp(subcommand, "status")) {
7681 if (argc > 2)
7682 warn("ignoring arguments after `%s'", subcommand);
7683 return REQ_VIEW_STATUS;
7685 } else if (!strcmp(subcommand, "blame")) {
7686 if (argc <= 2 || argc > 4)
7687 die("invalid number of options to blame\n\n%s", usage);
7689 i = 2;
7690 if (argc == 4) {
7691 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7692 i++;
7695 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7696 return REQ_VIEW_BLAME;
7698 } else if (!strcmp(subcommand, "show")) {
7699 request = REQ_VIEW_DIFF;
7701 } else {
7702 subcommand = NULL;
7705 if (subcommand) {
7706 custom_argv[1] = subcommand;
7707 j = 2;
7710 for (i = 1 + !!subcommand; i < argc; i++) {
7711 const char *opt = argv[i];
7713 if (seen_dashdash || !strcmp(opt, "--")) {
7714 seen_dashdash = TRUE;
7716 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7717 printf("tig version %s\n", TIG_VERSION);
7718 quit(0);
7720 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7721 printf("%s\n", usage);
7722 quit(0);
7725 custom_argv[j++] = opt;
7726 if (j >= ARRAY_SIZE(custom_argv))
7727 die("command too long");
7730 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7731 die("Failed to format arguments");
7733 return request;
7737 main(int argc, const char *argv[])
7739 enum request request = parse_options(argc, argv);
7740 struct view *view;
7741 size_t i;
7743 signal(SIGINT, quit);
7744 signal(SIGPIPE, SIG_IGN);
7746 if (setlocale(LC_ALL, "")) {
7747 char *codeset = nl_langinfo(CODESET);
7749 string_ncopy(opt_codeset, codeset, strlen(codeset));
7752 if (load_repo_info() == ERR)
7753 die("Failed to load repo info.");
7755 if (load_options() == ERR)
7756 die("Failed to load user config.");
7758 if (load_git_config() == ERR)
7759 die("Failed to load repo config.");
7761 /* Require a git repository unless when running in pager mode. */
7762 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7763 die("Not a git repository");
7765 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7766 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7767 if (opt_iconv_in == ICONV_NONE)
7768 die("Failed to initialize character set conversion");
7771 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7772 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7773 if (opt_iconv_out == ICONV_NONE)
7774 die("Failed to initialize character set conversion");
7777 if (load_refs() == ERR)
7778 die("Failed to load refs.");
7780 foreach_view (view, i)
7781 argv_from_env(view->ops->argv, view->cmd_env);
7783 init_display();
7785 if (request != REQ_NONE)
7786 open_view(NULL, request, OPEN_PREPARED);
7787 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7789 while (view_driver(display[current_view], request)) {
7790 int key = get_input(0);
7792 view = display[current_view];
7793 request = get_keybinding(view->keymap, key);
7795 /* Some low-level request handling. This keeps access to
7796 * status_win restricted. */
7797 switch (request) {
7798 case REQ_PROMPT:
7800 char *cmd = read_prompt(":");
7802 if (cmd && isdigit(*cmd)) {
7803 int lineno = view->lineno + 1;
7805 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7806 select_view_line(view, lineno - 1);
7807 report("");
7808 } else {
7809 report("Unable to parse '%s' as a line number", cmd);
7812 } else if (cmd) {
7813 struct view *next = VIEW(REQ_VIEW_PAGER);
7814 const char *argv[SIZEOF_ARG] = { "git" };
7815 int argc = 1;
7817 /* When running random commands, initially show the
7818 * command in the title. However, it maybe later be
7819 * overwritten if a commit line is selected. */
7820 string_ncopy(next->ref, cmd, strlen(cmd));
7822 if (!argv_from_string(argv, &argc, cmd)) {
7823 report("Too many arguments");
7824 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7825 report("Failed to format command");
7826 } else {
7827 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7831 request = REQ_NONE;
7832 break;
7834 case REQ_SEARCH:
7835 case REQ_SEARCH_BACK:
7837 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7838 char *search = read_prompt(prompt);
7840 if (search)
7841 string_ncopy(opt_search, search, strlen(search));
7842 else if (*opt_search)
7843 request = request == REQ_SEARCH ?
7844 REQ_FIND_NEXT :
7845 REQ_FIND_PREV;
7846 else
7847 request = REQ_NONE;
7848 break;
7850 default:
7851 break;
7855 quit(0);
7857 return 0;