Add an option to ignore unknown directories contents in the status view
[tig.git] / tig.c
blobb6df54c82060163ce895102c69180a1294b48e78
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, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
139 static struct ref *get_ref_head();
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum input_status {
145 INPUT_OK,
146 INPUT_SKIP,
147 INPUT_STOP,
148 INPUT_CANCEL
151 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
153 static char *prompt_input(const char *prompt, input_handler handler, void *data);
154 static bool prompt_yesno(const char *prompt);
156 struct menu_item {
157 int hotkey;
158 const char *text;
159 void *data;
162 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
165 * Allocation helpers ... Entering macro hell to never be seen again.
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 static type * \
170 name(type **mem, size_t size, size_t increase) \
172 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
173 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174 type *tmp = *mem; \
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178 if (tmp) \
179 *mem = tmp; \
182 return tmp; \
186 * String helpers
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static size_t
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216 size_t size, pos;
218 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219 if (src[pos] == '\t') {
220 size_t expanded = tabsize - (size % tabsize);
222 if (expanded + size >= dstlen - 1)
223 expanded = dstlen - size - 1;
224 memcpy(dst + size, " ", expanded);
225 size += expanded;
226 } else {
227 dst[size++] = src[pos];
231 dst[size] = 0;
232 return pos;
235 static char *
236 chomp_string(char *name)
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
291 return 0;
294 #define enum_equals(entry, str, len) \
295 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
297 struct enum_map {
298 const char *name;
299 int namelen;
300 int value;
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 static char *
306 enum_map_name(const char *name, size_t namelen)
308 static char buf[SIZEOF_STR];
309 int bufpos;
311 for (bufpos = 0; bufpos <= namelen; bufpos++) {
312 buf[bufpos] = tolower(name[bufpos]);
313 if (buf[bufpos] == '_')
314 buf[bufpos] = '-';
317 buf[bufpos] = 0;
318 return buf;
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 static bool
324 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
326 size_t namelen = strlen(name);
327 int i;
329 for (i = 0; i < map_size; i++)
330 if (enum_equals(map[i], name, namelen)) {
331 *value = map[i].value;
332 return TRUE;
335 return FALSE;
338 #define map_enum(attr, map, name) \
339 map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342 strncmp(str1, str2, STRING_SIZE(str2))
344 static inline int
345 suffixcmp(const char *str, int slen, const char *suffix)
347 size_t len = slen >= 0 ? slen : strlen(str);
348 size_t suffixlen = strlen(suffix);
350 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
355 * Unicode / UTF-8 handling
357 * NOTE: Much of the following code for dealing with Unicode is derived from
358 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
362 static inline int
363 unicode_width(unsigned long c, int tab_size)
365 if (c >= 0x1100 &&
366 (c <= 0x115f /* Hangul Jamo */
367 || c == 0x2329
368 || c == 0x232a
369 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
370 /* CJK ... Yi */
371 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
372 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
373 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
374 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
375 || (c >= 0xffe0 && c <= 0xffe6)
376 || (c >= 0x20000 && c <= 0x2fffd)
377 || (c >= 0x30000 && c <= 0x3fffd)))
378 return 2;
380 if (c == '\t')
381 return tab_size;
383 return 1;
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387 * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes[256] = {
389 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,
390 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,
391 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,
392 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,
393 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,
394 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,
395 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,
396 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,
399 static inline unsigned char
400 utf8_char_length(const char *string, const char *end)
402 int c = *(unsigned char *) string;
404 return utf8_bytes[c];
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string, size_t length)
411 unsigned long unicode;
413 switch (length) {
414 case 1:
415 unicode = string[0];
416 break;
417 case 2:
418 unicode = (string[0] & 0x1f) << 6;
419 unicode += (string[1] & 0x3f);
420 break;
421 case 3:
422 unicode = (string[0] & 0x0f) << 12;
423 unicode += ((string[1] & 0x3f) << 6);
424 unicode += (string[2] & 0x3f);
425 break;
426 case 4:
427 unicode = (string[0] & 0x0f) << 18;
428 unicode += ((string[1] & 0x3f) << 12);
429 unicode += ((string[2] & 0x3f) << 6);
430 unicode += (string[3] & 0x3f);
431 break;
432 case 5:
433 unicode = (string[0] & 0x0f) << 24;
434 unicode += ((string[1] & 0x3f) << 18);
435 unicode += ((string[2] & 0x3f) << 12);
436 unicode += ((string[3] & 0x3f) << 6);
437 unicode += (string[4] & 0x3f);
438 break;
439 case 6:
440 unicode = (string[0] & 0x01) << 30;
441 unicode += ((string[1] & 0x3f) << 24);
442 unicode += ((string[2] & 0x3f) << 18);
443 unicode += ((string[3] & 0x3f) << 12);
444 unicode += ((string[4] & 0x3f) << 6);
445 unicode += (string[5] & 0x3f);
446 break;
447 default:
448 return 0;
451 /* Invalid characters could return the special 0xfffd value but NUL
452 * should be just as good. */
453 return unicode > 0xffff ? 0 : unicode;
456 /* Calculates how much of string can be shown within the given maximum width
457 * and sets trimmed parameter to non-zero value if all of string could not be
458 * shown. If the reserve flag is TRUE, it will reserve at least one
459 * trailing character, which can be useful when drawing a delimiter.
461 * Returns the number of bytes to output from string to satisfy max_width. */
462 static size_t
463 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
465 const char *string = *start;
466 const char *end = strchr(string, '\0');
467 unsigned char last_bytes = 0;
468 size_t last_ucwidth = 0;
470 *width = 0;
471 *trimmed = 0;
473 while (string < end) {
474 unsigned char bytes = utf8_char_length(string, end);
475 size_t ucwidth;
476 unsigned long unicode;
478 if (string + bytes > end)
479 break;
481 /* Change representation to figure out whether
482 * it is a single- or double-width character. */
484 unicode = utf8_to_unicode(string, bytes);
485 /* FIXME: Graceful handling of invalid Unicode character. */
486 if (!unicode)
487 break;
489 ucwidth = unicode_width(unicode, tab_size);
490 if (skip > 0) {
491 skip -= ucwidth <= skip ? ucwidth : skip;
492 *start += bytes;
494 *width += ucwidth;
495 if (*width > max_width) {
496 *trimmed = 1;
497 *width -= ucwidth;
498 if (reserve && *width == max_width) {
499 string -= last_bytes;
500 *width -= last_ucwidth;
502 break;
505 string += bytes;
506 last_bytes = ucwidth ? bytes : 0;
507 last_ucwidth = ucwidth;
510 return string - *start;
514 #define DATE_INFO \
515 DATE_(NO), \
516 DATE_(DEFAULT), \
517 DATE_(LOCAL), \
518 DATE_(RELATIVE), \
519 DATE_(SHORT)
521 enum date {
522 #define DATE_(name) DATE_##name
523 DATE_INFO
524 #undef DATE_
527 static const struct enum_map date_map[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
529 DATE_INFO
530 #undef DATE_
533 struct time {
534 time_t sec;
535 int tz;
538 static inline int timecmp(const struct time *t1, const struct time *t2)
540 return t1->sec - t2->sec;
543 static const char *
544 mkdate(const struct time *time, enum date date)
546 static char buf[DATE_COLS + 1];
547 static const struct enum_map reldate[] = {
548 { "second", 1, 60 * 2 },
549 { "minute", 60, 60 * 60 * 2 },
550 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
551 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
552 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
553 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
555 struct tm tm;
557 if (!date || !time || !time->sec)
558 return "";
560 if (date == DATE_RELATIVE) {
561 struct timeval now;
562 time_t date = time->sec + time->tz;
563 time_t seconds;
564 int i;
566 gettimeofday(&now, NULL);
567 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
568 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
569 if (seconds >= reldate[i].value)
570 continue;
572 seconds /= reldate[i].namelen;
573 if (!string_format(buf, "%ld %s%s %s",
574 seconds, reldate[i].name,
575 seconds > 1 ? "s" : "",
576 now.tv_sec >= date ? "ago" : "ahead"))
577 break;
578 return buf;
582 if (date == DATE_LOCAL) {
583 time_t date = time->sec + time->tz;
584 localtime_r(&date, &tm);
586 else {
587 gmtime_r(&time->sec, &tm);
589 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
593 #define AUTHOR_VALUES \
594 AUTHOR_(NO), \
595 AUTHOR_(FULL), \
596 AUTHOR_(ABBREVIATED)
598 enum author {
599 #define AUTHOR_(name) AUTHOR_##name
600 AUTHOR_VALUES,
601 #undef AUTHOR_
602 AUTHOR_DEFAULT = AUTHOR_FULL
605 static const struct enum_map author_map[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
607 AUTHOR_VALUES
608 #undef AUTHOR_
611 static const char *
612 get_author_initials(const char *author)
614 static char initials[AUTHOR_COLS * 6 + 1];
615 size_t pos = 0;
616 const char *end = strchr(author, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620 memset(initials, 0, sizeof(initials));
621 while (author < end) {
622 unsigned char bytes;
623 size_t i;
625 while (is_initial_sep(*author))
626 author++;
628 bytes = utf8_char_length(author, end);
629 if (bytes < sizeof(initials) - 1 - pos) {
630 while (bytes--) {
631 initials[pos++] = *author++;
635 for (i = pos; author < end && !is_initial_sep(*author); author++) {
636 if (i < sizeof(initials) - 1)
637 initials[i++] = *author;
640 initials[i++] = 0;
643 return initials;
647 static bool
648 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
650 int valuelen;
652 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
653 bool advance = cmd[valuelen] != 0;
655 cmd[valuelen] = 0;
656 argv[(*argc)++] = chomp_string(cmd);
657 cmd = chomp_string(cmd + valuelen + advance);
660 if (*argc < SIZEOF_ARG)
661 argv[*argc] = NULL;
662 return *argc < SIZEOF_ARG;
665 static bool
666 argv_from_env(const char **argv, const char *name)
668 char *env = argv ? getenv(name) : NULL;
669 int argc = 0;
671 if (env && *env)
672 env = strdup(env);
673 return !env || argv_from_string(argv, &argc, env);
676 static void
677 argv_free(const char *argv[])
679 int argc;
681 if (!argv)
682 return;
683 for (argc = 0; argv[argc]; argc++)
684 free((void *) argv[argc]);
685 argv[0] = NULL;
688 static size_t
689 argv_size(const char **argv)
691 int argc = 0;
693 while (argv && argv[argc])
694 argc++;
696 return argc;
699 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
701 static bool
702 argv_append(const char ***argv, const char *arg)
704 size_t argc = argv_size(*argv);
706 if (!argv_realloc(argv, argc, 2))
707 return FALSE;
709 (*argv)[argc++] = strdup(arg);
710 (*argv)[argc] = NULL;
711 return TRUE;
714 static bool
715 argv_append_array(const char ***dst_argv, const char *src_argv[])
717 int i;
719 for (i = 0; src_argv && src_argv[i]; i++)
720 if (!argv_append(dst_argv, src_argv[i]))
721 return FALSE;
722 return TRUE;
725 static bool
726 argv_copy(const char ***dst, const char *src[])
728 int argc;
730 for (argc = 0; src[argc]; argc++)
731 if (!argv_append(dst, src[argc]))
732 return FALSE;
733 return TRUE;
738 * Executing external commands.
741 enum io_type {
742 IO_FD, /* File descriptor based IO. */
743 IO_BG, /* Execute command in the background. */
744 IO_FG, /* Execute command with same std{in,out,err}. */
745 IO_RD, /* Read only fork+exec IO. */
746 IO_WR, /* Write only fork+exec IO. */
747 IO_AP, /* Append fork+exec output to file. */
750 struct io {
751 int pipe; /* Pipe end for reading or writing. */
752 pid_t pid; /* PID of spawned process. */
753 int error; /* Error status. */
754 char *buf; /* Read buffer. */
755 size_t bufalloc; /* Allocated buffer size. */
756 size_t bufsize; /* Buffer content size. */
757 char *bufpos; /* Current buffer position. */
758 unsigned int eof:1; /* Has end of file been reached. */
761 static void
762 io_init(struct io *io)
764 memset(io, 0, sizeof(*io));
765 io->pipe = -1;
768 static bool
769 io_open(struct io *io, const char *fmt, ...)
771 char name[SIZEOF_STR] = "";
772 bool fits;
773 va_list args;
775 io_init(io);
777 va_start(args, fmt);
778 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
779 va_end(args);
781 if (!fits) {
782 io->error = ENAMETOOLONG;
783 return FALSE;
785 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
786 if (io->pipe == -1)
787 io->error = errno;
788 return io->pipe != -1;
791 static bool
792 io_kill(struct io *io)
794 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
797 static bool
798 io_done(struct io *io)
800 pid_t pid = io->pid;
802 if (io->pipe != -1)
803 close(io->pipe);
804 free(io->buf);
805 io_init(io);
807 while (pid > 0) {
808 int status;
809 pid_t waiting = waitpid(pid, &status, 0);
811 if (waiting < 0) {
812 if (errno == EINTR)
813 continue;
814 io->error = errno;
815 return FALSE;
818 return waiting == pid &&
819 !WIFSIGNALED(status) &&
820 WIFEXITED(status) &&
821 !WEXITSTATUS(status);
824 return TRUE;
827 static bool
828 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
830 int pipefds[2] = { -1, -1 };
831 va_list args;
833 io_init(io);
835 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
836 io->error = errno;
837 return FALSE;
838 } else if (type == IO_AP) {
839 va_start(args, argv);
840 pipefds[1] = va_arg(args, int);
841 va_end(args);
844 if ((io->pid = fork())) {
845 if (io->pid == -1)
846 io->error = errno;
847 if (pipefds[!(type == IO_WR)] != -1)
848 close(pipefds[!(type == IO_WR)]);
849 if (io->pid != -1) {
850 io->pipe = pipefds[!!(type == IO_WR)];
851 return TRUE;
854 } else {
855 if (type != IO_FG) {
856 int devnull = open("/dev/null", O_RDWR);
857 int readfd = type == IO_WR ? pipefds[0] : devnull;
858 int writefd = (type == IO_RD || type == IO_AP)
859 ? pipefds[1] : devnull;
861 dup2(readfd, STDIN_FILENO);
862 dup2(writefd, STDOUT_FILENO);
863 dup2(devnull, STDERR_FILENO);
865 close(devnull);
866 if (pipefds[0] != -1)
867 close(pipefds[0]);
868 if (pipefds[1] != -1)
869 close(pipefds[1]);
872 if (dir && *dir && chdir(dir) == -1)
873 exit(errno);
875 execvp(argv[0], (char *const*) argv);
876 exit(errno);
879 if (pipefds[!!(type == IO_WR)] != -1)
880 close(pipefds[!!(type == IO_WR)]);
881 return FALSE;
884 static bool
885 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
887 struct io io;
889 return io_run(&io, type, dir, argv, fd) && io_done(&io);
892 static bool
893 io_run_bg(const char **argv)
895 return io_complete(IO_BG, argv, NULL, -1);
898 static bool
899 io_run_fg(const char **argv, const char *dir)
901 return io_complete(IO_FG, argv, dir, -1);
904 static bool
905 io_run_append(const char **argv, int fd)
907 return io_complete(IO_AP, argv, NULL, fd);
910 static bool
911 io_eof(struct io *io)
913 return io->eof;
916 static int
917 io_error(struct io *io)
919 return io->error;
922 static char *
923 io_strerror(struct io *io)
925 return strerror(io->error);
928 static bool
929 io_can_read(struct io *io)
931 struct timeval tv = { 0, 500 };
932 fd_set fds;
934 FD_ZERO(&fds);
935 FD_SET(io->pipe, &fds);
937 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
940 static ssize_t
941 io_read(struct io *io, void *buf, size_t bufsize)
943 do {
944 ssize_t readsize = read(io->pipe, buf, bufsize);
946 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
947 continue;
948 else if (readsize == -1)
949 io->error = errno;
950 else if (readsize == 0)
951 io->eof = 1;
952 return readsize;
953 } while (1);
956 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
958 static char *
959 io_get(struct io *io, int c, bool can_read)
961 char *eol;
962 ssize_t readsize;
964 while (TRUE) {
965 if (io->bufsize > 0) {
966 eol = memchr(io->bufpos, c, io->bufsize);
967 if (eol) {
968 char *line = io->bufpos;
970 *eol = 0;
971 io->bufpos = eol + 1;
972 io->bufsize -= io->bufpos - line;
973 return line;
977 if (io_eof(io)) {
978 if (io->bufsize) {
979 io->bufpos[io->bufsize] = 0;
980 io->bufsize = 0;
981 return io->bufpos;
983 return NULL;
986 if (!can_read)
987 return NULL;
989 if (io->bufsize > 0 && io->bufpos > io->buf)
990 memmove(io->buf, io->bufpos, io->bufsize);
992 if (io->bufalloc == io->bufsize) {
993 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
994 return NULL;
995 io->bufalloc += BUFSIZ;
998 io->bufpos = io->buf;
999 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1000 if (io_error(io))
1001 return NULL;
1002 io->bufsize += readsize;
1006 static bool
1007 io_write(struct io *io, const void *buf, size_t bufsize)
1009 size_t written = 0;
1011 while (!io_error(io) && written < bufsize) {
1012 ssize_t size;
1014 size = write(io->pipe, buf + written, bufsize - written);
1015 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1016 continue;
1017 else if (size == -1)
1018 io->error = errno;
1019 else
1020 written += size;
1023 return written == bufsize;
1026 static bool
1027 io_read_buf(struct io *io, char buf[], size_t bufsize)
1029 char *result = io_get(io, '\n', TRUE);
1031 if (result) {
1032 result = chomp_string(result);
1033 string_ncopy_do(buf, bufsize, result, strlen(result));
1036 return io_done(io) && result;
1039 static bool
1040 io_run_buf(const char **argv, char buf[], size_t bufsize)
1042 struct io io;
1044 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1047 static int
1048 io_load(struct io *io, const char *separators,
1049 int (*read_property)(char *, size_t, char *, size_t))
1051 char *name;
1052 int state = OK;
1054 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1055 char *value;
1056 size_t namelen;
1057 size_t valuelen;
1059 name = chomp_string(name);
1060 namelen = strcspn(name, separators);
1062 if (name[namelen]) {
1063 name[namelen] = 0;
1064 value = chomp_string(name + namelen + 1);
1065 valuelen = strlen(value);
1067 } else {
1068 value = "";
1069 valuelen = 0;
1072 state = read_property(name, namelen, value, valuelen);
1075 if (state != ERR && io_error(io))
1076 state = ERR;
1077 io_done(io);
1079 return state;
1082 static int
1083 io_run_load(const char **argv, const char *separators,
1084 int (*read_property)(char *, size_t, char *, size_t))
1086 struct io io;
1088 if (!io_run(&io, IO_RD, NULL, argv))
1089 return ERR;
1090 return io_load(&io, separators, read_property);
1095 * User requests
1098 #define REQ_INFO \
1099 /* XXX: Keep the view request first and in sync with views[]. */ \
1100 REQ_GROUP("View switching") \
1101 REQ_(VIEW_MAIN, "Show main view"), \
1102 REQ_(VIEW_DIFF, "Show diff view"), \
1103 REQ_(VIEW_LOG, "Show log view"), \
1104 REQ_(VIEW_TREE, "Show tree view"), \
1105 REQ_(VIEW_BLOB, "Show blob view"), \
1106 REQ_(VIEW_BLAME, "Show blame view"), \
1107 REQ_(VIEW_BRANCH, "Show branch view"), \
1108 REQ_(VIEW_HELP, "Show help page"), \
1109 REQ_(VIEW_PAGER, "Show pager view"), \
1110 REQ_(VIEW_STATUS, "Show status view"), \
1111 REQ_(VIEW_STAGE, "Show stage view"), \
1113 REQ_GROUP("View manipulation") \
1114 REQ_(ENTER, "Enter current line and scroll"), \
1115 REQ_(NEXT, "Move to next"), \
1116 REQ_(PREVIOUS, "Move to previous"), \
1117 REQ_(PARENT, "Move to parent"), \
1118 REQ_(VIEW_NEXT, "Move focus to next view"), \
1119 REQ_(REFRESH, "Reload and refresh"), \
1120 REQ_(MAXIMIZE, "Maximize the current view"), \
1121 REQ_(VIEW_CLOSE, "Close the current view"), \
1122 REQ_(QUIT, "Close all views and quit"), \
1124 REQ_GROUP("View specific requests") \
1125 REQ_(STATUS_UPDATE, "Update file status"), \
1126 REQ_(STATUS_REVERT, "Revert file changes"), \
1127 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1128 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1130 REQ_GROUP("Cursor navigation") \
1131 REQ_(MOVE_UP, "Move cursor one line up"), \
1132 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1133 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1134 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1135 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1136 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1138 REQ_GROUP("Scrolling") \
1139 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1140 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1141 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1142 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1143 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1144 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1145 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1147 REQ_GROUP("Searching") \
1148 REQ_(SEARCH, "Search the view"), \
1149 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1150 REQ_(FIND_NEXT, "Find next search match"), \
1151 REQ_(FIND_PREV, "Find previous search match"), \
1153 REQ_GROUP("Option manipulation") \
1154 REQ_(OPTIONS, "Open option menu"), \
1155 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1156 REQ_(TOGGLE_DATE, "Toggle date display"), \
1157 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1158 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1159 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1160 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1161 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1163 REQ_GROUP("Misc") \
1164 REQ_(PROMPT, "Bring up the prompt"), \
1165 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1166 REQ_(SHOW_VERSION, "Show version information"), \
1167 REQ_(STOP_LOADING, "Stop all loading views"), \
1168 REQ_(EDIT, "Open in editor"), \
1169 REQ_(NONE, "Do nothing")
1172 /* User action requests. */
1173 enum request {
1174 #define REQ_GROUP(help)
1175 #define REQ_(req, help) REQ_##req
1177 /* Offset all requests to avoid conflicts with ncurses getch values. */
1178 REQ_UNKNOWN = KEY_MAX + 1,
1179 REQ_OFFSET,
1180 REQ_INFO
1182 #undef REQ_GROUP
1183 #undef REQ_
1186 struct request_info {
1187 enum request request;
1188 const char *name;
1189 int namelen;
1190 const char *help;
1193 static const struct request_info req_info[] = {
1194 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1195 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1196 REQ_INFO
1197 #undef REQ_GROUP
1198 #undef REQ_
1201 static enum request
1202 get_request(const char *name)
1204 int namelen = strlen(name);
1205 int i;
1207 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1208 if (enum_equals(req_info[i], name, namelen))
1209 return req_info[i].request;
1211 return REQ_UNKNOWN;
1216 * Options
1219 /* Option and state variables. */
1220 static enum date opt_date = DATE_DEFAULT;
1221 static enum author opt_author = AUTHOR_DEFAULT;
1222 static bool opt_line_number = FALSE;
1223 static bool opt_line_graphics = TRUE;
1224 static bool opt_rev_graph = FALSE;
1225 static bool opt_show_refs = TRUE;
1226 static bool opt_untracked_dirs_content = TRUE;
1227 static int opt_num_interval = 5;
1228 static double opt_hscroll = 0.50;
1229 static double opt_scale_split_view = 2.0 / 3.0;
1230 static int opt_tab_size = 8;
1231 static int opt_author_cols = AUTHOR_COLS;
1232 static char opt_path[SIZEOF_STR] = "";
1233 static char opt_file[SIZEOF_STR] = "";
1234 static char opt_ref[SIZEOF_REF] = "";
1235 static char opt_head[SIZEOF_REF] = "";
1236 static char opt_remote[SIZEOF_REF] = "";
1237 static char opt_encoding[20] = "UTF-8";
1238 static iconv_t opt_iconv_in = ICONV_NONE;
1239 static iconv_t opt_iconv_out = ICONV_NONE;
1240 static char opt_search[SIZEOF_STR] = "";
1241 static char opt_cdup[SIZEOF_STR] = "";
1242 static char opt_prefix[SIZEOF_STR] = "";
1243 static char opt_git_dir[SIZEOF_STR] = "";
1244 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1245 static char opt_editor[SIZEOF_STR] = "";
1246 static FILE *opt_tty = NULL;
1247 static const char **opt_diff_args = NULL;
1248 static const char **opt_rev_args = NULL;
1249 static const char **opt_file_args = NULL;
1251 #define is_initial_commit() (!get_ref_head())
1252 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1256 * Line-oriented content detection.
1259 #define LINE_INFO \
1260 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1274 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1275 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1276 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1281 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1283 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1285 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1291 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1292 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1293 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1294 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1295 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1298 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1299 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1301 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1307 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1308 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1309 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1310 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1311 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1312 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1313 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1315 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1317 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1318 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1320 enum line_type {
1321 #define LINE(type, line, fg, bg, attr) \
1322 LINE_##type
1323 LINE_INFO,
1324 LINE_NONE
1325 #undef LINE
1328 struct line_info {
1329 const char *name; /* Option name. */
1330 int namelen; /* Size of option name. */
1331 const char *line; /* The start of line to match. */
1332 int linelen; /* Size of string to match. */
1333 int fg, bg, attr; /* Color and text attributes for the lines. */
1336 static struct line_info line_info[] = {
1337 #define LINE(type, line, fg, bg, attr) \
1338 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1339 LINE_INFO
1340 #undef LINE
1343 static enum line_type
1344 get_line_type(const char *line)
1346 int linelen = strlen(line);
1347 enum line_type type;
1349 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1350 /* Case insensitive search matches Signed-off-by lines better. */
1351 if (linelen >= line_info[type].linelen &&
1352 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1353 return type;
1355 return LINE_DEFAULT;
1358 static inline int
1359 get_line_attr(enum line_type type)
1361 assert(type < ARRAY_SIZE(line_info));
1362 return COLOR_PAIR(type) | line_info[type].attr;
1365 static struct line_info *
1366 get_line_info(const char *name)
1368 size_t namelen = strlen(name);
1369 enum line_type type;
1371 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1372 if (enum_equals(line_info[type], name, namelen))
1373 return &line_info[type];
1375 return NULL;
1378 static void
1379 init_colors(void)
1381 int default_bg = line_info[LINE_DEFAULT].bg;
1382 int default_fg = line_info[LINE_DEFAULT].fg;
1383 enum line_type type;
1385 start_color();
1387 if (assume_default_colors(default_fg, default_bg) == ERR) {
1388 default_bg = COLOR_BLACK;
1389 default_fg = COLOR_WHITE;
1392 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1393 struct line_info *info = &line_info[type];
1394 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1395 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1397 init_pair(type, fg, bg);
1401 struct line {
1402 enum line_type type;
1404 /* State flags */
1405 unsigned int selected:1;
1406 unsigned int dirty:1;
1407 unsigned int cleareol:1;
1408 unsigned int other:16;
1410 void *data; /* User data */
1415 * Keys
1418 struct keybinding {
1419 int alias;
1420 enum request request;
1423 static struct keybinding default_keybindings[] = {
1424 /* View switching */
1425 { 'm', REQ_VIEW_MAIN },
1426 { 'd', REQ_VIEW_DIFF },
1427 { 'l', REQ_VIEW_LOG },
1428 { 't', REQ_VIEW_TREE },
1429 { 'f', REQ_VIEW_BLOB },
1430 { 'B', REQ_VIEW_BLAME },
1431 { 'H', REQ_VIEW_BRANCH },
1432 { 'p', REQ_VIEW_PAGER },
1433 { 'h', REQ_VIEW_HELP },
1434 { 'S', REQ_VIEW_STATUS },
1435 { 'c', REQ_VIEW_STAGE },
1437 /* View manipulation */
1438 { 'q', REQ_VIEW_CLOSE },
1439 { KEY_TAB, REQ_VIEW_NEXT },
1440 { KEY_RETURN, REQ_ENTER },
1441 { KEY_UP, REQ_PREVIOUS },
1442 { KEY_CTL('P'), REQ_PREVIOUS },
1443 { KEY_DOWN, REQ_NEXT },
1444 { KEY_CTL('N'), REQ_NEXT },
1445 { 'R', REQ_REFRESH },
1446 { KEY_F(5), REQ_REFRESH },
1447 { 'O', REQ_MAXIMIZE },
1449 /* Cursor navigation */
1450 { 'k', REQ_MOVE_UP },
1451 { 'j', REQ_MOVE_DOWN },
1452 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1453 { KEY_END, REQ_MOVE_LAST_LINE },
1454 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1455 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1456 { ' ', REQ_MOVE_PAGE_DOWN },
1457 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1458 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1459 { 'b', REQ_MOVE_PAGE_UP },
1460 { '-', REQ_MOVE_PAGE_UP },
1462 /* Scrolling */
1463 { '|', REQ_SCROLL_FIRST_COL },
1464 { KEY_LEFT, REQ_SCROLL_LEFT },
1465 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1466 { KEY_IC, REQ_SCROLL_LINE_UP },
1467 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1468 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1469 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1470 { 'w', REQ_SCROLL_PAGE_UP },
1471 { 's', REQ_SCROLL_PAGE_DOWN },
1473 /* Searching */
1474 { '/', REQ_SEARCH },
1475 { '?', REQ_SEARCH_BACK },
1476 { 'n', REQ_FIND_NEXT },
1477 { 'N', REQ_FIND_PREV },
1479 /* Misc */
1480 { 'Q', REQ_QUIT },
1481 { 'z', REQ_STOP_LOADING },
1482 { 'v', REQ_SHOW_VERSION },
1483 { 'r', REQ_SCREEN_REDRAW },
1484 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1485 { 'o', REQ_OPTIONS },
1486 { '.', REQ_TOGGLE_LINENO },
1487 { 'D', REQ_TOGGLE_DATE },
1488 { 'A', REQ_TOGGLE_AUTHOR },
1489 { 'g', REQ_TOGGLE_REV_GRAPH },
1490 { 'F', REQ_TOGGLE_REFS },
1491 { 'I', REQ_TOGGLE_SORT_ORDER },
1492 { 'i', REQ_TOGGLE_SORT_FIELD },
1493 { ':', REQ_PROMPT },
1494 { 'u', REQ_STATUS_UPDATE },
1495 { '!', REQ_STATUS_REVERT },
1496 { 'M', REQ_STATUS_MERGE },
1497 { '@', REQ_STAGE_NEXT },
1498 { ',', REQ_PARENT },
1499 { 'e', REQ_EDIT },
1502 #define KEYMAP_INFO \
1503 KEYMAP_(GENERIC), \
1504 KEYMAP_(MAIN), \
1505 KEYMAP_(DIFF), \
1506 KEYMAP_(LOG), \
1507 KEYMAP_(TREE), \
1508 KEYMAP_(BLOB), \
1509 KEYMAP_(BLAME), \
1510 KEYMAP_(BRANCH), \
1511 KEYMAP_(PAGER), \
1512 KEYMAP_(HELP), \
1513 KEYMAP_(STATUS), \
1514 KEYMAP_(STAGE)
1516 enum keymap {
1517 #define KEYMAP_(name) KEYMAP_##name
1518 KEYMAP_INFO
1519 #undef KEYMAP_
1522 static const struct enum_map keymap_table[] = {
1523 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1524 KEYMAP_INFO
1525 #undef KEYMAP_
1528 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1530 struct keybinding_table {
1531 struct keybinding *data;
1532 size_t size;
1535 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1537 static void
1538 add_keybinding(enum keymap keymap, enum request request, int key)
1540 struct keybinding_table *table = &keybindings[keymap];
1541 size_t i;
1543 for (i = 0; i < keybindings[keymap].size; i++) {
1544 if (keybindings[keymap].data[i].alias == key) {
1545 keybindings[keymap].data[i].request = request;
1546 return;
1550 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1551 if (!table->data)
1552 die("Failed to allocate keybinding");
1553 table->data[table->size].alias = key;
1554 table->data[table->size++].request = request;
1556 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1557 int i;
1559 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1560 if (default_keybindings[i].alias == key)
1561 default_keybindings[i].request = REQ_NONE;
1565 /* Looks for a key binding first in the given map, then in the generic map, and
1566 * lastly in the default keybindings. */
1567 static enum request
1568 get_keybinding(enum keymap keymap, int key)
1570 size_t i;
1572 for (i = 0; i < keybindings[keymap].size; i++)
1573 if (keybindings[keymap].data[i].alias == key)
1574 return keybindings[keymap].data[i].request;
1576 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1577 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1578 return keybindings[KEYMAP_GENERIC].data[i].request;
1580 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1581 if (default_keybindings[i].alias == key)
1582 return default_keybindings[i].request;
1584 return (enum request) key;
1588 struct key {
1589 const char *name;
1590 int value;
1593 static const struct key key_table[] = {
1594 { "Enter", KEY_RETURN },
1595 { "Space", ' ' },
1596 { "Backspace", KEY_BACKSPACE },
1597 { "Tab", KEY_TAB },
1598 { "Escape", KEY_ESC },
1599 { "Left", KEY_LEFT },
1600 { "Right", KEY_RIGHT },
1601 { "Up", KEY_UP },
1602 { "Down", KEY_DOWN },
1603 { "Insert", KEY_IC },
1604 { "Delete", KEY_DC },
1605 { "Hash", '#' },
1606 { "Home", KEY_HOME },
1607 { "End", KEY_END },
1608 { "PageUp", KEY_PPAGE },
1609 { "PageDown", KEY_NPAGE },
1610 { "F1", KEY_F(1) },
1611 { "F2", KEY_F(2) },
1612 { "F3", KEY_F(3) },
1613 { "F4", KEY_F(4) },
1614 { "F5", KEY_F(5) },
1615 { "F6", KEY_F(6) },
1616 { "F7", KEY_F(7) },
1617 { "F8", KEY_F(8) },
1618 { "F9", KEY_F(9) },
1619 { "F10", KEY_F(10) },
1620 { "F11", KEY_F(11) },
1621 { "F12", KEY_F(12) },
1624 static int
1625 get_key_value(const char *name)
1627 int i;
1629 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1630 if (!strcasecmp(key_table[i].name, name))
1631 return key_table[i].value;
1633 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1634 return (int)name[1] & 0x1f;
1635 if (strlen(name) == 1 && isprint(*name))
1636 return (int) *name;
1637 return ERR;
1640 static const char *
1641 get_key_name(int key_value)
1643 static char key_char[] = "'X'\0";
1644 const char *seq = NULL;
1645 int key;
1647 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1648 if (key_table[key].value == key_value)
1649 seq = key_table[key].name;
1651 if (seq == NULL && key_value < 0x7f) {
1652 char *s = key_char + 1;
1654 if (key_value >= 0x20) {
1655 *s++ = key_value;
1656 } else {
1657 *s++ = '^';
1658 *s++ = 0x40 | (key_value & 0x1f);
1660 *s++ = '\'';
1661 *s++ = '\0';
1662 seq = key_char;
1665 return seq ? seq : "(no key)";
1668 static bool
1669 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1671 const char *sep = *pos > 0 ? ", " : "";
1672 const char *keyname = get_key_name(keybinding->alias);
1674 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1677 static bool
1678 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1679 enum keymap keymap, bool all)
1681 int i;
1683 for (i = 0; i < keybindings[keymap].size; i++) {
1684 if (keybindings[keymap].data[i].request == request) {
1685 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1686 return FALSE;
1687 if (!all)
1688 break;
1692 return TRUE;
1695 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1697 static const char *
1698 get_keys(enum keymap keymap, enum request request, bool all)
1700 static char buf[BUFSIZ];
1701 size_t pos = 0;
1702 int i;
1704 buf[pos] = 0;
1706 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1707 return "Too many keybindings!";
1708 if (pos > 0 && !all)
1709 return buf;
1711 if (keymap != KEYMAP_GENERIC) {
1712 /* Only the generic keymap includes the default keybindings when
1713 * listing all keys. */
1714 if (all)
1715 return buf;
1717 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1718 return "Too many keybindings!";
1719 if (pos)
1720 return buf;
1723 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1724 if (default_keybindings[i].request == request) {
1725 if (!append_key(buf, &pos, &default_keybindings[i]))
1726 return "Too many keybindings!";
1727 if (!all)
1728 return buf;
1732 return buf;
1735 struct run_request {
1736 enum keymap keymap;
1737 int key;
1738 const char **argv;
1741 static struct run_request *run_request;
1742 static size_t run_requests;
1744 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1746 static enum request
1747 add_run_request(enum keymap keymap, int key, const char **argv)
1749 struct run_request *req;
1751 if (!realloc_run_requests(&run_request, run_requests, 1))
1752 return REQ_NONE;
1754 req = &run_request[run_requests];
1755 req->keymap = keymap;
1756 req->key = key;
1757 req->argv = NULL;
1759 if (!argv_copy(&req->argv, argv))
1760 return REQ_NONE;
1762 return REQ_NONE + ++run_requests;
1765 static struct run_request *
1766 get_run_request(enum request request)
1768 if (request <= REQ_NONE)
1769 return NULL;
1770 return &run_request[request - REQ_NONE - 1];
1773 static void
1774 add_builtin_run_requests(void)
1776 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1777 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1778 const char *commit[] = { "git", "commit", NULL };
1779 const char *gc[] = { "git", "gc", NULL };
1780 struct run_request reqs[] = {
1781 { KEYMAP_MAIN, 'C', cherry_pick },
1782 { KEYMAP_STATUS, 'C', commit },
1783 { KEYMAP_BRANCH, 'C', checkout },
1784 { KEYMAP_GENERIC, 'G', gc },
1786 int i;
1788 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1789 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1791 if (req != reqs[i].key)
1792 continue;
1793 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1794 if (req != REQ_NONE)
1795 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1800 * User config file handling.
1803 static int config_lineno;
1804 static bool config_errors;
1805 static const char *config_msg;
1807 static const struct enum_map color_map[] = {
1808 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1809 COLOR_MAP(DEFAULT),
1810 COLOR_MAP(BLACK),
1811 COLOR_MAP(BLUE),
1812 COLOR_MAP(CYAN),
1813 COLOR_MAP(GREEN),
1814 COLOR_MAP(MAGENTA),
1815 COLOR_MAP(RED),
1816 COLOR_MAP(WHITE),
1817 COLOR_MAP(YELLOW),
1820 static const struct enum_map attr_map[] = {
1821 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1822 ATTR_MAP(NORMAL),
1823 ATTR_MAP(BLINK),
1824 ATTR_MAP(BOLD),
1825 ATTR_MAP(DIM),
1826 ATTR_MAP(REVERSE),
1827 ATTR_MAP(STANDOUT),
1828 ATTR_MAP(UNDERLINE),
1831 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1833 static int parse_step(double *opt, const char *arg)
1835 *opt = atoi(arg);
1836 if (!strchr(arg, '%'))
1837 return OK;
1839 /* "Shift down" so 100% and 1 does not conflict. */
1840 *opt = (*opt - 1) / 100;
1841 if (*opt >= 1.0) {
1842 *opt = 0.99;
1843 config_msg = "Step value larger than 100%";
1844 return ERR;
1846 if (*opt < 0.0) {
1847 *opt = 1;
1848 config_msg = "Invalid step value";
1849 return ERR;
1851 return OK;
1854 static int
1855 parse_int(int *opt, const char *arg, int min, int max)
1857 int value = atoi(arg);
1859 if (min <= value && value <= max) {
1860 *opt = value;
1861 return OK;
1864 config_msg = "Integer value out of bound";
1865 return ERR;
1868 static bool
1869 set_color(int *color, const char *name)
1871 if (map_enum(color, color_map, name))
1872 return TRUE;
1873 if (!prefixcmp(name, "color"))
1874 return parse_int(color, name + 5, 0, 255) == OK;
1875 return FALSE;
1878 /* Wants: object fgcolor bgcolor [attribute] */
1879 static int
1880 option_color_command(int argc, const char *argv[])
1882 struct line_info *info;
1884 if (argc < 3) {
1885 config_msg = "Wrong number of arguments given to color command";
1886 return ERR;
1889 info = get_line_info(argv[0]);
1890 if (!info) {
1891 static const struct enum_map obsolete[] = {
1892 ENUM_MAP("main-delim", LINE_DELIMITER),
1893 ENUM_MAP("main-date", LINE_DATE),
1894 ENUM_MAP("main-author", LINE_AUTHOR),
1896 int index;
1898 if (!map_enum(&index, obsolete, argv[0])) {
1899 config_msg = "Unknown color name";
1900 return ERR;
1902 info = &line_info[index];
1905 if (!set_color(&info->fg, argv[1]) ||
1906 !set_color(&info->bg, argv[2])) {
1907 config_msg = "Unknown color";
1908 return ERR;
1911 info->attr = 0;
1912 while (argc-- > 3) {
1913 int attr;
1915 if (!set_attribute(&attr, argv[argc])) {
1916 config_msg = "Unknown attribute";
1917 return ERR;
1919 info->attr |= attr;
1922 return OK;
1925 static int parse_bool(bool *opt, const char *arg)
1927 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1928 ? TRUE : FALSE;
1929 return OK;
1932 static int parse_enum_do(unsigned int *opt, const char *arg,
1933 const struct enum_map *map, size_t map_size)
1935 bool is_true;
1937 assert(map_size > 1);
1939 if (map_enum_do(map, map_size, (int *) opt, arg))
1940 return OK;
1942 if (parse_bool(&is_true, arg) != OK)
1943 return ERR;
1945 *opt = is_true ? map[1].value : map[0].value;
1946 return OK;
1949 #define parse_enum(opt, arg, map) \
1950 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1952 static int
1953 parse_string(char *opt, const char *arg, size_t optsize)
1955 int arglen = strlen(arg);
1957 switch (arg[0]) {
1958 case '\"':
1959 case '\'':
1960 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1961 config_msg = "Unmatched quotation";
1962 return ERR;
1964 arg += 1; arglen -= 2;
1965 default:
1966 string_ncopy_do(opt, optsize, arg, arglen);
1967 return OK;
1971 /* Wants: name = value */
1972 static int
1973 option_set_command(int argc, const char *argv[])
1975 if (argc != 3) {
1976 config_msg = "Wrong number of arguments given to set command";
1977 return ERR;
1980 if (strcmp(argv[1], "=")) {
1981 config_msg = "No value assigned";
1982 return ERR;
1985 if (!strcmp(argv[0], "show-author"))
1986 return parse_enum(&opt_author, argv[2], author_map);
1988 if (!strcmp(argv[0], "show-date"))
1989 return parse_enum(&opt_date, argv[2], date_map);
1991 if (!strcmp(argv[0], "show-rev-graph"))
1992 return parse_bool(&opt_rev_graph, argv[2]);
1994 if (!strcmp(argv[0], "show-refs"))
1995 return parse_bool(&opt_show_refs, argv[2]);
1997 if (!strcmp(argv[0], "show-line-numbers"))
1998 return parse_bool(&opt_line_number, argv[2]);
2000 if (!strcmp(argv[0], "line-graphics"))
2001 return parse_bool(&opt_line_graphics, argv[2]);
2003 if (!strcmp(argv[0], "line-number-interval"))
2004 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2006 if (!strcmp(argv[0], "author-width"))
2007 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2009 if (!strcmp(argv[0], "horizontal-scroll"))
2010 return parse_step(&opt_hscroll, argv[2]);
2012 if (!strcmp(argv[0], "split-view-height"))
2013 return parse_step(&opt_scale_split_view, argv[2]);
2015 if (!strcmp(argv[0], "tab-size"))
2016 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2018 if (!strcmp(argv[0], "commit-encoding"))
2019 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2021 if (!strcmp(argv[0], "status-untracked-dirs"))
2022 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2024 config_msg = "Unknown variable name";
2025 return ERR;
2028 /* Wants: mode request key */
2029 static int
2030 option_bind_command(int argc, const char *argv[])
2032 enum request request;
2033 int keymap = -1;
2034 int key;
2036 if (argc < 3) {
2037 config_msg = "Wrong number of arguments given to bind command";
2038 return ERR;
2041 if (!set_keymap(&keymap, argv[0])) {
2042 config_msg = "Unknown key map";
2043 return ERR;
2046 key = get_key_value(argv[1]);
2047 if (key == ERR) {
2048 config_msg = "Unknown key";
2049 return ERR;
2052 request = get_request(argv[2]);
2053 if (request == REQ_UNKNOWN) {
2054 static const struct enum_map obsolete[] = {
2055 ENUM_MAP("cherry-pick", REQ_NONE),
2056 ENUM_MAP("screen-resize", REQ_NONE),
2057 ENUM_MAP("tree-parent", REQ_PARENT),
2059 int alias;
2061 if (map_enum(&alias, obsolete, argv[2])) {
2062 if (alias != REQ_NONE)
2063 add_keybinding(keymap, alias, key);
2064 config_msg = "Obsolete request name";
2065 return ERR;
2068 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2069 request = add_run_request(keymap, key, argv + 2);
2070 if (request == REQ_UNKNOWN) {
2071 config_msg = "Unknown request name";
2072 return ERR;
2075 add_keybinding(keymap, request, key);
2077 return OK;
2080 static int
2081 set_option(const char *opt, char *value)
2083 const char *argv[SIZEOF_ARG];
2084 int argc = 0;
2086 if (!argv_from_string(argv, &argc, value)) {
2087 config_msg = "Too many option arguments";
2088 return ERR;
2091 if (!strcmp(opt, "color"))
2092 return option_color_command(argc, argv);
2094 if (!strcmp(opt, "set"))
2095 return option_set_command(argc, argv);
2097 if (!strcmp(opt, "bind"))
2098 return option_bind_command(argc, argv);
2100 config_msg = "Unknown option command";
2101 return ERR;
2104 static int
2105 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2107 int status = OK;
2109 config_lineno++;
2110 config_msg = "Internal error";
2112 /* Check for comment markers, since read_properties() will
2113 * only ensure opt and value are split at first " \t". */
2114 optlen = strcspn(opt, "#");
2115 if (optlen == 0)
2116 return OK;
2118 if (opt[optlen] != 0) {
2119 config_msg = "No option value";
2120 status = ERR;
2122 } else {
2123 /* Look for comment endings in the value. */
2124 size_t len = strcspn(value, "#");
2126 if (len < valuelen) {
2127 valuelen = len;
2128 value[valuelen] = 0;
2131 status = set_option(opt, value);
2134 if (status == ERR) {
2135 warn("Error on line %d, near '%.*s': %s",
2136 config_lineno, (int) optlen, opt, config_msg);
2137 config_errors = TRUE;
2140 /* Always keep going if errors are encountered. */
2141 return OK;
2144 static void
2145 load_option_file(const char *path)
2147 struct io io;
2149 /* It's OK that the file doesn't exist. */
2150 if (!io_open(&io, "%s", path))
2151 return;
2153 config_lineno = 0;
2154 config_errors = FALSE;
2156 if (io_load(&io, " \t", read_option) == ERR ||
2157 config_errors == TRUE)
2158 warn("Errors while loading %s.", path);
2161 static int
2162 load_options(void)
2164 const char *home = getenv("HOME");
2165 const char *tigrc_user = getenv("TIGRC_USER");
2166 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2167 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2168 char buf[SIZEOF_STR];
2170 if (!tigrc_system)
2171 tigrc_system = SYSCONFDIR "/tigrc";
2172 load_option_file(tigrc_system);
2174 if (!tigrc_user) {
2175 if (!home || !string_format(buf, "%s/.tigrc", home))
2176 return ERR;
2177 tigrc_user = buf;
2179 load_option_file(tigrc_user);
2181 /* Add _after_ loading config files to avoid adding run requests
2182 * that conflict with keybindings. */
2183 add_builtin_run_requests();
2185 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2186 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2187 int argc = 0;
2189 if (!string_format(buf, "%s", tig_diff_opts) ||
2190 !argv_from_string(diff_opts, &argc, buf))
2191 die("TIG_DIFF_OPTS contains too many arguments");
2192 else if (!argv_copy(&opt_diff_args, diff_opts))
2193 die("Failed to format TIG_DIFF_OPTS arguments");
2196 return OK;
2201 * The viewer
2204 struct view;
2205 struct view_ops;
2207 /* The display array of active views and the index of the current view. */
2208 static struct view *display[2];
2209 static unsigned int current_view;
2211 #define foreach_displayed_view(view, i) \
2212 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2214 #define displayed_views() (display[1] != NULL ? 2 : 1)
2216 /* Current head and commit ID */
2217 static char ref_blob[SIZEOF_REF] = "";
2218 static char ref_commit[SIZEOF_REF] = "HEAD";
2219 static char ref_head[SIZEOF_REF] = "HEAD";
2220 static char ref_branch[SIZEOF_REF] = "";
2222 enum view_type {
2223 VIEW_MAIN,
2224 VIEW_DIFF,
2225 VIEW_LOG,
2226 VIEW_TREE,
2227 VIEW_BLOB,
2228 VIEW_BLAME,
2229 VIEW_BRANCH,
2230 VIEW_HELP,
2231 VIEW_PAGER,
2232 VIEW_STATUS,
2233 VIEW_STAGE,
2236 struct view {
2237 enum view_type type; /* View type */
2238 const char *name; /* View name */
2239 const char *cmd_env; /* Command line set via environment */
2240 const char *id; /* Points to either of ref_{head,commit,blob} */
2242 struct view_ops *ops; /* View operations */
2244 enum keymap keymap; /* What keymap does this view have */
2245 bool git_dir; /* Whether the view requires a git directory. */
2247 char ref[SIZEOF_REF]; /* Hovered commit reference */
2248 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2250 int height, width; /* The width and height of the main window */
2251 WINDOW *win; /* The main window */
2252 WINDOW *title; /* The title window living below the main window */
2254 /* Navigation */
2255 unsigned long offset; /* Offset of the window top */
2256 unsigned long yoffset; /* Offset from the window side. */
2257 unsigned long lineno; /* Current line number */
2258 unsigned long p_offset; /* Previous offset of the window top */
2259 unsigned long p_yoffset;/* Previous offset from the window side */
2260 unsigned long p_lineno; /* Previous current line number */
2261 bool p_restore; /* Should the previous position be restored. */
2263 /* Searching */
2264 char grep[SIZEOF_STR]; /* Search string */
2265 regex_t *regex; /* Pre-compiled regexp */
2267 /* If non-NULL, points to the view that opened this view. If this view
2268 * is closed tig will switch back to the parent view. */
2269 struct view *parent;
2270 struct view *prev;
2272 /* Buffering */
2273 size_t lines; /* Total number of lines */
2274 struct line *line; /* Line index */
2275 unsigned int digits; /* Number of digits in the lines member. */
2277 /* Drawing */
2278 struct line *curline; /* Line currently being drawn. */
2279 enum line_type curtype; /* Attribute currently used for drawing. */
2280 unsigned long col; /* Column when drawing. */
2281 bool has_scrolled; /* View was scrolled. */
2283 /* Loading */
2284 const char **argv; /* Shell command arguments. */
2285 const char *dir; /* Directory from which to execute. */
2286 struct io io;
2287 struct io *pipe;
2288 time_t start_time;
2289 time_t update_secs;
2292 struct view_ops {
2293 /* What type of content being displayed. Used in the title bar. */
2294 const char *type;
2295 /* Default command arguments. */
2296 const char **argv;
2297 /* Open and reads in all view content. */
2298 bool (*open)(struct view *view);
2299 /* Read one line; updates view->line. */
2300 bool (*read)(struct view *view, char *data);
2301 /* Draw one line; @lineno must be < view->height. */
2302 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2303 /* Depending on view handle a special requests. */
2304 enum request (*request)(struct view *view, enum request request, struct line *line);
2305 /* Search for regexp in a line. */
2306 bool (*grep)(struct view *view, struct line *line);
2307 /* Select line */
2308 void (*select)(struct view *view, struct line *line);
2309 /* Prepare view for loading */
2310 bool (*prepare)(struct view *view);
2313 static struct view_ops blame_ops;
2314 static struct view_ops blob_ops;
2315 static struct view_ops diff_ops;
2316 static struct view_ops help_ops;
2317 static struct view_ops log_ops;
2318 static struct view_ops main_ops;
2319 static struct view_ops pager_ops;
2320 static struct view_ops stage_ops;
2321 static struct view_ops status_ops;
2322 static struct view_ops tree_ops;
2323 static struct view_ops branch_ops;
2325 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2326 { type, name, #env, ref, ops, map, git }
2328 #define VIEW_(id, name, ops, git, ref) \
2329 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2331 static struct view views[] = {
2332 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2333 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2334 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2335 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2336 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2337 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2338 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2339 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2340 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2341 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2342 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2345 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2347 #define foreach_view(view, i) \
2348 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2350 #define view_is_displayed(view) \
2351 (view == display[0] || view == display[1])
2353 static enum request
2354 view_request(struct view *view, enum request request)
2356 if (!view || !view->lines)
2357 return request;
2358 return view->ops->request(view, request, &view->line[view->lineno]);
2363 * View drawing.
2366 static inline void
2367 set_view_attr(struct view *view, enum line_type type)
2369 if (!view->curline->selected && view->curtype != type) {
2370 (void) wattrset(view->win, get_line_attr(type));
2371 wchgat(view->win, -1, 0, type, NULL);
2372 view->curtype = type;
2376 static int
2377 draw_chars(struct view *view, enum line_type type, const char *string,
2378 int max_len, bool use_tilde)
2380 static char out_buffer[BUFSIZ * 2];
2381 int len = 0;
2382 int col = 0;
2383 int trimmed = FALSE;
2384 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2386 if (max_len <= 0)
2387 return 0;
2389 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2391 set_view_attr(view, type);
2392 if (len > 0) {
2393 if (opt_iconv_out != ICONV_NONE) {
2394 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2395 size_t inlen = len + 1;
2397 char *outbuf = out_buffer;
2398 size_t outlen = sizeof(out_buffer);
2400 size_t ret;
2402 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2403 if (ret != (size_t) -1) {
2404 string = out_buffer;
2405 len = sizeof(out_buffer) - outlen;
2409 waddnstr(view->win, string, len);
2411 if (trimmed && use_tilde) {
2412 set_view_attr(view, LINE_DELIMITER);
2413 waddch(view->win, '~');
2414 col++;
2417 return col;
2420 static int
2421 draw_space(struct view *view, enum line_type type, int max, int spaces)
2423 static char space[] = " ";
2424 int col = 0;
2426 spaces = MIN(max, spaces);
2428 while (spaces > 0) {
2429 int len = MIN(spaces, sizeof(space) - 1);
2431 col += draw_chars(view, type, space, len, FALSE);
2432 spaces -= len;
2435 return col;
2438 static bool
2439 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2441 char text[SIZEOF_STR];
2443 do {
2444 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2446 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2447 string += pos;
2448 } while (*string && view->width + view->yoffset > view->col);
2450 return view->width + view->yoffset <= view->col;
2453 static bool
2454 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2456 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2457 int max = view->width + view->yoffset - view->col;
2458 int i;
2460 if (max < size)
2461 size = max;
2463 set_view_attr(view, type);
2464 /* Using waddch() instead of waddnstr() ensures that
2465 * they'll be rendered correctly for the cursor line. */
2466 for (i = skip; i < size; i++)
2467 waddch(view->win, graphic[i]);
2469 view->col += size;
2470 if (size < max && skip <= size)
2471 waddch(view->win, ' ');
2472 view->col++;
2474 return view->width + view->yoffset <= view->col;
2477 static bool
2478 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2480 int max = MIN(view->width + view->yoffset - view->col, len);
2481 int col;
2483 if (text)
2484 col = draw_chars(view, type, text, max - 1, trim);
2485 else
2486 col = draw_space(view, type, max - 1, max - 1);
2488 view->col += col;
2489 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2490 return view->width + view->yoffset <= view->col;
2493 static bool
2494 draw_date(struct view *view, struct time *time)
2496 const char *date = mkdate(time, opt_date);
2497 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2499 return draw_field(view, LINE_DATE, date, cols, FALSE);
2502 static bool
2503 draw_author(struct view *view, const char *author)
2505 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2506 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2508 if (abbreviate && author)
2509 author = get_author_initials(author);
2511 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2514 static bool
2515 draw_mode(struct view *view, mode_t mode)
2517 const char *str;
2519 if (S_ISDIR(mode))
2520 str = "drwxr-xr-x";
2521 else if (S_ISLNK(mode))
2522 str = "lrwxrwxrwx";
2523 else if (S_ISGITLINK(mode))
2524 str = "m---------";
2525 else if (S_ISREG(mode) && mode & S_IXUSR)
2526 str = "-rwxr-xr-x";
2527 else if (S_ISREG(mode))
2528 str = "-rw-r--r--";
2529 else
2530 str = "----------";
2532 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2535 static bool
2536 draw_lineno(struct view *view, unsigned int lineno)
2538 char number[10];
2539 int digits3 = view->digits < 3 ? 3 : view->digits;
2540 int max = MIN(view->width + view->yoffset - view->col, digits3);
2541 char *text = NULL;
2542 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2544 lineno += view->offset + 1;
2545 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2546 static char fmt[] = "%1ld";
2548 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2549 if (string_format(number, fmt, lineno))
2550 text = number;
2552 if (text)
2553 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2554 else
2555 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2556 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2559 static bool
2560 draw_view_line(struct view *view, unsigned int lineno)
2562 struct line *line;
2563 bool selected = (view->offset + lineno == view->lineno);
2565 assert(view_is_displayed(view));
2567 if (view->offset + lineno >= view->lines)
2568 return FALSE;
2570 line = &view->line[view->offset + lineno];
2572 wmove(view->win, lineno, 0);
2573 if (line->cleareol)
2574 wclrtoeol(view->win);
2575 view->col = 0;
2576 view->curline = line;
2577 view->curtype = LINE_NONE;
2578 line->selected = FALSE;
2579 line->dirty = line->cleareol = 0;
2581 if (selected) {
2582 set_view_attr(view, LINE_CURSOR);
2583 line->selected = TRUE;
2584 view->ops->select(view, line);
2587 return view->ops->draw(view, line, lineno);
2590 static void
2591 redraw_view_dirty(struct view *view)
2593 bool dirty = FALSE;
2594 int lineno;
2596 for (lineno = 0; lineno < view->height; lineno++) {
2597 if (view->offset + lineno >= view->lines)
2598 break;
2599 if (!view->line[view->offset + lineno].dirty)
2600 continue;
2601 dirty = TRUE;
2602 if (!draw_view_line(view, lineno))
2603 break;
2606 if (!dirty)
2607 return;
2608 wnoutrefresh(view->win);
2611 static void
2612 redraw_view_from(struct view *view, int lineno)
2614 assert(0 <= lineno && lineno < view->height);
2616 for (; lineno < view->height; lineno++) {
2617 if (!draw_view_line(view, lineno))
2618 break;
2621 wnoutrefresh(view->win);
2624 static void
2625 redraw_view(struct view *view)
2627 werase(view->win);
2628 redraw_view_from(view, 0);
2632 static void
2633 update_view_title(struct view *view)
2635 char buf[SIZEOF_STR];
2636 char state[SIZEOF_STR];
2637 size_t bufpos = 0, statelen = 0;
2639 assert(view_is_displayed(view));
2641 if (view->type != VIEW_STATUS && view->lines) {
2642 unsigned int view_lines = view->offset + view->height;
2643 unsigned int lines = view->lines
2644 ? MIN(view_lines, view->lines) * 100 / view->lines
2645 : 0;
2647 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2648 view->ops->type,
2649 view->lineno + 1,
2650 view->lines,
2651 lines);
2655 if (view->pipe) {
2656 time_t secs = time(NULL) - view->start_time;
2658 /* Three git seconds are a long time ... */
2659 if (secs > 2)
2660 string_format_from(state, &statelen, " loading %lds", secs);
2663 string_format_from(buf, &bufpos, "[%s]", view->name);
2664 if (*view->ref && bufpos < view->width) {
2665 size_t refsize = strlen(view->ref);
2666 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2668 if (minsize < view->width)
2669 refsize = view->width - minsize + 7;
2670 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2673 if (statelen && bufpos < view->width) {
2674 string_format_from(buf, &bufpos, "%s", state);
2677 if (view == display[current_view])
2678 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2679 else
2680 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2682 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2683 wclrtoeol(view->title);
2684 wnoutrefresh(view->title);
2687 static int
2688 apply_step(double step, int value)
2690 if (step >= 1)
2691 return (int) step;
2692 value *= step + 0.01;
2693 return value ? value : 1;
2696 static void
2697 resize_display(void)
2699 int offset, i;
2700 struct view *base = display[0];
2701 struct view *view = display[1] ? display[1] : display[0];
2703 /* Setup window dimensions */
2705 getmaxyx(stdscr, base->height, base->width);
2707 /* Make room for the status window. */
2708 base->height -= 1;
2710 if (view != base) {
2711 /* Horizontal split. */
2712 view->width = base->width;
2713 view->height = apply_step(opt_scale_split_view, base->height);
2714 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2715 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2716 base->height -= view->height;
2718 /* Make room for the title bar. */
2719 view->height -= 1;
2722 /* Make room for the title bar. */
2723 base->height -= 1;
2725 offset = 0;
2727 foreach_displayed_view (view, i) {
2728 if (!view->win) {
2729 view->win = newwin(view->height, 0, offset, 0);
2730 if (!view->win)
2731 die("Failed to create %s view", view->name);
2733 scrollok(view->win, FALSE);
2735 view->title = newwin(1, 0, offset + view->height, 0);
2736 if (!view->title)
2737 die("Failed to create title window");
2739 } else {
2740 wresize(view->win, view->height, view->width);
2741 mvwin(view->win, offset, 0);
2742 mvwin(view->title, offset + view->height, 0);
2745 offset += view->height + 1;
2749 static void
2750 redraw_display(bool clear)
2752 struct view *view;
2753 int i;
2755 foreach_displayed_view (view, i) {
2756 if (clear)
2757 wclear(view->win);
2758 redraw_view(view);
2759 update_view_title(view);
2765 * Option management
2768 static void
2769 toggle_enum_option_do(unsigned int *opt, const char *help,
2770 const struct enum_map *map, size_t size)
2772 *opt = (*opt + 1) % size;
2773 redraw_display(FALSE);
2774 report("Displaying %s %s", enum_name(map[*opt]), help);
2777 #define toggle_enum_option(opt, help, map) \
2778 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2780 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2781 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2783 static void
2784 toggle_view_option(bool *option, const char *help)
2786 *option = !*option;
2787 redraw_display(FALSE);
2788 report("%sabling %s", *option ? "En" : "Dis", help);
2791 static void
2792 open_option_menu(void)
2794 const struct menu_item menu[] = {
2795 { '.', "line numbers", &opt_line_number },
2796 { 'D', "date display", &opt_date },
2797 { 'A', "author display", &opt_author },
2798 { 'g', "revision graph display", &opt_rev_graph },
2799 { 'F', "reference display", &opt_show_refs },
2800 { 0 }
2802 int selected = 0;
2804 if (prompt_menu("Toggle option", menu, &selected)) {
2805 if (menu[selected].data == &opt_date)
2806 toggle_date();
2807 else if (menu[selected].data == &opt_author)
2808 toggle_author();
2809 else
2810 toggle_view_option(menu[selected].data, menu[selected].text);
2814 static void
2815 maximize_view(struct view *view)
2817 memset(display, 0, sizeof(display));
2818 current_view = 0;
2819 display[current_view] = view;
2820 resize_display();
2821 redraw_display(FALSE);
2822 report("");
2827 * Navigation
2830 static bool
2831 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2833 if (lineno >= view->lines)
2834 lineno = view->lines > 0 ? view->lines - 1 : 0;
2836 if (offset > lineno || offset + view->height <= lineno) {
2837 unsigned long half = view->height / 2;
2839 if (lineno > half)
2840 offset = lineno - half;
2841 else
2842 offset = 0;
2845 if (offset != view->offset || lineno != view->lineno) {
2846 view->offset = offset;
2847 view->lineno = lineno;
2848 return TRUE;
2851 return FALSE;
2854 /* Scrolling backend */
2855 static void
2856 do_scroll_view(struct view *view, int lines)
2858 bool redraw_current_line = FALSE;
2860 /* The rendering expects the new offset. */
2861 view->offset += lines;
2863 assert(0 <= view->offset && view->offset < view->lines);
2864 assert(lines);
2866 /* Move current line into the view. */
2867 if (view->lineno < view->offset) {
2868 view->lineno = view->offset;
2869 redraw_current_line = TRUE;
2870 } else if (view->lineno >= view->offset + view->height) {
2871 view->lineno = view->offset + view->height - 1;
2872 redraw_current_line = TRUE;
2875 assert(view->offset <= view->lineno && view->lineno < view->lines);
2877 /* Redraw the whole screen if scrolling is pointless. */
2878 if (view->height < ABS(lines)) {
2879 redraw_view(view);
2881 } else {
2882 int line = lines > 0 ? view->height - lines : 0;
2883 int end = line + ABS(lines);
2885 scrollok(view->win, TRUE);
2886 wscrl(view->win, lines);
2887 scrollok(view->win, FALSE);
2889 while (line < end && draw_view_line(view, line))
2890 line++;
2892 if (redraw_current_line)
2893 draw_view_line(view, view->lineno - view->offset);
2894 wnoutrefresh(view->win);
2897 view->has_scrolled = TRUE;
2898 report("");
2901 /* Scroll frontend */
2902 static void
2903 scroll_view(struct view *view, enum request request)
2905 int lines = 1;
2907 assert(view_is_displayed(view));
2909 switch (request) {
2910 case REQ_SCROLL_FIRST_COL:
2911 view->yoffset = 0;
2912 redraw_view_from(view, 0);
2913 report("");
2914 return;
2915 case REQ_SCROLL_LEFT:
2916 if (view->yoffset == 0) {
2917 report("Cannot scroll beyond the first column");
2918 return;
2920 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2921 view->yoffset = 0;
2922 else
2923 view->yoffset -= apply_step(opt_hscroll, view->width);
2924 redraw_view_from(view, 0);
2925 report("");
2926 return;
2927 case REQ_SCROLL_RIGHT:
2928 view->yoffset += apply_step(opt_hscroll, view->width);
2929 redraw_view(view);
2930 report("");
2931 return;
2932 case REQ_SCROLL_PAGE_DOWN:
2933 lines = view->height;
2934 case REQ_SCROLL_LINE_DOWN:
2935 if (view->offset + lines > view->lines)
2936 lines = view->lines - view->offset;
2938 if (lines == 0 || view->offset + view->height >= view->lines) {
2939 report("Cannot scroll beyond the last line");
2940 return;
2942 break;
2944 case REQ_SCROLL_PAGE_UP:
2945 lines = view->height;
2946 case REQ_SCROLL_LINE_UP:
2947 if (lines > view->offset)
2948 lines = view->offset;
2950 if (lines == 0) {
2951 report("Cannot scroll beyond the first line");
2952 return;
2955 lines = -lines;
2956 break;
2958 default:
2959 die("request %d not handled in switch", request);
2962 do_scroll_view(view, lines);
2965 /* Cursor moving */
2966 static void
2967 move_view(struct view *view, enum request request)
2969 int scroll_steps = 0;
2970 int steps;
2972 switch (request) {
2973 case REQ_MOVE_FIRST_LINE:
2974 steps = -view->lineno;
2975 break;
2977 case REQ_MOVE_LAST_LINE:
2978 steps = view->lines - view->lineno - 1;
2979 break;
2981 case REQ_MOVE_PAGE_UP:
2982 steps = view->height > view->lineno
2983 ? -view->lineno : -view->height;
2984 break;
2986 case REQ_MOVE_PAGE_DOWN:
2987 steps = view->lineno + view->height >= view->lines
2988 ? view->lines - view->lineno - 1 : view->height;
2989 break;
2991 case REQ_MOVE_UP:
2992 steps = -1;
2993 break;
2995 case REQ_MOVE_DOWN:
2996 steps = 1;
2997 break;
2999 default:
3000 die("request %d not handled in switch", request);
3003 if (steps <= 0 && view->lineno == 0) {
3004 report("Cannot move beyond the first line");
3005 return;
3007 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3008 report("Cannot move beyond the last line");
3009 return;
3012 /* Move the current line */
3013 view->lineno += steps;
3014 assert(0 <= view->lineno && view->lineno < view->lines);
3016 /* Check whether the view needs to be scrolled */
3017 if (view->lineno < view->offset ||
3018 view->lineno >= view->offset + view->height) {
3019 scroll_steps = steps;
3020 if (steps < 0 && -steps > view->offset) {
3021 scroll_steps = -view->offset;
3023 } else if (steps > 0) {
3024 if (view->lineno == view->lines - 1 &&
3025 view->lines > view->height) {
3026 scroll_steps = view->lines - view->offset - 1;
3027 if (scroll_steps >= view->height)
3028 scroll_steps -= view->height - 1;
3033 if (!view_is_displayed(view)) {
3034 view->offset += scroll_steps;
3035 assert(0 <= view->offset && view->offset < view->lines);
3036 view->ops->select(view, &view->line[view->lineno]);
3037 return;
3040 /* Repaint the old "current" line if we be scrolling */
3041 if (ABS(steps) < view->height)
3042 draw_view_line(view, view->lineno - steps - view->offset);
3044 if (scroll_steps) {
3045 do_scroll_view(view, scroll_steps);
3046 return;
3049 /* Draw the current line */
3050 draw_view_line(view, view->lineno - view->offset);
3052 wnoutrefresh(view->win);
3053 report("");
3058 * Searching
3061 static void search_view(struct view *view, enum request request);
3063 static bool
3064 grep_text(struct view *view, const char *text[])
3066 regmatch_t pmatch;
3067 size_t i;
3069 for (i = 0; text[i]; i++)
3070 if (*text[i] &&
3071 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3072 return TRUE;
3073 return FALSE;
3076 static void
3077 select_view_line(struct view *view, unsigned long lineno)
3079 unsigned long old_lineno = view->lineno;
3080 unsigned long old_offset = view->offset;
3082 if (goto_view_line(view, view->offset, lineno)) {
3083 if (view_is_displayed(view)) {
3084 if (old_offset != view->offset) {
3085 redraw_view(view);
3086 } else {
3087 draw_view_line(view, old_lineno - view->offset);
3088 draw_view_line(view, view->lineno - view->offset);
3089 wnoutrefresh(view->win);
3091 } else {
3092 view->ops->select(view, &view->line[view->lineno]);
3097 static void
3098 find_next(struct view *view, enum request request)
3100 unsigned long lineno = view->lineno;
3101 int direction;
3103 if (!*view->grep) {
3104 if (!*opt_search)
3105 report("No previous search");
3106 else
3107 search_view(view, request);
3108 return;
3111 switch (request) {
3112 case REQ_SEARCH:
3113 case REQ_FIND_NEXT:
3114 direction = 1;
3115 break;
3117 case REQ_SEARCH_BACK:
3118 case REQ_FIND_PREV:
3119 direction = -1;
3120 break;
3122 default:
3123 return;
3126 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3127 lineno += direction;
3129 /* Note, lineno is unsigned long so will wrap around in which case it
3130 * will become bigger than view->lines. */
3131 for (; lineno < view->lines; lineno += direction) {
3132 if (view->ops->grep(view, &view->line[lineno])) {
3133 select_view_line(view, lineno);
3134 report("Line %ld matches '%s'", lineno + 1, view->grep);
3135 return;
3139 report("No match found for '%s'", view->grep);
3142 static void
3143 search_view(struct view *view, enum request request)
3145 int regex_err;
3147 if (view->regex) {
3148 regfree(view->regex);
3149 *view->grep = 0;
3150 } else {
3151 view->regex = calloc(1, sizeof(*view->regex));
3152 if (!view->regex)
3153 return;
3156 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3157 if (regex_err != 0) {
3158 char buf[SIZEOF_STR] = "unknown error";
3160 regerror(regex_err, view->regex, buf, sizeof(buf));
3161 report("Search failed: %s", buf);
3162 return;
3165 string_copy(view->grep, opt_search);
3167 find_next(view, request);
3171 * Incremental updating
3174 static void
3175 reset_view(struct view *view)
3177 int i;
3179 for (i = 0; i < view->lines; i++)
3180 free(view->line[i].data);
3181 free(view->line);
3183 view->p_offset = view->offset;
3184 view->p_yoffset = view->yoffset;
3185 view->p_lineno = view->lineno;
3187 view->line = NULL;
3188 view->offset = 0;
3189 view->yoffset = 0;
3190 view->lines = 0;
3191 view->lineno = 0;
3192 view->vid[0] = 0;
3193 view->update_secs = 0;
3196 static const char *
3197 format_arg(const char *name)
3199 static struct {
3200 const char *name;
3201 size_t namelen;
3202 const char *value;
3203 const char *value_if_empty;
3204 } vars[] = {
3205 #define FORMAT_VAR(name, value, value_if_empty) \
3206 { name, STRING_SIZE(name), value, value_if_empty }
3207 FORMAT_VAR("%(directory)", opt_path, ""),
3208 FORMAT_VAR("%(file)", opt_file, ""),
3209 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3210 FORMAT_VAR("%(head)", ref_head, ""),
3211 FORMAT_VAR("%(commit)", ref_commit, ""),
3212 FORMAT_VAR("%(blob)", ref_blob, ""),
3213 FORMAT_VAR("%(branch)", ref_branch, ""),
3215 int i;
3217 for (i = 0; i < ARRAY_SIZE(vars); i++)
3218 if (!strncmp(name, vars[i].name, vars[i].namelen))
3219 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3221 report("Unknown replacement: `%s`", name);
3222 return NULL;
3225 static bool
3226 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3228 char buf[SIZEOF_STR];
3229 int argc;
3231 argv_free(*dst_argv);
3233 for (argc = 0; src_argv[argc]; argc++) {
3234 const char *arg = src_argv[argc];
3235 size_t bufpos = 0;
3237 if (!strcmp(arg, "%(fileargs)")) {
3238 if (!argv_append_array(dst_argv, opt_file_args))
3239 break;
3240 continue;
3242 } else if (!strcmp(arg, "%(diffargs)")) {
3243 if (!argv_append_array(dst_argv, opt_diff_args))
3244 break;
3245 continue;
3247 } else if (!strcmp(arg, "%(revargs)")) {
3248 if (!argv_append_array(dst_argv, opt_rev_args))
3249 break;
3250 continue;
3253 while (arg) {
3254 char *next = strstr(arg, "%(");
3255 int len = next - arg;
3256 const char *value;
3258 if (!next || !replace) {
3259 len = strlen(arg);
3260 value = "";
3262 } else {
3263 value = format_arg(next);
3265 if (!value) {
3266 return FALSE;
3270 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3271 return FALSE;
3273 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3276 if (!argv_append(dst_argv, buf))
3277 break;
3280 return src_argv[argc] == NULL;
3283 static bool
3284 restore_view_position(struct view *view)
3286 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3287 return FALSE;
3289 /* Changing the view position cancels the restoring. */
3290 /* FIXME: Changing back to the first line is not detected. */
3291 if (view->offset != 0 || view->lineno != 0) {
3292 view->p_restore = FALSE;
3293 return FALSE;
3296 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3297 view_is_displayed(view))
3298 werase(view->win);
3300 view->yoffset = view->p_yoffset;
3301 view->p_restore = FALSE;
3303 return TRUE;
3306 static void
3307 end_update(struct view *view, bool force)
3309 if (!view->pipe)
3310 return;
3311 while (!view->ops->read(view, NULL))
3312 if (!force)
3313 return;
3314 if (force)
3315 io_kill(view->pipe);
3316 io_done(view->pipe);
3317 view->pipe = NULL;
3320 static void
3321 setup_update(struct view *view, const char *vid)
3323 reset_view(view);
3324 string_copy_rev(view->vid, vid);
3325 view->pipe = &view->io;
3326 view->start_time = time(NULL);
3329 static bool
3330 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3332 view->dir = dir;
3333 return format_argv(&view->argv, argv, replace);
3336 static bool
3337 prepare_update(struct view *view, const char *argv[], const char *dir)
3339 if (view->pipe)
3340 end_update(view, TRUE);
3341 return prepare_io(view, dir, argv, FALSE);
3344 static bool
3345 start_update(struct view *view, const char **argv, const char *dir)
3347 if (view->pipe)
3348 io_done(view->pipe);
3349 return prepare_io(view, dir, argv, FALSE) &&
3350 io_run(&view->io, IO_RD, dir, view->argv);
3353 static bool
3354 prepare_update_file(struct view *view, const char *name)
3356 if (view->pipe)
3357 end_update(view, TRUE);
3358 argv_free(view->argv);
3359 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3362 static bool
3363 begin_update(struct view *view, bool refresh)
3365 if (view->pipe)
3366 end_update(view, TRUE);
3368 if (!refresh) {
3369 if (view->ops->prepare) {
3370 if (!view->ops->prepare(view))
3371 return FALSE;
3372 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3373 return FALSE;
3376 /* Put the current ref_* value to the view title ref
3377 * member. This is needed by the blob view. Most other
3378 * views sets it automatically after loading because the
3379 * first line is a commit line. */
3380 string_copy_rev(view->ref, view->id);
3383 if (view->argv && view->argv[0] &&
3384 !io_run(&view->io, IO_RD, view->dir, view->argv))
3385 return FALSE;
3387 setup_update(view, view->id);
3389 return TRUE;
3392 static bool
3393 update_view(struct view *view)
3395 char out_buffer[BUFSIZ * 2];
3396 char *line;
3397 /* Clear the view and redraw everything since the tree sorting
3398 * might have rearranged things. */
3399 bool redraw = view->lines == 0;
3400 bool can_read = TRUE;
3402 if (!view->pipe)
3403 return TRUE;
3405 if (!io_can_read(view->pipe)) {
3406 if (view->lines == 0 && view_is_displayed(view)) {
3407 time_t secs = time(NULL) - view->start_time;
3409 if (secs > 1 && secs > view->update_secs) {
3410 if (view->update_secs == 0)
3411 redraw_view(view);
3412 update_view_title(view);
3413 view->update_secs = secs;
3416 return TRUE;
3419 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3420 if (opt_iconv_in != ICONV_NONE) {
3421 ICONV_CONST char *inbuf = line;
3422 size_t inlen = strlen(line) + 1;
3424 char *outbuf = out_buffer;
3425 size_t outlen = sizeof(out_buffer);
3427 size_t ret;
3429 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3430 if (ret != (size_t) -1)
3431 line = out_buffer;
3434 if (!view->ops->read(view, line)) {
3435 report("Allocation failure");
3436 end_update(view, TRUE);
3437 return FALSE;
3442 unsigned long lines = view->lines;
3443 int digits;
3445 for (digits = 0; lines; digits++)
3446 lines /= 10;
3448 /* Keep the displayed view in sync with line number scaling. */
3449 if (digits != view->digits) {
3450 view->digits = digits;
3451 if (opt_line_number || view->type == VIEW_BLAME)
3452 redraw = TRUE;
3456 if (io_error(view->pipe)) {
3457 report("Failed to read: %s", io_strerror(view->pipe));
3458 end_update(view, TRUE);
3460 } else if (io_eof(view->pipe)) {
3461 if (view_is_displayed(view))
3462 report("");
3463 end_update(view, FALSE);
3466 if (restore_view_position(view))
3467 redraw = TRUE;
3469 if (!view_is_displayed(view))
3470 return TRUE;
3472 if (redraw)
3473 redraw_view_from(view, 0);
3474 else
3475 redraw_view_dirty(view);
3477 /* Update the title _after_ the redraw so that if the redraw picks up a
3478 * commit reference in view->ref it'll be available here. */
3479 update_view_title(view);
3480 return TRUE;
3483 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3485 static struct line *
3486 add_line_data(struct view *view, void *data, enum line_type type)
3488 struct line *line;
3490 if (!realloc_lines(&view->line, view->lines, 1))
3491 return NULL;
3493 line = &view->line[view->lines++];
3494 memset(line, 0, sizeof(*line));
3495 line->type = type;
3496 line->data = data;
3497 line->dirty = 1;
3499 return line;
3502 static struct line *
3503 add_line_text(struct view *view, const char *text, enum line_type type)
3505 char *data = text ? strdup(text) : NULL;
3507 return data ? add_line_data(view, data, type) : NULL;
3510 static struct line *
3511 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3513 char buf[SIZEOF_STR];
3514 va_list args;
3516 va_start(args, fmt);
3517 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3518 buf[0] = 0;
3519 va_end(args);
3521 return buf[0] ? add_line_text(view, buf, type) : NULL;
3525 * View opening
3528 enum open_flags {
3529 OPEN_DEFAULT = 0, /* Use default view switching. */
3530 OPEN_SPLIT = 1, /* Split current view. */
3531 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3532 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3533 OPEN_PREPARED = 32, /* Open already prepared command. */
3536 static void
3537 open_view(struct view *prev, enum request request, enum open_flags flags)
3539 bool split = !!(flags & OPEN_SPLIT);
3540 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3541 bool nomaximize = !!(flags & OPEN_REFRESH);
3542 struct view *view = VIEW(request);
3543 int nviews = displayed_views();
3544 struct view *base_view = display[0];
3546 if (view == prev && nviews == 1 && !reload) {
3547 report("Already in %s view", view->name);
3548 return;
3551 if (view->git_dir && !opt_git_dir[0]) {
3552 report("The %s view is disabled in pager view", view->name);
3553 return;
3556 if (split) {
3557 display[1] = view;
3558 current_view = 1;
3559 view->parent = prev;
3560 } else if (!nomaximize) {
3561 /* Maximize the current view. */
3562 memset(display, 0, sizeof(display));
3563 current_view = 0;
3564 display[current_view] = view;
3567 /* No prev signals that this is the first loaded view. */
3568 if (prev && view != prev) {
3569 view->prev = prev;
3572 /* Resize the view when switching between split- and full-screen,
3573 * or when switching between two different full-screen views. */
3574 if (nviews != displayed_views() ||
3575 (nviews == 1 && base_view != display[0]))
3576 resize_display();
3578 if (view->ops->open) {
3579 if (view->pipe)
3580 end_update(view, TRUE);
3581 if (!view->ops->open(view)) {
3582 report("Failed to load %s view", view->name);
3583 return;
3585 restore_view_position(view);
3587 } else if ((reload || strcmp(view->vid, view->id)) &&
3588 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3589 report("Failed to load %s view", view->name);
3590 return;
3593 if (split && prev->lineno - prev->offset >= prev->height) {
3594 /* Take the title line into account. */
3595 int lines = prev->lineno - prev->offset - prev->height + 1;
3597 /* Scroll the view that was split if the current line is
3598 * outside the new limited view. */
3599 do_scroll_view(prev, lines);
3602 if (prev && view != prev && split && view_is_displayed(prev)) {
3603 /* "Blur" the previous view. */
3604 update_view_title(prev);
3607 if (view->pipe && view->lines == 0) {
3608 /* Clear the old view and let the incremental updating refill
3609 * the screen. */
3610 werase(view->win);
3611 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3612 report("");
3613 } else if (view_is_displayed(view)) {
3614 redraw_view(view);
3615 report("");
3619 static void
3620 open_external_viewer(const char *argv[], const char *dir)
3622 def_prog_mode(); /* save current tty modes */
3623 endwin(); /* restore original tty modes */
3624 io_run_fg(argv, dir);
3625 fprintf(stderr, "Press Enter to continue");
3626 getc(opt_tty);
3627 reset_prog_mode();
3628 redraw_display(TRUE);
3631 static void
3632 open_mergetool(const char *file)
3634 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3636 open_external_viewer(mergetool_argv, opt_cdup);
3639 static void
3640 open_editor(const char *file)
3642 const char *editor_argv[] = { "vi", file, NULL };
3643 const char *editor;
3645 editor = getenv("GIT_EDITOR");
3646 if (!editor && *opt_editor)
3647 editor = opt_editor;
3648 if (!editor)
3649 editor = getenv("VISUAL");
3650 if (!editor)
3651 editor = getenv("EDITOR");
3652 if (!editor)
3653 editor = "vi";
3655 editor_argv[0] = editor;
3656 open_external_viewer(editor_argv, opt_cdup);
3659 static void
3660 open_run_request(enum request request)
3662 struct run_request *req = get_run_request(request);
3663 const char **argv = NULL;
3665 if (!req) {
3666 report("Unknown run request");
3667 return;
3670 if (format_argv(&argv, req->argv, TRUE))
3671 open_external_viewer(argv, NULL);
3672 if (argv)
3673 argv_free(argv);
3674 free(argv);
3678 * User request switch noodle
3681 static int
3682 view_driver(struct view *view, enum request request)
3684 int i;
3686 if (request == REQ_NONE)
3687 return TRUE;
3689 if (request > REQ_NONE) {
3690 open_run_request(request);
3691 view_request(view, REQ_REFRESH);
3692 return TRUE;
3695 request = view_request(view, request);
3696 if (request == REQ_NONE)
3697 return TRUE;
3699 switch (request) {
3700 case REQ_MOVE_UP:
3701 case REQ_MOVE_DOWN:
3702 case REQ_MOVE_PAGE_UP:
3703 case REQ_MOVE_PAGE_DOWN:
3704 case REQ_MOVE_FIRST_LINE:
3705 case REQ_MOVE_LAST_LINE:
3706 move_view(view, request);
3707 break;
3709 case REQ_SCROLL_FIRST_COL:
3710 case REQ_SCROLL_LEFT:
3711 case REQ_SCROLL_RIGHT:
3712 case REQ_SCROLL_LINE_DOWN:
3713 case REQ_SCROLL_LINE_UP:
3714 case REQ_SCROLL_PAGE_DOWN:
3715 case REQ_SCROLL_PAGE_UP:
3716 scroll_view(view, request);
3717 break;
3719 case REQ_VIEW_BLAME:
3720 if (!opt_file[0]) {
3721 report("No file chosen, press %s to open tree view",
3722 get_key(view->keymap, REQ_VIEW_TREE));
3723 break;
3725 open_view(view, request, OPEN_DEFAULT);
3726 break;
3728 case REQ_VIEW_BLOB:
3729 if (!ref_blob[0]) {
3730 report("No file chosen, press %s to open tree view",
3731 get_key(view->keymap, REQ_VIEW_TREE));
3732 break;
3734 open_view(view, request, OPEN_DEFAULT);
3735 break;
3737 case REQ_VIEW_PAGER:
3738 if (view == NULL) {
3739 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3740 die("Failed to open stdin");
3741 open_view(view, request, OPEN_PREPARED);
3742 break;
3745 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3746 report("No pager content, press %s to run command from prompt",
3747 get_key(view->keymap, REQ_PROMPT));
3748 break;
3750 open_view(view, request, OPEN_DEFAULT);
3751 break;
3753 case REQ_VIEW_STAGE:
3754 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3755 report("No stage content, press %s to open the status view and choose file",
3756 get_key(view->keymap, REQ_VIEW_STATUS));
3757 break;
3759 open_view(view, request, OPEN_DEFAULT);
3760 break;
3762 case REQ_VIEW_STATUS:
3763 if (opt_is_inside_work_tree == FALSE) {
3764 report("The status view requires a working tree");
3765 break;
3767 open_view(view, request, OPEN_DEFAULT);
3768 break;
3770 case REQ_VIEW_MAIN:
3771 case REQ_VIEW_DIFF:
3772 case REQ_VIEW_LOG:
3773 case REQ_VIEW_TREE:
3774 case REQ_VIEW_HELP:
3775 case REQ_VIEW_BRANCH:
3776 open_view(view, request, OPEN_DEFAULT);
3777 break;
3779 case REQ_NEXT:
3780 case REQ_PREVIOUS:
3781 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3783 if (view->parent) {
3784 int line;
3786 view = view->parent;
3787 line = view->lineno;
3788 move_view(view, request);
3789 if (view_is_displayed(view))
3790 update_view_title(view);
3791 if (line != view->lineno)
3792 view_request(view, REQ_ENTER);
3793 } else {
3794 move_view(view, request);
3796 break;
3798 case REQ_VIEW_NEXT:
3800 int nviews = displayed_views();
3801 int next_view = (current_view + 1) % nviews;
3803 if (next_view == current_view) {
3804 report("Only one view is displayed");
3805 break;
3808 current_view = next_view;
3809 /* Blur out the title of the previous view. */
3810 update_view_title(view);
3811 report("");
3812 break;
3814 case REQ_REFRESH:
3815 report("Refreshing is not yet supported for the %s view", view->name);
3816 break;
3818 case REQ_MAXIMIZE:
3819 if (displayed_views() == 2)
3820 maximize_view(view);
3821 break;
3823 case REQ_OPTIONS:
3824 open_option_menu();
3825 break;
3827 case REQ_TOGGLE_LINENO:
3828 toggle_view_option(&opt_line_number, "line numbers");
3829 break;
3831 case REQ_TOGGLE_DATE:
3832 toggle_date();
3833 break;
3835 case REQ_TOGGLE_AUTHOR:
3836 toggle_author();
3837 break;
3839 case REQ_TOGGLE_REV_GRAPH:
3840 toggle_view_option(&opt_rev_graph, "revision graph display");
3841 break;
3843 case REQ_TOGGLE_REFS:
3844 toggle_view_option(&opt_show_refs, "reference display");
3845 break;
3847 case REQ_TOGGLE_SORT_FIELD:
3848 case REQ_TOGGLE_SORT_ORDER:
3849 report("Sorting is not yet supported for the %s view", view->name);
3850 break;
3852 case REQ_SEARCH:
3853 case REQ_SEARCH_BACK:
3854 search_view(view, request);
3855 break;
3857 case REQ_FIND_NEXT:
3858 case REQ_FIND_PREV:
3859 find_next(view, request);
3860 break;
3862 case REQ_STOP_LOADING:
3863 foreach_view(view, i) {
3864 if (view->pipe)
3865 report("Stopped loading the %s view", view->name),
3866 end_update(view, TRUE);
3868 break;
3870 case REQ_SHOW_VERSION:
3871 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3872 return TRUE;
3874 case REQ_SCREEN_REDRAW:
3875 redraw_display(TRUE);
3876 break;
3878 case REQ_EDIT:
3879 report("Nothing to edit");
3880 break;
3882 case REQ_ENTER:
3883 report("Nothing to enter");
3884 break;
3886 case REQ_VIEW_CLOSE:
3887 /* XXX: Mark closed views by letting view->prev point to the
3888 * view itself. Parents to closed view should never be
3889 * followed. */
3890 if (view->prev && view->prev != view) {
3891 maximize_view(view->prev);
3892 view->prev = view;
3893 break;
3895 /* Fall-through */
3896 case REQ_QUIT:
3897 return FALSE;
3899 default:
3900 report("Unknown key, press %s for help",
3901 get_key(view->keymap, REQ_VIEW_HELP));
3902 return TRUE;
3905 return TRUE;
3910 * View backend utilities
3913 enum sort_field {
3914 ORDERBY_NAME,
3915 ORDERBY_DATE,
3916 ORDERBY_AUTHOR,
3919 struct sort_state {
3920 const enum sort_field *fields;
3921 size_t size, current;
3922 bool reverse;
3925 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3926 #define get_sort_field(state) ((state).fields[(state).current])
3927 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3929 static void
3930 sort_view(struct view *view, enum request request, struct sort_state *state,
3931 int (*compare)(const void *, const void *))
3933 switch (request) {
3934 case REQ_TOGGLE_SORT_FIELD:
3935 state->current = (state->current + 1) % state->size;
3936 break;
3938 case REQ_TOGGLE_SORT_ORDER:
3939 state->reverse = !state->reverse;
3940 break;
3941 default:
3942 die("Not a sort request");
3945 qsort(view->line, view->lines, sizeof(*view->line), compare);
3946 redraw_view(view);
3949 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3951 /* Small author cache to reduce memory consumption. It uses binary
3952 * search to lookup or find place to position new entries. No entries
3953 * are ever freed. */
3954 static const char *
3955 get_author(const char *name)
3957 static const char **authors;
3958 static size_t authors_size;
3959 int from = 0, to = authors_size - 1;
3961 while (from <= to) {
3962 size_t pos = (to + from) / 2;
3963 int cmp = strcmp(name, authors[pos]);
3965 if (!cmp)
3966 return authors[pos];
3968 if (cmp < 0)
3969 to = pos - 1;
3970 else
3971 from = pos + 1;
3974 if (!realloc_authors(&authors, authors_size, 1))
3975 return NULL;
3976 name = strdup(name);
3977 if (!name)
3978 return NULL;
3980 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3981 authors[from] = name;
3982 authors_size++;
3984 return name;
3987 static void
3988 parse_timesec(struct time *time, const char *sec)
3990 time->sec = (time_t) atol(sec);
3993 static void
3994 parse_timezone(struct time *time, const char *zone)
3996 long tz;
3998 tz = ('0' - zone[1]) * 60 * 60 * 10;
3999 tz += ('0' - zone[2]) * 60 * 60;
4000 tz += ('0' - zone[3]) * 60 * 10;
4001 tz += ('0' - zone[4]) * 60;
4003 if (zone[0] == '-')
4004 tz = -tz;
4006 time->tz = tz;
4007 time->sec -= tz;
4010 /* Parse author lines where the name may be empty:
4011 * author <email@address.tld> 1138474660 +0100
4013 static void
4014 parse_author_line(char *ident, const char **author, struct time *time)
4016 char *nameend = strchr(ident, '<');
4017 char *emailend = strchr(ident, '>');
4019 if (nameend && emailend)
4020 *nameend = *emailend = 0;
4021 ident = chomp_string(ident);
4022 if (!*ident) {
4023 if (nameend)
4024 ident = chomp_string(nameend + 1);
4025 if (!*ident)
4026 ident = "Unknown";
4029 *author = get_author(ident);
4031 /* Parse epoch and timezone */
4032 if (emailend && emailend[1] == ' ') {
4033 char *secs = emailend + 2;
4034 char *zone = strchr(secs, ' ');
4036 parse_timesec(time, secs);
4038 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4039 parse_timezone(time, zone + 1);
4044 * Pager backend
4047 static bool
4048 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4050 if (opt_line_number && draw_lineno(view, lineno))
4051 return TRUE;
4053 draw_text(view, line->type, line->data, TRUE);
4054 return TRUE;
4057 static bool
4058 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4060 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4061 char ref[SIZEOF_STR];
4063 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4064 return TRUE;
4066 /* This is the only fatal call, since it can "corrupt" the buffer. */
4067 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4068 return FALSE;
4070 return TRUE;
4073 static void
4074 add_pager_refs(struct view *view, struct line *line)
4076 char buf[SIZEOF_STR];
4077 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4078 struct ref_list *list;
4079 size_t bufpos = 0, i;
4080 const char *sep = "Refs: ";
4081 bool is_tag = FALSE;
4083 assert(line->type == LINE_COMMIT);
4085 list = get_ref_list(commit_id);
4086 if (!list) {
4087 if (view->type == VIEW_DIFF)
4088 goto try_add_describe_ref;
4089 return;
4092 for (i = 0; i < list->size; i++) {
4093 struct ref *ref = list->refs[i];
4094 const char *fmt = ref->tag ? "%s[%s]" :
4095 ref->remote ? "%s<%s>" : "%s%s";
4097 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4098 return;
4099 sep = ", ";
4100 if (ref->tag)
4101 is_tag = TRUE;
4104 if (!is_tag && view->type == VIEW_DIFF) {
4105 try_add_describe_ref:
4106 /* Add <tag>-g<commit_id> "fake" reference. */
4107 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4108 return;
4111 if (bufpos == 0)
4112 return;
4114 add_line_text(view, buf, LINE_PP_REFS);
4117 static bool
4118 pager_read(struct view *view, char *data)
4120 struct line *line;
4122 if (!data)
4123 return TRUE;
4125 line = add_line_text(view, data, get_line_type(data));
4126 if (!line)
4127 return FALSE;
4129 if (line->type == LINE_COMMIT &&
4130 (view->type == VIEW_DIFF ||
4131 view->type == VIEW_LOG))
4132 add_pager_refs(view, line);
4134 return TRUE;
4137 static enum request
4138 pager_request(struct view *view, enum request request, struct line *line)
4140 int split = 0;
4142 if (request != REQ_ENTER)
4143 return request;
4145 if (line->type == LINE_COMMIT &&
4146 (view->type == VIEW_LOG ||
4147 view->type == VIEW_PAGER)) {
4148 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4149 split = 1;
4152 /* Always scroll the view even if it was split. That way
4153 * you can use Enter to scroll through the log view and
4154 * split open each commit diff. */
4155 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4157 /* FIXME: A minor workaround. Scrolling the view will call report("")
4158 * but if we are scrolling a non-current view this won't properly
4159 * update the view title. */
4160 if (split)
4161 update_view_title(view);
4163 return REQ_NONE;
4166 static bool
4167 pager_grep(struct view *view, struct line *line)
4169 const char *text[] = { line->data, NULL };
4171 return grep_text(view, text);
4174 static void
4175 pager_select(struct view *view, struct line *line)
4177 if (line->type == LINE_COMMIT) {
4178 char *text = (char *)line->data + STRING_SIZE("commit ");
4180 if (view->type != VIEW_PAGER)
4181 string_copy_rev(view->ref, text);
4182 string_copy_rev(ref_commit, text);
4186 static struct view_ops pager_ops = {
4187 "line",
4188 NULL,
4189 NULL,
4190 pager_read,
4191 pager_draw,
4192 pager_request,
4193 pager_grep,
4194 pager_select,
4197 static const char *log_argv[SIZEOF_ARG] = {
4198 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4201 static enum request
4202 log_request(struct view *view, enum request request, struct line *line)
4204 switch (request) {
4205 case REQ_REFRESH:
4206 load_refs();
4207 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4208 return REQ_NONE;
4209 default:
4210 return pager_request(view, request, line);
4214 static struct view_ops log_ops = {
4215 "line",
4216 log_argv,
4217 NULL,
4218 pager_read,
4219 pager_draw,
4220 log_request,
4221 pager_grep,
4222 pager_select,
4225 static const char *diff_argv[SIZEOF_ARG] = {
4226 "git", "show", "--pretty=fuller", "--no-color", "--root",
4227 "--patch-with-stat", "--find-copies-harder", "-C",
4228 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4231 static bool
4232 diff_read(struct view *view, char *data)
4234 if (!data) {
4235 /* Fall back to retry if no diff will be shown. */
4236 if (view->lines == 0 && opt_file_args) {
4237 int pos = argv_size(view->argv)
4238 - argv_size(opt_file_args) - 1;
4240 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4241 for (; view->argv[pos]; pos++) {
4242 free((void *) view->argv[pos]);
4243 view->argv[pos] = NULL;
4246 if (view->pipe)
4247 io_done(view->pipe);
4248 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4249 return FALSE;
4252 return TRUE;
4255 return pager_read(view, data);
4258 static struct view_ops diff_ops = {
4259 "line",
4260 diff_argv,
4261 NULL,
4262 diff_read,
4263 pager_draw,
4264 pager_request,
4265 pager_grep,
4266 pager_select,
4270 * Help backend
4273 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4275 static bool
4276 help_open_keymap_title(struct view *view, enum keymap keymap)
4278 struct line *line;
4280 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4281 help_keymap_hidden[keymap] ? '+' : '-',
4282 enum_name(keymap_table[keymap]));
4283 if (line)
4284 line->other = keymap;
4286 return help_keymap_hidden[keymap];
4289 static void
4290 help_open_keymap(struct view *view, enum keymap keymap)
4292 const char *group = NULL;
4293 char buf[SIZEOF_STR];
4294 size_t bufpos;
4295 bool add_title = TRUE;
4296 int i;
4298 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4299 const char *key = NULL;
4301 if (req_info[i].request == REQ_NONE)
4302 continue;
4304 if (!req_info[i].request) {
4305 group = req_info[i].help;
4306 continue;
4309 key = get_keys(keymap, req_info[i].request, TRUE);
4310 if (!key || !*key)
4311 continue;
4313 if (add_title && help_open_keymap_title(view, keymap))
4314 return;
4315 add_title = FALSE;
4317 if (group) {
4318 add_line_text(view, group, LINE_HELP_GROUP);
4319 group = NULL;
4322 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4323 enum_name(req_info[i]), req_info[i].help);
4326 group = "External commands:";
4328 for (i = 0; i < run_requests; i++) {
4329 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4330 const char *key;
4331 int argc;
4333 if (!req || req->keymap != keymap)
4334 continue;
4336 key = get_key_name(req->key);
4337 if (!*key)
4338 key = "(no key defined)";
4340 if (add_title && help_open_keymap_title(view, keymap))
4341 return;
4342 if (group) {
4343 add_line_text(view, group, LINE_HELP_GROUP);
4344 group = NULL;
4347 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4348 if (!string_format_from(buf, &bufpos, "%s%s",
4349 argc ? " " : "", req->argv[argc]))
4350 return;
4352 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4356 static bool
4357 help_open(struct view *view)
4359 enum keymap keymap;
4361 reset_view(view);
4362 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4363 add_line_text(view, "", LINE_DEFAULT);
4365 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4366 help_open_keymap(view, keymap);
4368 return TRUE;
4371 static enum request
4372 help_request(struct view *view, enum request request, struct line *line)
4374 switch (request) {
4375 case REQ_ENTER:
4376 if (line->type == LINE_HELP_KEYMAP) {
4377 help_keymap_hidden[line->other] =
4378 !help_keymap_hidden[line->other];
4379 view->p_restore = TRUE;
4380 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4383 return REQ_NONE;
4384 default:
4385 return pager_request(view, request, line);
4389 static struct view_ops help_ops = {
4390 "line",
4391 NULL,
4392 help_open,
4393 NULL,
4394 pager_draw,
4395 help_request,
4396 pager_grep,
4397 pager_select,
4402 * Tree backend
4405 struct tree_stack_entry {
4406 struct tree_stack_entry *prev; /* Entry below this in the stack */
4407 unsigned long lineno; /* Line number to restore */
4408 char *name; /* Position of name in opt_path */
4411 /* The top of the path stack. */
4412 static struct tree_stack_entry *tree_stack = NULL;
4413 unsigned long tree_lineno = 0;
4415 static void
4416 pop_tree_stack_entry(void)
4418 struct tree_stack_entry *entry = tree_stack;
4420 tree_lineno = entry->lineno;
4421 entry->name[0] = 0;
4422 tree_stack = entry->prev;
4423 free(entry);
4426 static void
4427 push_tree_stack_entry(const char *name, unsigned long lineno)
4429 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4430 size_t pathlen = strlen(opt_path);
4432 if (!entry)
4433 return;
4435 entry->prev = tree_stack;
4436 entry->name = opt_path + pathlen;
4437 tree_stack = entry;
4439 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4440 pop_tree_stack_entry();
4441 return;
4444 /* Move the current line to the first tree entry. */
4445 tree_lineno = 1;
4446 entry->lineno = lineno;
4449 /* Parse output from git-ls-tree(1):
4451 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4454 #define SIZEOF_TREE_ATTR \
4455 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4457 #define SIZEOF_TREE_MODE \
4458 STRING_SIZE("100644 ")
4460 #define TREE_ID_OFFSET \
4461 STRING_SIZE("100644 blob ")
4463 struct tree_entry {
4464 char id[SIZEOF_REV];
4465 mode_t mode;
4466 struct time time; /* Date from the author ident. */
4467 const char *author; /* Author of the commit. */
4468 char name[1];
4471 static const char *
4472 tree_path(const struct line *line)
4474 return ((struct tree_entry *) line->data)->name;
4477 static int
4478 tree_compare_entry(const struct line *line1, const struct line *line2)
4480 if (line1->type != line2->type)
4481 return line1->type == LINE_TREE_DIR ? -1 : 1;
4482 return strcmp(tree_path(line1), tree_path(line2));
4485 static const enum sort_field tree_sort_fields[] = {
4486 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4488 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4490 static int
4491 tree_compare(const void *l1, const void *l2)
4493 const struct line *line1 = (const struct line *) l1;
4494 const struct line *line2 = (const struct line *) l2;
4495 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4496 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4498 if (line1->type == LINE_TREE_HEAD)
4499 return -1;
4500 if (line2->type == LINE_TREE_HEAD)
4501 return 1;
4503 switch (get_sort_field(tree_sort_state)) {
4504 case ORDERBY_DATE:
4505 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4507 case ORDERBY_AUTHOR:
4508 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4510 case ORDERBY_NAME:
4511 default:
4512 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4517 static struct line *
4518 tree_entry(struct view *view, enum line_type type, const char *path,
4519 const char *mode, const char *id)
4521 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4522 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4524 if (!entry || !line) {
4525 free(entry);
4526 return NULL;
4529 strncpy(entry->name, path, strlen(path));
4530 if (mode)
4531 entry->mode = strtoul(mode, NULL, 8);
4532 if (id)
4533 string_copy_rev(entry->id, id);
4535 return line;
4538 static bool
4539 tree_read_date(struct view *view, char *text, bool *read_date)
4541 static const char *author_name;
4542 static struct time author_time;
4544 if (!text && *read_date) {
4545 *read_date = FALSE;
4546 return TRUE;
4548 } else if (!text) {
4549 char *path = *opt_path ? opt_path : ".";
4550 /* Find next entry to process */
4551 const char *log_file[] = {
4552 "git", "log", "--no-color", "--pretty=raw",
4553 "--cc", "--raw", view->id, "--", path, NULL
4556 if (!view->lines) {
4557 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4558 report("Tree is empty");
4559 return TRUE;
4562 if (!start_update(view, log_file, opt_cdup)) {
4563 report("Failed to load tree data");
4564 return TRUE;
4567 *read_date = TRUE;
4568 return FALSE;
4570 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4571 parse_author_line(text + STRING_SIZE("author "),
4572 &author_name, &author_time);
4574 } else if (*text == ':') {
4575 char *pos;
4576 size_t annotated = 1;
4577 size_t i;
4579 pos = strchr(text, '\t');
4580 if (!pos)
4581 return TRUE;
4582 text = pos + 1;
4583 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4584 text += strlen(opt_path);
4585 pos = strchr(text, '/');
4586 if (pos)
4587 *pos = 0;
4589 for (i = 1; i < view->lines; i++) {
4590 struct line *line = &view->line[i];
4591 struct tree_entry *entry = line->data;
4593 annotated += !!entry->author;
4594 if (entry->author || strcmp(entry->name, text))
4595 continue;
4597 entry->author = author_name;
4598 entry->time = author_time;
4599 line->dirty = 1;
4600 break;
4603 if (annotated == view->lines)
4604 io_kill(view->pipe);
4606 return TRUE;
4609 static bool
4610 tree_read(struct view *view, char *text)
4612 static bool read_date = FALSE;
4613 struct tree_entry *data;
4614 struct line *entry, *line;
4615 enum line_type type;
4616 size_t textlen = text ? strlen(text) : 0;
4617 char *path = text + SIZEOF_TREE_ATTR;
4619 if (read_date || !text)
4620 return tree_read_date(view, text, &read_date);
4622 if (textlen <= SIZEOF_TREE_ATTR)
4623 return FALSE;
4624 if (view->lines == 0 &&
4625 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4626 return FALSE;
4628 /* Strip the path part ... */
4629 if (*opt_path) {
4630 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4631 size_t striplen = strlen(opt_path);
4633 if (pathlen > striplen)
4634 memmove(path, path + striplen,
4635 pathlen - striplen + 1);
4637 /* Insert "link" to parent directory. */
4638 if (view->lines == 1 &&
4639 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4640 return FALSE;
4643 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4644 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4645 if (!entry)
4646 return FALSE;
4647 data = entry->data;
4649 /* Skip "Directory ..." and ".." line. */
4650 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4651 if (tree_compare_entry(line, entry) <= 0)
4652 continue;
4654 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4656 line->data = data;
4657 line->type = type;
4658 for (; line <= entry; line++)
4659 line->dirty = line->cleareol = 1;
4660 return TRUE;
4663 if (tree_lineno > view->lineno) {
4664 view->lineno = tree_lineno;
4665 tree_lineno = 0;
4668 return TRUE;
4671 static bool
4672 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4674 struct tree_entry *entry = line->data;
4676 if (line->type == LINE_TREE_HEAD) {
4677 if (draw_text(view, line->type, "Directory path /", TRUE))
4678 return TRUE;
4679 } else {
4680 if (draw_mode(view, entry->mode))
4681 return TRUE;
4683 if (opt_author && draw_author(view, entry->author))
4684 return TRUE;
4686 if (opt_date && draw_date(view, &entry->time))
4687 return TRUE;
4689 if (draw_text(view, line->type, entry->name, TRUE))
4690 return TRUE;
4691 return TRUE;
4694 static void
4695 open_blob_editor(const char *id)
4697 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4698 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4699 int fd = mkstemp(file);
4701 if (fd == -1)
4702 report("Failed to create temporary file");
4703 else if (!io_run_append(blob_argv, fd))
4704 report("Failed to save blob data to file");
4705 else
4706 open_editor(file);
4707 if (fd != -1)
4708 unlink(file);
4711 static enum request
4712 tree_request(struct view *view, enum request request, struct line *line)
4714 enum open_flags flags;
4715 struct tree_entry *entry = line->data;
4717 switch (request) {
4718 case REQ_VIEW_BLAME:
4719 if (line->type != LINE_TREE_FILE) {
4720 report("Blame only supported for files");
4721 return REQ_NONE;
4724 string_copy(opt_ref, view->vid);
4725 return request;
4727 case REQ_EDIT:
4728 if (line->type != LINE_TREE_FILE) {
4729 report("Edit only supported for files");
4730 } else if (!is_head_commit(view->vid)) {
4731 open_blob_editor(entry->id);
4732 } else {
4733 open_editor(opt_file);
4735 return REQ_NONE;
4737 case REQ_TOGGLE_SORT_FIELD:
4738 case REQ_TOGGLE_SORT_ORDER:
4739 sort_view(view, request, &tree_sort_state, tree_compare);
4740 return REQ_NONE;
4742 case REQ_PARENT:
4743 if (!*opt_path) {
4744 /* quit view if at top of tree */
4745 return REQ_VIEW_CLOSE;
4747 /* fake 'cd ..' */
4748 line = &view->line[1];
4749 break;
4751 case REQ_ENTER:
4752 break;
4754 default:
4755 return request;
4758 /* Cleanup the stack if the tree view is at a different tree. */
4759 while (!*opt_path && tree_stack)
4760 pop_tree_stack_entry();
4762 switch (line->type) {
4763 case LINE_TREE_DIR:
4764 /* Depending on whether it is a subdirectory or parent link
4765 * mangle the path buffer. */
4766 if (line == &view->line[1] && *opt_path) {
4767 pop_tree_stack_entry();
4769 } else {
4770 const char *basename = tree_path(line);
4772 push_tree_stack_entry(basename, view->lineno);
4775 /* Trees and subtrees share the same ID, so they are not not
4776 * unique like blobs. */
4777 flags = OPEN_RELOAD;
4778 request = REQ_VIEW_TREE;
4779 break;
4781 case LINE_TREE_FILE:
4782 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4783 request = REQ_VIEW_BLOB;
4784 break;
4786 default:
4787 return REQ_NONE;
4790 open_view(view, request, flags);
4791 if (request == REQ_VIEW_TREE)
4792 view->lineno = tree_lineno;
4794 return REQ_NONE;
4797 static bool
4798 tree_grep(struct view *view, struct line *line)
4800 struct tree_entry *entry = line->data;
4801 const char *text[] = {
4802 entry->name,
4803 opt_author ? entry->author : "",
4804 mkdate(&entry->time, opt_date),
4805 NULL
4808 return grep_text(view, text);
4811 static void
4812 tree_select(struct view *view, struct line *line)
4814 struct tree_entry *entry = line->data;
4816 if (line->type == LINE_TREE_FILE) {
4817 string_copy_rev(ref_blob, entry->id);
4818 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4820 } else if (line->type != LINE_TREE_DIR) {
4821 return;
4824 string_copy_rev(view->ref, entry->id);
4827 static bool
4828 tree_prepare(struct view *view)
4830 if (view->lines == 0 && opt_prefix[0]) {
4831 char *pos = opt_prefix;
4833 while (pos && *pos) {
4834 char *end = strchr(pos, '/');
4836 if (end)
4837 *end = 0;
4838 push_tree_stack_entry(pos, 0);
4839 pos = end;
4840 if (end) {
4841 *end = '/';
4842 pos++;
4846 } else if (strcmp(view->vid, view->id)) {
4847 opt_path[0] = 0;
4850 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4853 static const char *tree_argv[SIZEOF_ARG] = {
4854 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4857 static struct view_ops tree_ops = {
4858 "file",
4859 tree_argv,
4860 NULL,
4861 tree_read,
4862 tree_draw,
4863 tree_request,
4864 tree_grep,
4865 tree_select,
4866 tree_prepare,
4869 static bool
4870 blob_read(struct view *view, char *line)
4872 if (!line)
4873 return TRUE;
4874 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4877 static enum request
4878 blob_request(struct view *view, enum request request, struct line *line)
4880 switch (request) {
4881 case REQ_EDIT:
4882 open_blob_editor(view->vid);
4883 return REQ_NONE;
4884 default:
4885 return pager_request(view, request, line);
4889 static const char *blob_argv[SIZEOF_ARG] = {
4890 "git", "cat-file", "blob", "%(blob)", NULL
4893 static struct view_ops blob_ops = {
4894 "line",
4895 blob_argv,
4896 NULL,
4897 blob_read,
4898 pager_draw,
4899 blob_request,
4900 pager_grep,
4901 pager_select,
4905 * Blame backend
4907 * Loading the blame view is a two phase job:
4909 * 1. File content is read either using opt_file from the
4910 * filesystem or using git-cat-file.
4911 * 2. Then blame information is incrementally added by
4912 * reading output from git-blame.
4915 struct blame_commit {
4916 char id[SIZEOF_REV]; /* SHA1 ID. */
4917 char title[128]; /* First line of the commit message. */
4918 const char *author; /* Author of the commit. */
4919 struct time time; /* Date from the author ident. */
4920 char filename[128]; /* Name of file. */
4921 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4922 char parent_filename[128]; /* Parent/previous name of file. */
4925 struct blame {
4926 struct blame_commit *commit;
4927 unsigned long lineno;
4928 char text[1];
4931 static bool
4932 blame_open(struct view *view)
4934 char path[SIZEOF_STR];
4935 size_t i;
4937 if (!view->prev && *opt_prefix) {
4938 string_copy(path, opt_file);
4939 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4940 return FALSE;
4943 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4944 const char *blame_cat_file_argv[] = {
4945 "git", "cat-file", "blob", path, NULL
4948 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4949 !start_update(view, blame_cat_file_argv, opt_cdup))
4950 return FALSE;
4953 /* First pass: remove multiple references to the same commit. */
4954 for (i = 0; i < view->lines; i++) {
4955 struct blame *blame = view->line[i].data;
4957 if (blame->commit && blame->commit->id[0])
4958 blame->commit->id[0] = 0;
4959 else
4960 blame->commit = NULL;
4963 /* Second pass: free existing references. */
4964 for (i = 0; i < view->lines; i++) {
4965 struct blame *blame = view->line[i].data;
4967 if (blame->commit)
4968 free(blame->commit);
4971 setup_update(view, opt_file);
4972 string_format(view->ref, "%s ...", opt_file);
4974 return TRUE;
4977 static struct blame_commit *
4978 get_blame_commit(struct view *view, const char *id)
4980 size_t i;
4982 for (i = 0; i < view->lines; i++) {
4983 struct blame *blame = view->line[i].data;
4985 if (!blame->commit)
4986 continue;
4988 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4989 return blame->commit;
4993 struct blame_commit *commit = calloc(1, sizeof(*commit));
4995 if (commit)
4996 string_ncopy(commit->id, id, SIZEOF_REV);
4997 return commit;
5001 static bool
5002 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5004 const char *pos = *posref;
5006 *posref = NULL;
5007 pos = strchr(pos + 1, ' ');
5008 if (!pos || !isdigit(pos[1]))
5009 return FALSE;
5010 *number = atoi(pos + 1);
5011 if (*number < min || *number > max)
5012 return FALSE;
5014 *posref = pos;
5015 return TRUE;
5018 static struct blame_commit *
5019 parse_blame_commit(struct view *view, const char *text, int *blamed)
5021 struct blame_commit *commit;
5022 struct blame *blame;
5023 const char *pos = text + SIZEOF_REV - 2;
5024 size_t orig_lineno = 0;
5025 size_t lineno;
5026 size_t group;
5028 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5029 return NULL;
5031 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5032 !parse_number(&pos, &lineno, 1, view->lines) ||
5033 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5034 return NULL;
5036 commit = get_blame_commit(view, text);
5037 if (!commit)
5038 return NULL;
5040 *blamed += group;
5041 while (group--) {
5042 struct line *line = &view->line[lineno + group - 1];
5044 blame = line->data;
5045 blame->commit = commit;
5046 blame->lineno = orig_lineno + group - 1;
5047 line->dirty = 1;
5050 return commit;
5053 static bool
5054 blame_read_file(struct view *view, const char *line, bool *read_file)
5056 if (!line) {
5057 const char *blame_argv[] = {
5058 "git", "blame", "--incremental",
5059 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5062 if (view->lines == 0 && !view->prev)
5063 die("No blame exist for %s", view->vid);
5065 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5066 report("Failed to load blame data");
5067 return TRUE;
5070 *read_file = FALSE;
5071 return FALSE;
5073 } else {
5074 size_t linelen = strlen(line);
5075 struct blame *blame = malloc(sizeof(*blame) + linelen);
5077 if (!blame)
5078 return FALSE;
5080 blame->commit = NULL;
5081 strncpy(blame->text, line, linelen);
5082 blame->text[linelen] = 0;
5083 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5087 static bool
5088 match_blame_header(const char *name, char **line)
5090 size_t namelen = strlen(name);
5091 bool matched = !strncmp(name, *line, namelen);
5093 if (matched)
5094 *line += namelen;
5096 return matched;
5099 static bool
5100 blame_read(struct view *view, char *line)
5102 static struct blame_commit *commit = NULL;
5103 static int blamed = 0;
5104 static bool read_file = TRUE;
5106 if (read_file)
5107 return blame_read_file(view, line, &read_file);
5109 if (!line) {
5110 /* Reset all! */
5111 commit = NULL;
5112 blamed = 0;
5113 read_file = TRUE;
5114 string_format(view->ref, "%s", view->vid);
5115 if (view_is_displayed(view)) {
5116 update_view_title(view);
5117 redraw_view_from(view, 0);
5119 return TRUE;
5122 if (!commit) {
5123 commit = parse_blame_commit(view, line, &blamed);
5124 string_format(view->ref, "%s %2d%%", view->vid,
5125 view->lines ? blamed * 100 / view->lines : 0);
5127 } else if (match_blame_header("author ", &line)) {
5128 commit->author = get_author(line);
5130 } else if (match_blame_header("author-time ", &line)) {
5131 parse_timesec(&commit->time, line);
5133 } else if (match_blame_header("author-tz ", &line)) {
5134 parse_timezone(&commit->time, line);
5136 } else if (match_blame_header("summary ", &line)) {
5137 string_ncopy(commit->title, line, strlen(line));
5139 } else if (match_blame_header("previous ", &line)) {
5140 if (strlen(line) <= SIZEOF_REV)
5141 return FALSE;
5142 string_copy_rev(commit->parent_id, line);
5143 line += SIZEOF_REV;
5144 string_ncopy(commit->parent_filename, line, strlen(line));
5146 } else if (match_blame_header("filename ", &line)) {
5147 string_ncopy(commit->filename, line, strlen(line));
5148 commit = NULL;
5151 return TRUE;
5154 static bool
5155 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5157 struct blame *blame = line->data;
5158 struct time *time = NULL;
5159 const char *id = NULL, *author = NULL;
5161 if (blame->commit && *blame->commit->filename) {
5162 id = blame->commit->id;
5163 author = blame->commit->author;
5164 time = &blame->commit->time;
5167 if (opt_date && draw_date(view, time))
5168 return TRUE;
5170 if (opt_author && draw_author(view, author))
5171 return TRUE;
5173 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5174 return TRUE;
5176 if (draw_lineno(view, lineno))
5177 return TRUE;
5179 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5180 return TRUE;
5183 static bool
5184 check_blame_commit(struct blame *blame, bool check_null_id)
5186 if (!blame->commit)
5187 report("Commit data not loaded yet");
5188 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5189 report("No commit exist for the selected line");
5190 else
5191 return TRUE;
5192 return FALSE;
5195 static void
5196 setup_blame_parent_line(struct view *view, struct blame *blame)
5198 char from[SIZEOF_REF + SIZEOF_STR];
5199 char to[SIZEOF_REF + SIZEOF_STR];
5200 const char *diff_tree_argv[] = {
5201 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5202 "-U0", from, to, "--", NULL
5204 struct io io;
5205 int parent_lineno = -1;
5206 int blamed_lineno = -1;
5207 char *line;
5209 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5210 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5211 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5212 return;
5214 while ((line = io_get(&io, '\n', TRUE))) {
5215 if (*line == '@') {
5216 char *pos = strchr(line, '+');
5218 parent_lineno = atoi(line + 4);
5219 if (pos)
5220 blamed_lineno = atoi(pos + 1);
5222 } else if (*line == '+' && parent_lineno != -1) {
5223 if (blame->lineno == blamed_lineno - 1 &&
5224 !strcmp(blame->text, line + 1)) {
5225 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5226 break;
5228 blamed_lineno++;
5232 io_done(&io);
5235 static enum request
5236 blame_request(struct view *view, enum request request, struct line *line)
5238 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5239 struct blame *blame = line->data;
5241 switch (request) {
5242 case REQ_VIEW_BLAME:
5243 if (check_blame_commit(blame, TRUE)) {
5244 string_copy(opt_ref, blame->commit->id);
5245 string_copy(opt_file, blame->commit->filename);
5246 if (blame->lineno)
5247 view->lineno = blame->lineno;
5248 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5250 break;
5252 case REQ_PARENT:
5253 if (!check_blame_commit(blame, TRUE))
5254 break;
5255 if (!*blame->commit->parent_id) {
5256 report("The selected commit has no parents");
5257 } else {
5258 string_copy_rev(opt_ref, blame->commit->parent_id);
5259 string_copy(opt_file, blame->commit->parent_filename);
5260 setup_blame_parent_line(view, blame);
5261 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5263 break;
5265 case REQ_ENTER:
5266 if (!check_blame_commit(blame, FALSE))
5267 break;
5269 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5270 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5271 break;
5273 if (!strcmp(blame->commit->id, NULL_ID)) {
5274 struct view *diff = VIEW(REQ_VIEW_DIFF);
5275 const char *diff_index_argv[] = {
5276 "git", "diff-index", "--root", "--patch-with-stat",
5277 "-C", "-M", "HEAD", "--", view->vid, NULL
5280 if (!*blame->commit->parent_id) {
5281 diff_index_argv[1] = "diff";
5282 diff_index_argv[2] = "--no-color";
5283 diff_index_argv[6] = "--";
5284 diff_index_argv[7] = "/dev/null";
5287 if (!prepare_update(diff, diff_index_argv, NULL)) {
5288 report("Failed to allocate diff command");
5289 break;
5291 flags |= OPEN_PREPARED;
5294 open_view(view, REQ_VIEW_DIFF, flags);
5295 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5296 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5297 break;
5299 default:
5300 return request;
5303 return REQ_NONE;
5306 static bool
5307 blame_grep(struct view *view, struct line *line)
5309 struct blame *blame = line->data;
5310 struct blame_commit *commit = blame->commit;
5311 const char *text[] = {
5312 blame->text,
5313 commit ? commit->title : "",
5314 commit ? commit->id : "",
5315 commit && opt_author ? commit->author : "",
5316 commit ? mkdate(&commit->time, opt_date) : "",
5317 NULL
5320 return grep_text(view, text);
5323 static void
5324 blame_select(struct view *view, struct line *line)
5326 struct blame *blame = line->data;
5327 struct blame_commit *commit = blame->commit;
5329 if (!commit)
5330 return;
5332 if (!strcmp(commit->id, NULL_ID))
5333 string_ncopy(ref_commit, "HEAD", 4);
5334 else
5335 string_copy_rev(ref_commit, commit->id);
5338 static struct view_ops blame_ops = {
5339 "line",
5340 NULL,
5341 blame_open,
5342 blame_read,
5343 blame_draw,
5344 blame_request,
5345 blame_grep,
5346 blame_select,
5350 * Branch backend
5353 struct branch {
5354 const char *author; /* Author of the last commit. */
5355 struct time time; /* Date of the last activity. */
5356 const struct ref *ref; /* Name and commit ID information. */
5359 static const struct ref branch_all;
5361 static const enum sort_field branch_sort_fields[] = {
5362 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5364 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5366 static int
5367 branch_compare(const void *l1, const void *l2)
5369 const struct branch *branch1 = ((const struct line *) l1)->data;
5370 const struct branch *branch2 = ((const struct line *) l2)->data;
5372 switch (get_sort_field(branch_sort_state)) {
5373 case ORDERBY_DATE:
5374 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5376 case ORDERBY_AUTHOR:
5377 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5379 case ORDERBY_NAME:
5380 default:
5381 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5385 static bool
5386 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5388 struct branch *branch = line->data;
5389 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5391 if (opt_date && draw_date(view, &branch->time))
5392 return TRUE;
5394 if (opt_author && draw_author(view, branch->author))
5395 return TRUE;
5397 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5398 return TRUE;
5401 static enum request
5402 branch_request(struct view *view, enum request request, struct line *line)
5404 struct branch *branch = line->data;
5406 switch (request) {
5407 case REQ_REFRESH:
5408 load_refs();
5409 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5410 return REQ_NONE;
5412 case REQ_TOGGLE_SORT_FIELD:
5413 case REQ_TOGGLE_SORT_ORDER:
5414 sort_view(view, request, &branch_sort_state, branch_compare);
5415 return REQ_NONE;
5417 case REQ_ENTER:
5419 const struct ref *ref = branch->ref;
5420 const char *all_branches_argv[] = {
5421 "git", "log", "--no-color", "--pretty=raw", "--parents",
5422 "--topo-order",
5423 ref == &branch_all ? "--all" : ref->name, NULL
5425 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5427 if (!prepare_update(main_view, all_branches_argv, NULL))
5428 report("Failed to load view of all branches");
5429 else
5430 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5431 return REQ_NONE;
5433 default:
5434 return request;
5438 static bool
5439 branch_read(struct view *view, char *line)
5441 static char id[SIZEOF_REV];
5442 struct branch *reference;
5443 size_t i;
5445 if (!line)
5446 return TRUE;
5448 switch (get_line_type(line)) {
5449 case LINE_COMMIT:
5450 string_copy_rev(id, line + STRING_SIZE("commit "));
5451 return TRUE;
5453 case LINE_AUTHOR:
5454 for (i = 0, reference = NULL; i < view->lines; i++) {
5455 struct branch *branch = view->line[i].data;
5457 if (strcmp(branch->ref->id, id))
5458 continue;
5460 view->line[i].dirty = TRUE;
5461 if (reference) {
5462 branch->author = reference->author;
5463 branch->time = reference->time;
5464 continue;
5467 parse_author_line(line + STRING_SIZE("author "),
5468 &branch->author, &branch->time);
5469 reference = branch;
5471 return TRUE;
5473 default:
5474 return TRUE;
5479 static bool
5480 branch_open_visitor(void *data, const struct ref *ref)
5482 struct view *view = data;
5483 struct branch *branch;
5485 if (ref->tag || ref->ltag || ref->remote)
5486 return TRUE;
5488 branch = calloc(1, sizeof(*branch));
5489 if (!branch)
5490 return FALSE;
5492 branch->ref = ref;
5493 return !!add_line_data(view, branch, LINE_DEFAULT);
5496 static bool
5497 branch_open(struct view *view)
5499 const char *branch_log[] = {
5500 "git", "log", "--no-color", "--pretty=raw",
5501 "--simplify-by-decoration", "--all", NULL
5504 if (!start_update(view, branch_log, NULL)) {
5505 report("Failed to load branch data");
5506 return TRUE;
5509 setup_update(view, view->id);
5510 branch_open_visitor(view, &branch_all);
5511 foreach_ref(branch_open_visitor, view);
5512 view->p_restore = TRUE;
5514 return TRUE;
5517 static bool
5518 branch_grep(struct view *view, struct line *line)
5520 struct branch *branch = line->data;
5521 const char *text[] = {
5522 branch->ref->name,
5523 branch->author,
5524 NULL
5527 return grep_text(view, text);
5530 static void
5531 branch_select(struct view *view, struct line *line)
5533 struct branch *branch = line->data;
5535 string_copy_rev(view->ref, branch->ref->id);
5536 string_copy_rev(ref_commit, branch->ref->id);
5537 string_copy_rev(ref_head, branch->ref->id);
5538 string_copy_rev(ref_branch, branch->ref->name);
5541 static struct view_ops branch_ops = {
5542 "branch",
5543 NULL,
5544 branch_open,
5545 branch_read,
5546 branch_draw,
5547 branch_request,
5548 branch_grep,
5549 branch_select,
5553 * Status backend
5556 struct status {
5557 char status;
5558 struct {
5559 mode_t mode;
5560 char rev[SIZEOF_REV];
5561 char name[SIZEOF_STR];
5562 } old;
5563 struct {
5564 mode_t mode;
5565 char rev[SIZEOF_REV];
5566 char name[SIZEOF_STR];
5567 } new;
5570 static char status_onbranch[SIZEOF_STR];
5571 static struct status stage_status;
5572 static enum line_type stage_line_type;
5573 static size_t stage_chunks;
5574 static int *stage_chunk;
5576 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5578 /* This should work even for the "On branch" line. */
5579 static inline bool
5580 status_has_none(struct view *view, struct line *line)
5582 return line < view->line + view->lines && !line[1].data;
5585 /* Get fields from the diff line:
5586 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5588 static inline bool
5589 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5591 const char *old_mode = buf + 1;
5592 const char *new_mode = buf + 8;
5593 const char *old_rev = buf + 15;
5594 const char *new_rev = buf + 56;
5595 const char *status = buf + 97;
5597 if (bufsize < 98 ||
5598 old_mode[-1] != ':' ||
5599 new_mode[-1] != ' ' ||
5600 old_rev[-1] != ' ' ||
5601 new_rev[-1] != ' ' ||
5602 status[-1] != ' ')
5603 return FALSE;
5605 file->status = *status;
5607 string_copy_rev(file->old.rev, old_rev);
5608 string_copy_rev(file->new.rev, new_rev);
5610 file->old.mode = strtoul(old_mode, NULL, 8);
5611 file->new.mode = strtoul(new_mode, NULL, 8);
5613 file->old.name[0] = file->new.name[0] = 0;
5615 return TRUE;
5618 static bool
5619 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5621 struct status *unmerged = NULL;
5622 char *buf;
5623 struct io io;
5625 if (!io_run(&io, IO_RD, opt_cdup, argv))
5626 return FALSE;
5628 add_line_data(view, NULL, type);
5630 while ((buf = io_get(&io, 0, TRUE))) {
5631 struct status *file = unmerged;
5633 if (!file) {
5634 file = calloc(1, sizeof(*file));
5635 if (!file || !add_line_data(view, file, type))
5636 goto error_out;
5639 /* Parse diff info part. */
5640 if (status) {
5641 file->status = status;
5642 if (status == 'A')
5643 string_copy(file->old.rev, NULL_ID);
5645 } else if (!file->status || file == unmerged) {
5646 if (!status_get_diff(file, buf, strlen(buf)))
5647 goto error_out;
5649 buf = io_get(&io, 0, TRUE);
5650 if (!buf)
5651 break;
5653 /* Collapse all modified entries that follow an
5654 * associated unmerged entry. */
5655 if (unmerged == file) {
5656 unmerged->status = 'U';
5657 unmerged = NULL;
5658 } else if (file->status == 'U') {
5659 unmerged = file;
5663 /* Grab the old name for rename/copy. */
5664 if (!*file->old.name &&
5665 (file->status == 'R' || file->status == 'C')) {
5666 string_ncopy(file->old.name, buf, strlen(buf));
5668 buf = io_get(&io, 0, TRUE);
5669 if (!buf)
5670 break;
5673 /* git-ls-files just delivers a NUL separated list of
5674 * file names similar to the second half of the
5675 * git-diff-* output. */
5676 string_ncopy(file->new.name, buf, strlen(buf));
5677 if (!*file->old.name)
5678 string_copy(file->old.name, file->new.name);
5679 file = NULL;
5682 if (io_error(&io)) {
5683 error_out:
5684 io_done(&io);
5685 return FALSE;
5688 if (!view->line[view->lines - 1].data)
5689 add_line_data(view, NULL, LINE_STAT_NONE);
5691 io_done(&io);
5692 return TRUE;
5695 /* Don't show unmerged entries in the staged section. */
5696 static const char *status_diff_index_argv[] = {
5697 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5698 "--cached", "-M", "HEAD", NULL
5701 static const char *status_diff_files_argv[] = {
5702 "git", "diff-files", "-z", NULL
5705 static const char *status_list_other_argv[] = {
5706 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5709 static const char *status_list_no_head_argv[] = {
5710 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5713 static const char *update_index_argv[] = {
5714 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5717 /* Restore the previous line number to stay in the context or select a
5718 * line with something that can be updated. */
5719 static void
5720 status_restore(struct view *view)
5722 if (view->p_lineno >= view->lines)
5723 view->p_lineno = view->lines - 1;
5724 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5725 view->p_lineno++;
5726 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5727 view->p_lineno--;
5729 /* If the above fails, always skip the "On branch" line. */
5730 if (view->p_lineno < view->lines)
5731 view->lineno = view->p_lineno;
5732 else
5733 view->lineno = 1;
5735 if (view->lineno < view->offset)
5736 view->offset = view->lineno;
5737 else if (view->offset + view->height <= view->lineno)
5738 view->offset = view->lineno - view->height + 1;
5740 view->p_restore = FALSE;
5743 static void
5744 status_update_onbranch(void)
5746 static const char *paths[][2] = {
5747 { "rebase-apply/rebasing", "Rebasing" },
5748 { "rebase-apply/applying", "Applying mailbox" },
5749 { "rebase-apply/", "Rebasing mailbox" },
5750 { "rebase-merge/interactive", "Interactive rebase" },
5751 { "rebase-merge/", "Rebase merge" },
5752 { "MERGE_HEAD", "Merging" },
5753 { "BISECT_LOG", "Bisecting" },
5754 { "HEAD", "On branch" },
5756 char buf[SIZEOF_STR];
5757 struct stat stat;
5758 int i;
5760 if (is_initial_commit()) {
5761 string_copy(status_onbranch, "Initial commit");
5762 return;
5765 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5766 char *head = opt_head;
5768 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5769 lstat(buf, &stat) < 0)
5770 continue;
5772 if (!*opt_head) {
5773 struct io io;
5775 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5776 io_read_buf(&io, buf, sizeof(buf))) {
5777 head = buf;
5778 if (!prefixcmp(head, "refs/heads/"))
5779 head += STRING_SIZE("refs/heads/");
5783 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5784 string_copy(status_onbranch, opt_head);
5785 return;
5788 string_copy(status_onbranch, "Not currently on any branch");
5791 /* First parse staged info using git-diff-index(1), then parse unstaged
5792 * info using git-diff-files(1), and finally untracked files using
5793 * git-ls-files(1). */
5794 static bool
5795 status_open(struct view *view)
5797 reset_view(view);
5799 add_line_data(view, NULL, LINE_STAT_HEAD);
5800 status_update_onbranch();
5802 io_run_bg(update_index_argv);
5804 if (is_initial_commit()) {
5805 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5806 return FALSE;
5807 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5808 return FALSE;
5811 if (!opt_untracked_dirs_content)
5812 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5814 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5815 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5816 return FALSE;
5818 /* Restore the exact position or use the specialized restore
5819 * mode? */
5820 if (!view->p_restore)
5821 status_restore(view);
5822 return TRUE;
5825 static bool
5826 status_draw(struct view *view, struct line *line, unsigned int lineno)
5828 struct status *status = line->data;
5829 enum line_type type;
5830 const char *text;
5832 if (!status) {
5833 switch (line->type) {
5834 case LINE_STAT_STAGED:
5835 type = LINE_STAT_SECTION;
5836 text = "Changes to be committed:";
5837 break;
5839 case LINE_STAT_UNSTAGED:
5840 type = LINE_STAT_SECTION;
5841 text = "Changed but not updated:";
5842 break;
5844 case LINE_STAT_UNTRACKED:
5845 type = LINE_STAT_SECTION;
5846 text = "Untracked files:";
5847 break;
5849 case LINE_STAT_NONE:
5850 type = LINE_DEFAULT;
5851 text = " (no files)";
5852 break;
5854 case LINE_STAT_HEAD:
5855 type = LINE_STAT_HEAD;
5856 text = status_onbranch;
5857 break;
5859 default:
5860 return FALSE;
5862 } else {
5863 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5865 buf[0] = status->status;
5866 if (draw_text(view, line->type, buf, TRUE))
5867 return TRUE;
5868 type = LINE_DEFAULT;
5869 text = status->new.name;
5872 draw_text(view, type, text, TRUE);
5873 return TRUE;
5876 static enum request
5877 status_load_error(struct view *view, struct view *stage, const char *path)
5879 if (displayed_views() == 2 || display[current_view] != view)
5880 maximize_view(view);
5881 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5882 return REQ_NONE;
5885 static enum request
5886 status_enter(struct view *view, struct line *line)
5888 struct status *status = line->data;
5889 const char *oldpath = status ? status->old.name : NULL;
5890 /* Diffs for unmerged entries are empty when passing the new
5891 * path, so leave it empty. */
5892 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5893 const char *info;
5894 enum open_flags split;
5895 struct view *stage = VIEW(REQ_VIEW_STAGE);
5897 if (line->type == LINE_STAT_NONE ||
5898 (!status && line[1].type == LINE_STAT_NONE)) {
5899 report("No file to diff");
5900 return REQ_NONE;
5903 switch (line->type) {
5904 case LINE_STAT_STAGED:
5905 if (is_initial_commit()) {
5906 const char *no_head_diff_argv[] = {
5907 "git", "diff", "--no-color", "--patch-with-stat",
5908 "--", "/dev/null", newpath, NULL
5911 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5912 return status_load_error(view, stage, newpath);
5913 } else {
5914 const char *index_show_argv[] = {
5915 "git", "diff-index", "--root", "--patch-with-stat",
5916 "-C", "-M", "--cached", "HEAD", "--",
5917 oldpath, newpath, NULL
5920 if (!prepare_update(stage, index_show_argv, opt_cdup))
5921 return status_load_error(view, stage, newpath);
5924 if (status)
5925 info = "Staged changes to %s";
5926 else
5927 info = "Staged changes";
5928 break;
5930 case LINE_STAT_UNSTAGED:
5932 const char *files_show_argv[] = {
5933 "git", "diff-files", "--root", "--patch-with-stat",
5934 "-C", "-M", "--", oldpath, newpath, NULL
5937 if (!prepare_update(stage, files_show_argv, opt_cdup))
5938 return status_load_error(view, stage, newpath);
5939 if (status)
5940 info = "Unstaged changes to %s";
5941 else
5942 info = "Unstaged changes";
5943 break;
5945 case LINE_STAT_UNTRACKED:
5946 if (!newpath) {
5947 report("No file to show");
5948 return REQ_NONE;
5951 if (!suffixcmp(status->new.name, -1, "/")) {
5952 report("Cannot display a directory");
5953 return REQ_NONE;
5956 if (!prepare_update_file(stage, newpath))
5957 return status_load_error(view, stage, newpath);
5958 info = "Untracked file %s";
5959 break;
5961 case LINE_STAT_HEAD:
5962 return REQ_NONE;
5964 default:
5965 die("line type %d not handled in switch", line->type);
5968 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5969 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5970 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5971 if (status) {
5972 stage_status = *status;
5973 } else {
5974 memset(&stage_status, 0, sizeof(stage_status));
5977 stage_line_type = line->type;
5978 stage_chunks = 0;
5979 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5982 return REQ_NONE;
5985 static bool
5986 status_exists(struct status *status, enum line_type type)
5988 struct view *view = VIEW(REQ_VIEW_STATUS);
5989 unsigned long lineno;
5991 for (lineno = 0; lineno < view->lines; lineno++) {
5992 struct line *line = &view->line[lineno];
5993 struct status *pos = line->data;
5995 if (line->type != type)
5996 continue;
5997 if (!pos && (!status || !status->status) && line[1].data) {
5998 select_view_line(view, lineno);
5999 return TRUE;
6001 if (pos && !strcmp(status->new.name, pos->new.name)) {
6002 select_view_line(view, lineno);
6003 return TRUE;
6007 return FALSE;
6011 static bool
6012 status_update_prepare(struct io *io, enum line_type type)
6014 const char *staged_argv[] = {
6015 "git", "update-index", "-z", "--index-info", NULL
6017 const char *others_argv[] = {
6018 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6021 switch (type) {
6022 case LINE_STAT_STAGED:
6023 return io_run(io, IO_WR, opt_cdup, staged_argv);
6025 case LINE_STAT_UNSTAGED:
6026 case LINE_STAT_UNTRACKED:
6027 return io_run(io, IO_WR, opt_cdup, others_argv);
6029 default:
6030 die("line type %d not handled in switch", type);
6031 return FALSE;
6035 static bool
6036 status_update_write(struct io *io, struct status *status, enum line_type type)
6038 char buf[SIZEOF_STR];
6039 size_t bufsize = 0;
6041 switch (type) {
6042 case LINE_STAT_STAGED:
6043 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6044 status->old.mode,
6045 status->old.rev,
6046 status->old.name, 0))
6047 return FALSE;
6048 break;
6050 case LINE_STAT_UNSTAGED:
6051 case LINE_STAT_UNTRACKED:
6052 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6053 return FALSE;
6054 break;
6056 default:
6057 die("line type %d not handled in switch", type);
6060 return io_write(io, buf, bufsize);
6063 static bool
6064 status_update_file(struct status *status, enum line_type type)
6066 struct io io;
6067 bool result;
6069 if (!status_update_prepare(&io, type))
6070 return FALSE;
6072 result = status_update_write(&io, status, type);
6073 return io_done(&io) && result;
6076 static bool
6077 status_update_files(struct view *view, struct line *line)
6079 char buf[sizeof(view->ref)];
6080 struct io io;
6081 bool result = TRUE;
6082 struct line *pos = view->line + view->lines;
6083 int files = 0;
6084 int file, done;
6085 int cursor_y = -1, cursor_x = -1;
6087 if (!status_update_prepare(&io, line->type))
6088 return FALSE;
6090 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6091 files++;
6093 string_copy(buf, view->ref);
6094 getsyx(cursor_y, cursor_x);
6095 for (file = 0, done = 5; result && file < files; line++, file++) {
6096 int almost_done = file * 100 / files;
6098 if (almost_done > done) {
6099 done = almost_done;
6100 string_format(view->ref, "updating file %u of %u (%d%% done)",
6101 file, files, done);
6102 update_view_title(view);
6103 setsyx(cursor_y, cursor_x);
6104 doupdate();
6106 result = status_update_write(&io, line->data, line->type);
6108 string_copy(view->ref, buf);
6110 return io_done(&io) && result;
6113 static bool
6114 status_update(struct view *view)
6116 struct line *line = &view->line[view->lineno];
6118 assert(view->lines);
6120 if (!line->data) {
6121 /* This should work even for the "On branch" line. */
6122 if (line < view->line + view->lines && !line[1].data) {
6123 report("Nothing to update");
6124 return FALSE;
6127 if (!status_update_files(view, line + 1)) {
6128 report("Failed to update file status");
6129 return FALSE;
6132 } else if (!status_update_file(line->data, line->type)) {
6133 report("Failed to update file status");
6134 return FALSE;
6137 return TRUE;
6140 static bool
6141 status_revert(struct status *status, enum line_type type, bool has_none)
6143 if (!status || type != LINE_STAT_UNSTAGED) {
6144 if (type == LINE_STAT_STAGED) {
6145 report("Cannot revert changes to staged files");
6146 } else if (type == LINE_STAT_UNTRACKED) {
6147 report("Cannot revert changes to untracked files");
6148 } else if (has_none) {
6149 report("Nothing to revert");
6150 } else {
6151 report("Cannot revert changes to multiple files");
6154 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6155 char mode[10] = "100644";
6156 const char *reset_argv[] = {
6157 "git", "update-index", "--cacheinfo", mode,
6158 status->old.rev, status->old.name, NULL
6160 const char *checkout_argv[] = {
6161 "git", "checkout", "--", status->old.name, NULL
6164 if (status->status == 'U') {
6165 string_format(mode, "%5o", status->old.mode);
6167 if (status->old.mode == 0 && status->new.mode == 0) {
6168 reset_argv[2] = "--force-remove";
6169 reset_argv[3] = status->old.name;
6170 reset_argv[4] = NULL;
6173 if (!io_run_fg(reset_argv, opt_cdup))
6174 return FALSE;
6175 if (status->old.mode == 0 && status->new.mode == 0)
6176 return TRUE;
6179 return io_run_fg(checkout_argv, opt_cdup);
6182 return FALSE;
6185 static enum request
6186 status_request(struct view *view, enum request request, struct line *line)
6188 struct status *status = line->data;
6190 switch (request) {
6191 case REQ_STATUS_UPDATE:
6192 if (!status_update(view))
6193 return REQ_NONE;
6194 break;
6196 case REQ_STATUS_REVERT:
6197 if (!status_revert(status, line->type, status_has_none(view, line)))
6198 return REQ_NONE;
6199 break;
6201 case REQ_STATUS_MERGE:
6202 if (!status || status->status != 'U') {
6203 report("Merging only possible for files with unmerged status ('U').");
6204 return REQ_NONE;
6206 open_mergetool(status->new.name);
6207 break;
6209 case REQ_EDIT:
6210 if (!status)
6211 return request;
6212 if (status->status == 'D') {
6213 report("File has been deleted.");
6214 return REQ_NONE;
6217 open_editor(status->new.name);
6218 break;
6220 case REQ_VIEW_BLAME:
6221 if (status)
6222 opt_ref[0] = 0;
6223 return request;
6225 case REQ_ENTER:
6226 /* After returning the status view has been split to
6227 * show the stage view. No further reloading is
6228 * necessary. */
6229 return status_enter(view, line);
6231 case REQ_REFRESH:
6232 /* Simply reload the view. */
6233 break;
6235 default:
6236 return request;
6239 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6241 return REQ_NONE;
6244 static void
6245 status_select(struct view *view, struct line *line)
6247 struct status *status = line->data;
6248 char file[SIZEOF_STR] = "all files";
6249 const char *text;
6250 const char *key;
6252 if (status && !string_format(file, "'%s'", status->new.name))
6253 return;
6255 if (!status && line[1].type == LINE_STAT_NONE)
6256 line++;
6258 switch (line->type) {
6259 case LINE_STAT_STAGED:
6260 text = "Press %s to unstage %s for commit";
6261 break;
6263 case LINE_STAT_UNSTAGED:
6264 text = "Press %s to stage %s for commit";
6265 break;
6267 case LINE_STAT_UNTRACKED:
6268 text = "Press %s to stage %s for addition";
6269 break;
6271 case LINE_STAT_HEAD:
6272 case LINE_STAT_NONE:
6273 text = "Nothing to update";
6274 break;
6276 default:
6277 die("line type %d not handled in switch", line->type);
6280 if (status && status->status == 'U') {
6281 text = "Press %s to resolve conflict in %s";
6282 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6284 } else {
6285 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6288 string_format(view->ref, text, key, file);
6289 if (status)
6290 string_copy(opt_file, status->new.name);
6293 static bool
6294 status_grep(struct view *view, struct line *line)
6296 struct status *status = line->data;
6298 if (status) {
6299 const char buf[2] = { status->status, 0 };
6300 const char *text[] = { status->new.name, buf, NULL };
6302 return grep_text(view, text);
6305 return FALSE;
6308 static struct view_ops status_ops = {
6309 "file",
6310 NULL,
6311 status_open,
6312 NULL,
6313 status_draw,
6314 status_request,
6315 status_grep,
6316 status_select,
6320 static bool
6321 stage_diff_write(struct io *io, struct line *line, struct line *end)
6323 while (line < end) {
6324 if (!io_write(io, line->data, strlen(line->data)) ||
6325 !io_write(io, "\n", 1))
6326 return FALSE;
6327 line++;
6328 if (line->type == LINE_DIFF_CHUNK ||
6329 line->type == LINE_DIFF_HEADER)
6330 break;
6333 return TRUE;
6336 static struct line *
6337 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6339 for (; view->line < line; line--)
6340 if (line->type == type)
6341 return line;
6343 return NULL;
6346 static bool
6347 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6349 const char *apply_argv[SIZEOF_ARG] = {
6350 "git", "apply", "--whitespace=nowarn", NULL
6352 struct line *diff_hdr;
6353 struct io io;
6354 int argc = 3;
6356 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6357 if (!diff_hdr)
6358 return FALSE;
6360 if (!revert)
6361 apply_argv[argc++] = "--cached";
6362 if (revert || stage_line_type == LINE_STAT_STAGED)
6363 apply_argv[argc++] = "-R";
6364 apply_argv[argc++] = "-";
6365 apply_argv[argc++] = NULL;
6366 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6367 return FALSE;
6369 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6370 !stage_diff_write(&io, chunk, view->line + view->lines))
6371 chunk = NULL;
6373 io_done(&io);
6374 io_run_bg(update_index_argv);
6376 return chunk ? TRUE : FALSE;
6379 static bool
6380 stage_update(struct view *view, struct line *line)
6382 struct line *chunk = NULL;
6384 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6385 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6387 if (chunk) {
6388 if (!stage_apply_chunk(view, chunk, FALSE)) {
6389 report("Failed to apply chunk");
6390 return FALSE;
6393 } else if (!stage_status.status) {
6394 view = VIEW(REQ_VIEW_STATUS);
6396 for (line = view->line; line < view->line + view->lines; line++)
6397 if (line->type == stage_line_type)
6398 break;
6400 if (!status_update_files(view, line + 1)) {
6401 report("Failed to update files");
6402 return FALSE;
6405 } else if (!status_update_file(&stage_status, stage_line_type)) {
6406 report("Failed to update file");
6407 return FALSE;
6410 return TRUE;
6413 static bool
6414 stage_revert(struct view *view, struct line *line)
6416 struct line *chunk = NULL;
6418 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6419 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6421 if (chunk) {
6422 if (!prompt_yesno("Are you sure you want to revert changes?"))
6423 return FALSE;
6425 if (!stage_apply_chunk(view, chunk, TRUE)) {
6426 report("Failed to revert chunk");
6427 return FALSE;
6429 return TRUE;
6431 } else {
6432 return status_revert(stage_status.status ? &stage_status : NULL,
6433 stage_line_type, FALSE);
6438 static void
6439 stage_next(struct view *view, struct line *line)
6441 int i;
6443 if (!stage_chunks) {
6444 for (line = view->line; line < view->line + view->lines; line++) {
6445 if (line->type != LINE_DIFF_CHUNK)
6446 continue;
6448 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6449 report("Allocation failure");
6450 return;
6453 stage_chunk[stage_chunks++] = line - view->line;
6457 for (i = 0; i < stage_chunks; i++) {
6458 if (stage_chunk[i] > view->lineno) {
6459 do_scroll_view(view, stage_chunk[i] - view->lineno);
6460 report("Chunk %d of %d", i + 1, stage_chunks);
6461 return;
6465 report("No next chunk found");
6468 static enum request
6469 stage_request(struct view *view, enum request request, struct line *line)
6471 switch (request) {
6472 case REQ_STATUS_UPDATE:
6473 if (!stage_update(view, line))
6474 return REQ_NONE;
6475 break;
6477 case REQ_STATUS_REVERT:
6478 if (!stage_revert(view, line))
6479 return REQ_NONE;
6480 break;
6482 case REQ_STAGE_NEXT:
6483 if (stage_line_type == LINE_STAT_UNTRACKED) {
6484 report("File is untracked; press %s to add",
6485 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6486 return REQ_NONE;
6488 stage_next(view, line);
6489 return REQ_NONE;
6491 case REQ_EDIT:
6492 if (!stage_status.new.name[0])
6493 return request;
6494 if (stage_status.status == 'D') {
6495 report("File has been deleted.");
6496 return REQ_NONE;
6499 open_editor(stage_status.new.name);
6500 break;
6502 case REQ_REFRESH:
6503 /* Reload everything ... */
6504 break;
6506 case REQ_VIEW_BLAME:
6507 if (stage_status.new.name[0]) {
6508 string_copy(opt_file, stage_status.new.name);
6509 opt_ref[0] = 0;
6511 return request;
6513 case REQ_ENTER:
6514 return pager_request(view, request, line);
6516 default:
6517 return request;
6520 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6521 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6523 /* Check whether the staged entry still exists, and close the
6524 * stage view if it doesn't. */
6525 if (!status_exists(&stage_status, stage_line_type)) {
6526 status_restore(VIEW(REQ_VIEW_STATUS));
6527 return REQ_VIEW_CLOSE;
6530 if (stage_line_type == LINE_STAT_UNTRACKED) {
6531 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6532 report("Cannot display a directory");
6533 return REQ_NONE;
6536 if (!prepare_update_file(view, stage_status.new.name)) {
6537 report("Failed to open file: %s", strerror(errno));
6538 return REQ_NONE;
6541 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6543 return REQ_NONE;
6546 static struct view_ops stage_ops = {
6547 "line",
6548 NULL,
6549 NULL,
6550 pager_read,
6551 pager_draw,
6552 stage_request,
6553 pager_grep,
6554 pager_select,
6559 * Revision graph
6562 struct commit {
6563 char id[SIZEOF_REV]; /* SHA1 ID. */
6564 char title[128]; /* First line of the commit message. */
6565 const char *author; /* Author of the commit. */
6566 struct time time; /* Date from the author ident. */
6567 struct ref_list *refs; /* Repository references. */
6568 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6569 size_t graph_size; /* The width of the graph array. */
6570 bool has_parents; /* Rewritten --parents seen. */
6573 /* Size of rev graph with no "padding" columns */
6574 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6576 struct rev_graph {
6577 struct rev_graph *prev, *next, *parents;
6578 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6579 size_t size;
6580 struct commit *commit;
6581 size_t pos;
6582 unsigned int boundary:1;
6585 /* Parents of the commit being visualized. */
6586 static struct rev_graph graph_parents[4];
6588 /* The current stack of revisions on the graph. */
6589 static struct rev_graph graph_stacks[4] = {
6590 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6591 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6592 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6593 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6596 static inline bool
6597 graph_parent_is_merge(struct rev_graph *graph)
6599 return graph->parents->size > 1;
6602 static inline void
6603 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6605 struct commit *commit = graph->commit;
6607 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6608 commit->graph[commit->graph_size++] = symbol;
6611 static void
6612 clear_rev_graph(struct rev_graph *graph)
6614 graph->boundary = 0;
6615 graph->size = graph->pos = 0;
6616 graph->commit = NULL;
6617 memset(graph->parents, 0, sizeof(*graph->parents));
6620 static void
6621 done_rev_graph(struct rev_graph *graph)
6623 if (graph_parent_is_merge(graph) &&
6624 graph->pos < graph->size - 1 &&
6625 graph->next->size == graph->size + graph->parents->size - 1) {
6626 size_t i = graph->pos + graph->parents->size - 1;
6628 graph->commit->graph_size = i * 2;
6629 while (i < graph->next->size - 1) {
6630 append_to_rev_graph(graph, ' ');
6631 append_to_rev_graph(graph, '\\');
6632 i++;
6636 clear_rev_graph(graph);
6639 static void
6640 push_rev_graph(struct rev_graph *graph, const char *parent)
6642 int i;
6644 /* "Collapse" duplicate parents lines.
6646 * FIXME: This needs to also update update the drawn graph but
6647 * for now it just serves as a method for pruning graph lines. */
6648 for (i = 0; i < graph->size; i++)
6649 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6650 return;
6652 if (graph->size < SIZEOF_REVITEMS) {
6653 string_copy_rev(graph->rev[graph->size++], parent);
6657 static chtype
6658 get_rev_graph_symbol(struct rev_graph *graph)
6660 chtype symbol;
6662 if (graph->boundary)
6663 symbol = REVGRAPH_BOUND;
6664 else if (graph->parents->size == 0)
6665 symbol = REVGRAPH_INIT;
6666 else if (graph_parent_is_merge(graph))
6667 symbol = REVGRAPH_MERGE;
6668 else if (graph->pos >= graph->size)
6669 symbol = REVGRAPH_BRANCH;
6670 else
6671 symbol = REVGRAPH_COMMIT;
6673 return symbol;
6676 static void
6677 draw_rev_graph(struct rev_graph *graph)
6679 struct rev_filler {
6680 chtype separator, line;
6682 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6683 static struct rev_filler fillers[] = {
6684 { ' ', '|' },
6685 { '`', '.' },
6686 { '\'', ' ' },
6687 { '/', ' ' },
6689 chtype symbol = get_rev_graph_symbol(graph);
6690 struct rev_filler *filler;
6691 size_t i;
6693 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6694 filler = &fillers[DEFAULT];
6696 for (i = 0; i < graph->pos; i++) {
6697 append_to_rev_graph(graph, filler->line);
6698 if (graph_parent_is_merge(graph->prev) &&
6699 graph->prev->pos == i)
6700 filler = &fillers[RSHARP];
6702 append_to_rev_graph(graph, filler->separator);
6705 /* Place the symbol for this revision. */
6706 append_to_rev_graph(graph, symbol);
6708 if (graph->prev->size > graph->size)
6709 filler = &fillers[RDIAG];
6710 else
6711 filler = &fillers[DEFAULT];
6713 i++;
6715 for (; i < graph->size; i++) {
6716 append_to_rev_graph(graph, filler->separator);
6717 append_to_rev_graph(graph, filler->line);
6718 if (graph_parent_is_merge(graph->prev) &&
6719 i < graph->prev->pos + graph->parents->size)
6720 filler = &fillers[RSHARP];
6721 if (graph->prev->size > graph->size)
6722 filler = &fillers[LDIAG];
6725 if (graph->prev->size > graph->size) {
6726 append_to_rev_graph(graph, filler->separator);
6727 if (filler->line != ' ')
6728 append_to_rev_graph(graph, filler->line);
6732 /* Prepare the next rev graph */
6733 static void
6734 prepare_rev_graph(struct rev_graph *graph)
6736 size_t i;
6738 /* First, traverse all lines of revisions up to the active one. */
6739 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6740 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6741 break;
6743 push_rev_graph(graph->next, graph->rev[graph->pos]);
6746 /* Interleave the new revision parent(s). */
6747 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6748 push_rev_graph(graph->next, graph->parents->rev[i]);
6750 /* Lastly, put any remaining revisions. */
6751 for (i = graph->pos + 1; i < graph->size; i++)
6752 push_rev_graph(graph->next, graph->rev[i]);
6755 static void
6756 update_rev_graph(struct view *view, struct rev_graph *graph)
6758 /* If this is the finalizing update ... */
6759 if (graph->commit)
6760 prepare_rev_graph(graph);
6762 /* Graph visualization needs a one rev look-ahead,
6763 * so the first update doesn't visualize anything. */
6764 if (!graph->prev->commit)
6765 return;
6767 if (view->lines > 2)
6768 view->line[view->lines - 3].dirty = 1;
6769 if (view->lines > 1)
6770 view->line[view->lines - 2].dirty = 1;
6771 draw_rev_graph(graph->prev);
6772 done_rev_graph(graph->prev->prev);
6777 * Main view backend
6780 static const char *main_argv[SIZEOF_ARG] = {
6781 "git", "log", "--no-color", "--pretty=raw", "--parents",
6782 "--topo-order", "%(diffargs)", "%(revargs)",
6783 "--", "%(fileargs)", NULL
6786 static bool
6787 main_draw(struct view *view, struct line *line, unsigned int lineno)
6789 struct commit *commit = line->data;
6791 if (!commit->author)
6792 return FALSE;
6794 if (opt_date && draw_date(view, &commit->time))
6795 return TRUE;
6797 if (opt_author && draw_author(view, commit->author))
6798 return TRUE;
6800 if (opt_rev_graph && commit->graph_size &&
6801 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6802 return TRUE;
6804 if (opt_show_refs && commit->refs) {
6805 size_t i;
6807 for (i = 0; i < commit->refs->size; i++) {
6808 struct ref *ref = commit->refs->refs[i];
6809 enum line_type type;
6811 if (ref->head)
6812 type = LINE_MAIN_HEAD;
6813 else if (ref->ltag)
6814 type = LINE_MAIN_LOCAL_TAG;
6815 else if (ref->tag)
6816 type = LINE_MAIN_TAG;
6817 else if (ref->tracked)
6818 type = LINE_MAIN_TRACKED;
6819 else if (ref->remote)
6820 type = LINE_MAIN_REMOTE;
6821 else
6822 type = LINE_MAIN_REF;
6824 if (draw_text(view, type, "[", TRUE) ||
6825 draw_text(view, type, ref->name, TRUE) ||
6826 draw_text(view, type, "]", TRUE))
6827 return TRUE;
6829 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6830 return TRUE;
6834 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6835 return TRUE;
6838 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6839 static bool
6840 main_read(struct view *view, char *line)
6842 static struct rev_graph *graph = graph_stacks;
6843 enum line_type type;
6844 struct commit *commit;
6846 if (!line) {
6847 int i;
6849 if (!view->lines && !view->prev)
6850 die("No revisions match the given arguments.");
6851 if (view->lines > 0) {
6852 commit = view->line[view->lines - 1].data;
6853 view->line[view->lines - 1].dirty = 1;
6854 if (!commit->author) {
6855 view->lines--;
6856 free(commit);
6857 graph->commit = NULL;
6860 update_rev_graph(view, graph);
6862 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6863 clear_rev_graph(&graph_stacks[i]);
6864 return TRUE;
6867 type = get_line_type(line);
6868 if (type == LINE_COMMIT) {
6869 commit = calloc(1, sizeof(struct commit));
6870 if (!commit)
6871 return FALSE;
6873 line += STRING_SIZE("commit ");
6874 if (*line == '-') {
6875 graph->boundary = 1;
6876 line++;
6879 string_copy_rev(commit->id, line);
6880 commit->refs = get_ref_list(commit->id);
6881 graph->commit = commit;
6882 add_line_data(view, commit, LINE_MAIN_COMMIT);
6884 while ((line = strchr(line, ' '))) {
6885 line++;
6886 push_rev_graph(graph->parents, line);
6887 commit->has_parents = TRUE;
6889 return TRUE;
6892 if (!view->lines)
6893 return TRUE;
6894 commit = view->line[view->lines - 1].data;
6896 switch (type) {
6897 case LINE_PARENT:
6898 if (commit->has_parents)
6899 break;
6900 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6901 break;
6903 case LINE_AUTHOR:
6904 parse_author_line(line + STRING_SIZE("author "),
6905 &commit->author, &commit->time);
6906 update_rev_graph(view, graph);
6907 graph = graph->next;
6908 break;
6910 default:
6911 /* Fill in the commit title if it has not already been set. */
6912 if (commit->title[0])
6913 break;
6915 /* Require titles to start with a non-space character at the
6916 * offset used by git log. */
6917 if (strncmp(line, " ", 4))
6918 break;
6919 line += 4;
6920 /* Well, if the title starts with a whitespace character,
6921 * try to be forgiving. Otherwise we end up with no title. */
6922 while (isspace(*line))
6923 line++;
6924 if (*line == '\0')
6925 break;
6926 /* FIXME: More graceful handling of titles; append "..." to
6927 * shortened titles, etc. */
6929 string_expand(commit->title, sizeof(commit->title), line, 1);
6930 view->line[view->lines - 1].dirty = 1;
6933 return TRUE;
6936 static enum request
6937 main_request(struct view *view, enum request request, struct line *line)
6939 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6941 switch (request) {
6942 case REQ_ENTER:
6943 open_view(view, REQ_VIEW_DIFF, flags);
6944 break;
6945 case REQ_REFRESH:
6946 load_refs();
6947 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6948 break;
6949 default:
6950 return request;
6953 return REQ_NONE;
6956 static bool
6957 grep_refs(struct ref_list *list, regex_t *regex)
6959 regmatch_t pmatch;
6960 size_t i;
6962 if (!opt_show_refs || !list)
6963 return FALSE;
6965 for (i = 0; i < list->size; i++) {
6966 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6967 return TRUE;
6970 return FALSE;
6973 static bool
6974 main_grep(struct view *view, struct line *line)
6976 struct commit *commit = line->data;
6977 const char *text[] = {
6978 commit->title,
6979 opt_author ? commit->author : "",
6980 mkdate(&commit->time, opt_date),
6981 NULL
6984 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6987 static void
6988 main_select(struct view *view, struct line *line)
6990 struct commit *commit = line->data;
6992 string_copy_rev(view->ref, commit->id);
6993 string_copy_rev(ref_commit, view->ref);
6996 static struct view_ops main_ops = {
6997 "commit",
6998 main_argv,
6999 NULL,
7000 main_read,
7001 main_draw,
7002 main_request,
7003 main_grep,
7004 main_select,
7009 * Status management
7012 /* Whether or not the curses interface has been initialized. */
7013 static bool cursed = FALSE;
7015 /* Terminal hacks and workarounds. */
7016 static bool use_scroll_redrawwin;
7017 static bool use_scroll_status_wclear;
7019 /* The status window is used for polling keystrokes. */
7020 static WINDOW *status_win;
7022 /* Reading from the prompt? */
7023 static bool input_mode = FALSE;
7025 static bool status_empty = FALSE;
7027 /* Update status and title window. */
7028 static void
7029 report(const char *msg, ...)
7031 struct view *view = display[current_view];
7033 if (input_mode)
7034 return;
7036 if (!view) {
7037 char buf[SIZEOF_STR];
7038 va_list args;
7040 va_start(args, msg);
7041 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7042 buf[sizeof(buf) - 1] = 0;
7043 buf[sizeof(buf) - 2] = '.';
7044 buf[sizeof(buf) - 3] = '.';
7045 buf[sizeof(buf) - 4] = '.';
7047 va_end(args);
7048 die("%s", buf);
7051 if (!status_empty || *msg) {
7052 va_list args;
7054 va_start(args, msg);
7056 wmove(status_win, 0, 0);
7057 if (view->has_scrolled && use_scroll_status_wclear)
7058 wclear(status_win);
7059 if (*msg) {
7060 vwprintw(status_win, msg, args);
7061 status_empty = FALSE;
7062 } else {
7063 status_empty = TRUE;
7065 wclrtoeol(status_win);
7066 wnoutrefresh(status_win);
7068 va_end(args);
7071 update_view_title(view);
7074 static void
7075 init_display(void)
7077 const char *term;
7078 int x, y;
7080 /* Initialize the curses library */
7081 if (isatty(STDIN_FILENO)) {
7082 cursed = !!initscr();
7083 opt_tty = stdin;
7084 } else {
7085 /* Leave stdin and stdout alone when acting as a pager. */
7086 opt_tty = fopen("/dev/tty", "r+");
7087 if (!opt_tty)
7088 die("Failed to open /dev/tty");
7089 cursed = !!newterm(NULL, opt_tty, opt_tty);
7092 if (!cursed)
7093 die("Failed to initialize curses");
7095 nonl(); /* Disable conversion and detect newlines from input. */
7096 cbreak(); /* Take input chars one at a time, no wait for \n */
7097 noecho(); /* Don't echo input */
7098 leaveok(stdscr, FALSE);
7100 if (has_colors())
7101 init_colors();
7103 getmaxyx(stdscr, y, x);
7104 status_win = newwin(1, 0, y - 1, 0);
7105 if (!status_win)
7106 die("Failed to create status window");
7108 /* Enable keyboard mapping */
7109 keypad(status_win, TRUE);
7110 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7112 TABSIZE = opt_tab_size;
7114 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7115 if (term && !strcmp(term, "gnome-terminal")) {
7116 /* In the gnome-terminal-emulator, the message from
7117 * scrolling up one line when impossible followed by
7118 * scrolling down one line causes corruption of the
7119 * status line. This is fixed by calling wclear. */
7120 use_scroll_status_wclear = TRUE;
7121 use_scroll_redrawwin = FALSE;
7123 } else if (term && !strcmp(term, "xrvt-xpm")) {
7124 /* No problems with full optimizations in xrvt-(unicode)
7125 * and aterm. */
7126 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7128 } else {
7129 /* When scrolling in (u)xterm the last line in the
7130 * scrolling direction will update slowly. */
7131 use_scroll_redrawwin = TRUE;
7132 use_scroll_status_wclear = FALSE;
7136 static int
7137 get_input(int prompt_position)
7139 struct view *view;
7140 int i, key, cursor_y, cursor_x;
7142 if (prompt_position)
7143 input_mode = TRUE;
7145 while (TRUE) {
7146 bool loading = FALSE;
7148 foreach_view (view, i) {
7149 update_view(view);
7150 if (view_is_displayed(view) && view->has_scrolled &&
7151 use_scroll_redrawwin)
7152 redrawwin(view->win);
7153 view->has_scrolled = FALSE;
7154 if (view->pipe)
7155 loading = TRUE;
7158 /* Update the cursor position. */
7159 if (prompt_position) {
7160 getbegyx(status_win, cursor_y, cursor_x);
7161 cursor_x = prompt_position;
7162 } else {
7163 view = display[current_view];
7164 getbegyx(view->win, cursor_y, cursor_x);
7165 cursor_x = view->width - 1;
7166 cursor_y += view->lineno - view->offset;
7168 setsyx(cursor_y, cursor_x);
7170 /* Refresh, accept single keystroke of input */
7171 doupdate();
7172 nodelay(status_win, loading);
7173 key = wgetch(status_win);
7175 /* wgetch() with nodelay() enabled returns ERR when
7176 * there's no input. */
7177 if (key == ERR) {
7179 } else if (key == KEY_RESIZE) {
7180 int height, width;
7182 getmaxyx(stdscr, height, width);
7184 wresize(status_win, 1, width);
7185 mvwin(status_win, height - 1, 0);
7186 wnoutrefresh(status_win);
7187 resize_display();
7188 redraw_display(TRUE);
7190 } else {
7191 input_mode = FALSE;
7192 return key;
7197 static char *
7198 prompt_input(const char *prompt, input_handler handler, void *data)
7200 enum input_status status = INPUT_OK;
7201 static char buf[SIZEOF_STR];
7202 size_t pos = 0;
7204 buf[pos] = 0;
7206 while (status == INPUT_OK || status == INPUT_SKIP) {
7207 int key;
7209 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7210 wclrtoeol(status_win);
7212 key = get_input(pos + 1);
7213 switch (key) {
7214 case KEY_RETURN:
7215 case KEY_ENTER:
7216 case '\n':
7217 status = pos ? INPUT_STOP : INPUT_CANCEL;
7218 break;
7220 case KEY_BACKSPACE:
7221 if (pos > 0)
7222 buf[--pos] = 0;
7223 else
7224 status = INPUT_CANCEL;
7225 break;
7227 case KEY_ESC:
7228 status = INPUT_CANCEL;
7229 break;
7231 default:
7232 if (pos >= sizeof(buf)) {
7233 report("Input string too long");
7234 return NULL;
7237 status = handler(data, buf, key);
7238 if (status == INPUT_OK)
7239 buf[pos++] = (char) key;
7243 /* Clear the status window */
7244 status_empty = FALSE;
7245 report("");
7247 if (status == INPUT_CANCEL)
7248 return NULL;
7250 buf[pos++] = 0;
7252 return buf;
7255 static enum input_status
7256 prompt_yesno_handler(void *data, char *buf, int c)
7258 if (c == 'y' || c == 'Y')
7259 return INPUT_STOP;
7260 if (c == 'n' || c == 'N')
7261 return INPUT_CANCEL;
7262 return INPUT_SKIP;
7265 static bool
7266 prompt_yesno(const char *prompt)
7268 char prompt2[SIZEOF_STR];
7270 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7271 return FALSE;
7273 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7276 static enum input_status
7277 read_prompt_handler(void *data, char *buf, int c)
7279 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7282 static char *
7283 read_prompt(const char *prompt)
7285 return prompt_input(prompt, read_prompt_handler, NULL);
7288 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7290 enum input_status status = INPUT_OK;
7291 int size = 0;
7293 while (items[size].text)
7294 size++;
7296 while (status == INPUT_OK) {
7297 const struct menu_item *item = &items[*selected];
7298 int key;
7299 int i;
7301 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7302 prompt, *selected + 1, size);
7303 if (item->hotkey)
7304 wprintw(status_win, "[%c] ", (char) item->hotkey);
7305 wprintw(status_win, "%s", item->text);
7306 wclrtoeol(status_win);
7308 key = get_input(COLS - 1);
7309 switch (key) {
7310 case KEY_RETURN:
7311 case KEY_ENTER:
7312 case '\n':
7313 status = INPUT_STOP;
7314 break;
7316 case KEY_LEFT:
7317 case KEY_UP:
7318 *selected = *selected - 1;
7319 if (*selected < 0)
7320 *selected = size - 1;
7321 break;
7323 case KEY_RIGHT:
7324 case KEY_DOWN:
7325 *selected = (*selected + 1) % size;
7326 break;
7328 case KEY_ESC:
7329 status = INPUT_CANCEL;
7330 break;
7332 default:
7333 for (i = 0; items[i].text; i++)
7334 if (items[i].hotkey == key) {
7335 *selected = i;
7336 status = INPUT_STOP;
7337 break;
7342 /* Clear the status window */
7343 status_empty = FALSE;
7344 report("");
7346 return status != INPUT_CANCEL;
7350 * Repository properties
7353 static struct ref **refs = NULL;
7354 static size_t refs_size = 0;
7355 static struct ref *refs_head = NULL;
7357 static struct ref_list **ref_lists = NULL;
7358 static size_t ref_lists_size = 0;
7360 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7361 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7362 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7364 static int
7365 compare_refs(const void *ref1_, const void *ref2_)
7367 const struct ref *ref1 = *(const struct ref **)ref1_;
7368 const struct ref *ref2 = *(const struct ref **)ref2_;
7370 if (ref1->tag != ref2->tag)
7371 return ref2->tag - ref1->tag;
7372 if (ref1->ltag != ref2->ltag)
7373 return ref2->ltag - ref2->ltag;
7374 if (ref1->head != ref2->head)
7375 return ref2->head - ref1->head;
7376 if (ref1->tracked != ref2->tracked)
7377 return ref2->tracked - ref1->tracked;
7378 if (ref1->remote != ref2->remote)
7379 return ref2->remote - ref1->remote;
7380 return strcmp(ref1->name, ref2->name);
7383 static void
7384 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7386 size_t i;
7388 for (i = 0; i < refs_size; i++)
7389 if (!visitor(data, refs[i]))
7390 break;
7393 static struct ref *
7394 get_ref_head()
7396 return refs_head;
7399 static struct ref_list *
7400 get_ref_list(const char *id)
7402 struct ref_list *list;
7403 size_t i;
7405 for (i = 0; i < ref_lists_size; i++)
7406 if (!strcmp(id, ref_lists[i]->id))
7407 return ref_lists[i];
7409 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7410 return NULL;
7411 list = calloc(1, sizeof(*list));
7412 if (!list)
7413 return NULL;
7415 for (i = 0; i < refs_size; i++) {
7416 if (!strcmp(id, refs[i]->id) &&
7417 realloc_refs_list(&list->refs, list->size, 1))
7418 list->refs[list->size++] = refs[i];
7421 if (!list->refs) {
7422 free(list);
7423 return NULL;
7426 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7427 ref_lists[ref_lists_size++] = list;
7428 return list;
7431 static int
7432 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7434 struct ref *ref = NULL;
7435 bool tag = FALSE;
7436 bool ltag = FALSE;
7437 bool remote = FALSE;
7438 bool tracked = FALSE;
7439 bool head = FALSE;
7440 int from = 0, to = refs_size - 1;
7442 if (!prefixcmp(name, "refs/tags/")) {
7443 if (!suffixcmp(name, namelen, "^{}")) {
7444 namelen -= 3;
7445 name[namelen] = 0;
7446 } else {
7447 ltag = TRUE;
7450 tag = TRUE;
7451 namelen -= STRING_SIZE("refs/tags/");
7452 name += STRING_SIZE("refs/tags/");
7454 } else if (!prefixcmp(name, "refs/remotes/")) {
7455 remote = TRUE;
7456 namelen -= STRING_SIZE("refs/remotes/");
7457 name += STRING_SIZE("refs/remotes/");
7458 tracked = !strcmp(opt_remote, name);
7460 } else if (!prefixcmp(name, "refs/heads/")) {
7461 namelen -= STRING_SIZE("refs/heads/");
7462 name += STRING_SIZE("refs/heads/");
7463 if (!strncmp(opt_head, name, namelen))
7464 return OK;
7466 } else if (!strcmp(name, "HEAD")) {
7467 head = TRUE;
7468 if (*opt_head) {
7469 namelen = strlen(opt_head);
7470 name = opt_head;
7474 /* If we are reloading or it's an annotated tag, replace the
7475 * previous SHA1 with the resolved commit id; relies on the fact
7476 * git-ls-remote lists the commit id of an annotated tag right
7477 * before the commit id it points to. */
7478 while (from <= to) {
7479 size_t pos = (to + from) / 2;
7480 int cmp = strcmp(name, refs[pos]->name);
7482 if (!cmp) {
7483 ref = refs[pos];
7484 break;
7487 if (cmp < 0)
7488 to = pos - 1;
7489 else
7490 from = pos + 1;
7493 if (!ref) {
7494 if (!realloc_refs(&refs, refs_size, 1))
7495 return ERR;
7496 ref = calloc(1, sizeof(*ref) + namelen);
7497 if (!ref)
7498 return ERR;
7499 memmove(refs + from + 1, refs + from,
7500 (refs_size - from) * sizeof(*refs));
7501 refs[from] = ref;
7502 strncpy(ref->name, name, namelen);
7503 refs_size++;
7506 ref->head = head;
7507 ref->tag = tag;
7508 ref->ltag = ltag;
7509 ref->remote = remote;
7510 ref->tracked = tracked;
7511 string_copy_rev(ref->id, id);
7513 if (head)
7514 refs_head = ref;
7515 return OK;
7518 static int
7519 load_refs(void)
7521 const char *head_argv[] = {
7522 "git", "symbolic-ref", "HEAD", NULL
7524 static const char *ls_remote_argv[SIZEOF_ARG] = {
7525 "git", "ls-remote", opt_git_dir, NULL
7527 static bool init = FALSE;
7528 size_t i;
7530 if (!init) {
7531 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7532 die("TIG_LS_REMOTE contains too many arguments");
7533 init = TRUE;
7536 if (!*opt_git_dir)
7537 return OK;
7539 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7540 !prefixcmp(opt_head, "refs/heads/")) {
7541 char *offset = opt_head + STRING_SIZE("refs/heads/");
7543 memmove(opt_head, offset, strlen(offset) + 1);
7546 refs_head = NULL;
7547 for (i = 0; i < refs_size; i++)
7548 refs[i]->id[0] = 0;
7550 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7551 return ERR;
7553 /* Update the ref lists to reflect changes. */
7554 for (i = 0; i < ref_lists_size; i++) {
7555 struct ref_list *list = ref_lists[i];
7556 size_t old, new;
7558 for (old = new = 0; old < list->size; old++)
7559 if (!strcmp(list->id, list->refs[old]->id))
7560 list->refs[new++] = list->refs[old];
7561 list->size = new;
7564 return OK;
7567 static void
7568 set_remote_branch(const char *name, const char *value, size_t valuelen)
7570 if (!strcmp(name, ".remote")) {
7571 string_ncopy(opt_remote, value, valuelen);
7573 } else if (*opt_remote && !strcmp(name, ".merge")) {
7574 size_t from = strlen(opt_remote);
7576 if (!prefixcmp(value, "refs/heads/"))
7577 value += STRING_SIZE("refs/heads/");
7579 if (!string_format_from(opt_remote, &from, "/%s", value))
7580 opt_remote[0] = 0;
7584 static void
7585 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7587 const char *argv[SIZEOF_ARG] = { name, "=" };
7588 int argc = 1 + (cmd == option_set_command);
7589 int error = ERR;
7591 if (!argv_from_string(argv, &argc, value))
7592 config_msg = "Too many option arguments";
7593 else
7594 error = cmd(argc, argv);
7596 if (error == ERR)
7597 warn("Option 'tig.%s': %s", name, config_msg);
7600 static bool
7601 set_environment_variable(const char *name, const char *value)
7603 size_t len = strlen(name) + 1 + strlen(value) + 1;
7604 char *env = malloc(len);
7606 if (env &&
7607 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7608 putenv(env) == 0)
7609 return TRUE;
7610 free(env);
7611 return FALSE;
7614 static void
7615 set_work_tree(const char *value)
7617 char cwd[SIZEOF_STR];
7619 if (!getcwd(cwd, sizeof(cwd)))
7620 die("Failed to get cwd path: %s", strerror(errno));
7621 if (chdir(opt_git_dir) < 0)
7622 die("Failed to chdir(%s): %s", strerror(errno));
7623 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7624 die("Failed to get git path: %s", strerror(errno));
7625 if (chdir(cwd) < 0)
7626 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7627 if (chdir(value) < 0)
7628 die("Failed to chdir(%s): %s", value, strerror(errno));
7629 if (!getcwd(cwd, sizeof(cwd)))
7630 die("Failed to get cwd path: %s", strerror(errno));
7631 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7632 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7633 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7634 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7635 opt_is_inside_work_tree = TRUE;
7638 static int
7639 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7641 if (!strcmp(name, "i18n.commitencoding"))
7642 string_ncopy(opt_encoding, value, valuelen);
7644 else if (!strcmp(name, "core.editor"))
7645 string_ncopy(opt_editor, value, valuelen);
7647 else if (!strcmp(name, "core.worktree"))
7648 set_work_tree(value);
7650 else if (!prefixcmp(name, "tig.color."))
7651 set_repo_config_option(name + 10, value, option_color_command);
7653 else if (!prefixcmp(name, "tig.bind."))
7654 set_repo_config_option(name + 9, value, option_bind_command);
7656 else if (!prefixcmp(name, "tig."))
7657 set_repo_config_option(name + 4, value, option_set_command);
7659 else if (*opt_head && !prefixcmp(name, "branch.") &&
7660 !strncmp(name + 7, opt_head, strlen(opt_head)))
7661 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7663 return OK;
7666 static int
7667 load_git_config(void)
7669 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7671 return io_run_load(config_list_argv, "=", read_repo_config_option);
7674 static int
7675 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7677 if (!opt_git_dir[0]) {
7678 string_ncopy(opt_git_dir, name, namelen);
7680 } else if (opt_is_inside_work_tree == -1) {
7681 /* This can be 3 different values depending on the
7682 * version of git being used. If git-rev-parse does not
7683 * understand --is-inside-work-tree it will simply echo
7684 * the option else either "true" or "false" is printed.
7685 * Default to true for the unknown case. */
7686 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7688 } else if (*name == '.') {
7689 string_ncopy(opt_cdup, name, namelen);
7691 } else {
7692 string_ncopy(opt_prefix, name, namelen);
7695 return OK;
7698 static int
7699 load_repo_info(void)
7701 const char *rev_parse_argv[] = {
7702 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7703 "--show-cdup", "--show-prefix", NULL
7706 return io_run_load(rev_parse_argv, "=", read_repo_info);
7711 * Main
7714 static const char usage[] =
7715 "tig " TIG_VERSION " (" __DATE__ ")\n"
7716 "\n"
7717 "Usage: tig [options] [revs] [--] [paths]\n"
7718 " or: tig show [options] [revs] [--] [paths]\n"
7719 " or: tig blame [rev] path\n"
7720 " or: tig status\n"
7721 " or: tig < [git command output]\n"
7722 "\n"
7723 "Options:\n"
7724 " -v, --version Show version and exit\n"
7725 " -h, --help Show help message and exit";
7727 static void __NORETURN
7728 quit(int sig)
7730 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7731 if (cursed)
7732 endwin();
7733 exit(0);
7736 static void __NORETURN
7737 die(const char *err, ...)
7739 va_list args;
7741 endwin();
7743 va_start(args, err);
7744 fputs("tig: ", stderr);
7745 vfprintf(stderr, err, args);
7746 fputs("\n", stderr);
7747 va_end(args);
7749 exit(1);
7752 static void
7753 warn(const char *msg, ...)
7755 va_list args;
7757 va_start(args, msg);
7758 fputs("tig warning: ", stderr);
7759 vfprintf(stderr, msg, args);
7760 fputs("\n", stderr);
7761 va_end(args);
7764 static const char ***filter_args;
7766 static int
7767 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7769 return argv_append(filter_args, name) ? OK : ERR;
7772 static void
7773 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7775 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7776 const char **all_argv = NULL;
7778 filter_args = args;
7779 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7780 !argv_append_array(&all_argv, argv) ||
7781 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7782 die("Failed to split arguments");
7783 argv_free(all_argv);
7784 free(all_argv);
7787 static void
7788 filter_options(const char *argv[])
7790 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7791 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7792 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7795 static enum request
7796 parse_options(int argc, const char *argv[])
7798 enum request request = REQ_VIEW_MAIN;
7799 const char *subcommand;
7800 bool seen_dashdash = FALSE;
7801 const char **filter_argv = NULL;
7802 int i;
7804 if (!isatty(STDIN_FILENO))
7805 return REQ_VIEW_PAGER;
7807 if (argc <= 1)
7808 return REQ_VIEW_MAIN;
7810 subcommand = argv[1];
7811 if (!strcmp(subcommand, "status")) {
7812 if (argc > 2)
7813 warn("ignoring arguments after `%s'", subcommand);
7814 return REQ_VIEW_STATUS;
7816 } else if (!strcmp(subcommand, "blame")) {
7817 if (argc <= 2 || argc > 4)
7818 die("invalid number of options to blame\n\n%s", usage);
7820 i = 2;
7821 if (argc == 4) {
7822 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7823 i++;
7826 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7827 return REQ_VIEW_BLAME;
7829 } else if (!strcmp(subcommand, "show")) {
7830 request = REQ_VIEW_DIFF;
7832 } else {
7833 subcommand = NULL;
7836 for (i = 1 + !!subcommand; i < argc; i++) {
7837 const char *opt = argv[i];
7839 if (seen_dashdash) {
7840 argv_append(&opt_file_args, opt);
7841 continue;
7843 } else if (!strcmp(opt, "--")) {
7844 seen_dashdash = TRUE;
7845 continue;
7847 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7848 printf("tig version %s\n", TIG_VERSION);
7849 quit(0);
7851 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7852 printf("%s\n", usage);
7853 quit(0);
7855 } else if (!strcmp(opt, "--all")) {
7856 argv_append(&opt_rev_args, opt);
7857 continue;
7860 if (!argv_append(&filter_argv, opt))
7861 die("command too long");
7864 if (filter_argv)
7865 filter_options(filter_argv);
7867 return request;
7871 main(int argc, const char *argv[])
7873 const char *codeset = "UTF-8";
7874 enum request request = parse_options(argc, argv);
7875 struct view *view;
7876 size_t i;
7878 signal(SIGINT, quit);
7879 signal(SIGPIPE, SIG_IGN);
7881 if (setlocale(LC_ALL, "")) {
7882 codeset = nl_langinfo(CODESET);
7885 if (load_repo_info() == ERR)
7886 die("Failed to load repo info.");
7888 if (load_options() == ERR)
7889 die("Failed to load user config.");
7891 if (load_git_config() == ERR)
7892 die("Failed to load repo config.");
7894 /* Require a git repository unless when running in pager mode. */
7895 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7896 die("Not a git repository");
7898 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7899 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7900 if (opt_iconv_in == ICONV_NONE)
7901 die("Failed to initialize character set conversion");
7904 if (codeset && strcmp(codeset, "UTF-8")) {
7905 opt_iconv_out = iconv_open(codeset, "UTF-8");
7906 if (opt_iconv_out == ICONV_NONE)
7907 die("Failed to initialize character set conversion");
7910 if (load_refs() == ERR)
7911 die("Failed to load refs.");
7913 foreach_view (view, i) {
7914 if (getenv(view->cmd_env))
7915 warn("Use of the %s environment variable is deprecated,"
7916 " use options or TIG_DIFF_ARGS instead",
7917 view->cmd_env);
7918 if (!argv_from_env(view->ops->argv, view->cmd_env))
7919 die("Too many arguments in the `%s` environment variable",
7920 view->cmd_env);
7923 init_display();
7925 while (view_driver(display[current_view], request)) {
7926 int key = get_input(0);
7928 view = display[current_view];
7929 request = get_keybinding(view->keymap, key);
7931 /* Some low-level request handling. This keeps access to
7932 * status_win restricted. */
7933 switch (request) {
7934 case REQ_NONE:
7935 report("Unknown key, press %s for help",
7936 get_key(view->keymap, REQ_VIEW_HELP));
7937 break;
7938 case REQ_PROMPT:
7940 char *cmd = read_prompt(":");
7942 if (cmd && isdigit(*cmd)) {
7943 int lineno = view->lineno + 1;
7945 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7946 select_view_line(view, lineno - 1);
7947 report("");
7948 } else {
7949 report("Unable to parse '%s' as a line number", cmd);
7952 } else if (cmd) {
7953 struct view *next = VIEW(REQ_VIEW_PAGER);
7954 const char *argv[SIZEOF_ARG] = { "git" };
7955 int argc = 1;
7957 /* When running random commands, initially show the
7958 * command in the title. However, it maybe later be
7959 * overwritten if a commit line is selected. */
7960 string_ncopy(next->ref, cmd, strlen(cmd));
7962 if (!argv_from_string(argv, &argc, cmd)) {
7963 report("Too many arguments");
7964 } else if (!prepare_update(next, argv, NULL)) {
7965 report("Failed to format command");
7966 } else {
7967 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7971 request = REQ_NONE;
7972 break;
7974 case REQ_SEARCH:
7975 case REQ_SEARCH_BACK:
7977 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7978 char *search = read_prompt(prompt);
7980 if (search)
7981 string_ncopy(opt_search, search, strlen(search));
7982 else if (*opt_search)
7983 request = request == REQ_SEARCH ?
7984 REQ_FIND_NEXT :
7985 REQ_FIND_PREV;
7986 else
7987 request = REQ_NONE;
7988 break;
7990 default:
7991 break;
7995 quit(0);
7997 return 0;