Refactor argv_size out from argv_append
[tig.git] / tig.c
blobe2e83ff8fb320acb494796479f5288eb7e5d6e71
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_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
164 * Allocation helpers ... Entering macro hell to never be seen again.
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
181 return tmp; \
185 * String helpers
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static size_t
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
230 dst[size] = 0;
231 return pos;
234 static char *
235 chomp_string(char *name)
237 int namelen;
239 while (isspace(*name))
240 name++;
242 namelen = strlen(name) - 1;
243 while (namelen > 0 && isspace(name[namelen]))
244 name[namelen--] = 0;
246 return name;
249 static bool
250 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252 va_list args;
253 size_t pos = bufpos ? *bufpos : 0;
255 va_start(args, fmt);
256 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
257 va_end(args);
259 if (bufpos)
260 *bufpos = pos;
262 return pos >= bufsize ? FALSE : TRUE;
265 #define string_format(buf, fmt, args...) \
266 string_nformat(buf, sizeof(buf), NULL, fmt, args)
268 #define string_format_from(buf, from, fmt, args...) \
269 string_nformat(buf, sizeof(buf), from, fmt, args)
271 static int
272 string_enum_compare(const char *str1, const char *str2, int len)
274 size_t i;
276 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
278 /* Diff-Header == DIFF_HEADER */
279 for (i = 0; i < len; i++) {
280 if (toupper(str1[i]) == toupper(str2[i]))
281 continue;
283 if (string_enum_sep(str1[i]) &&
284 string_enum_sep(str2[i]))
285 continue;
287 return str1[i] - str2[i];
290 return 0;
293 #define enum_equals(entry, str, len) \
294 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
296 struct enum_map {
297 const char *name;
298 int namelen;
299 int value;
302 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
304 static char *
305 enum_map_name(const char *name, size_t namelen)
307 static char buf[SIZEOF_STR];
308 int bufpos;
310 for (bufpos = 0; bufpos <= namelen; bufpos++) {
311 buf[bufpos] = tolower(name[bufpos]);
312 if (buf[bufpos] == '_')
313 buf[bufpos] = '-';
316 buf[bufpos] = 0;
317 return buf;
320 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
322 static bool
323 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
325 size_t namelen = strlen(name);
326 int i;
328 for (i = 0; i < map_size; i++)
329 if (enum_equals(map[i], name, namelen)) {
330 *value = map[i].value;
331 return TRUE;
334 return FALSE;
337 #define map_enum(attr, map, name) \
338 map_enum_do(map, ARRAY_SIZE(map), attr, name)
340 #define prefixcmp(str1, str2) \
341 strncmp(str1, str2, STRING_SIZE(str2))
343 static inline int
344 suffixcmp(const char *str, int slen, const char *suffix)
346 size_t len = slen >= 0 ? slen : strlen(str);
347 size_t suffixlen = strlen(suffix);
349 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
354 * Unicode / UTF-8 handling
356 * NOTE: Much of the following code for dealing with Unicode is derived from
357 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
358 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
361 static inline int
362 unicode_width(unsigned long c, int tab_size)
364 if (c >= 0x1100 &&
365 (c <= 0x115f /* Hangul Jamo */
366 || c == 0x2329
367 || c == 0x232a
368 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
369 /* CJK ... Yi */
370 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
371 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
372 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
373 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
374 || (c >= 0xffe0 && c <= 0xffe6)
375 || (c >= 0x20000 && c <= 0x2fffd)
376 || (c >= 0x30000 && c <= 0x3fffd)))
377 return 2;
379 if (c == '\t')
380 return tab_size;
382 return 1;
385 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
386 * Illegal bytes are set one. */
387 static const unsigned char utf8_bytes[256] = {
388 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,
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 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,
395 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,
398 static inline unsigned char
399 utf8_char_length(const char *string, const char *end)
401 int c = *(unsigned char *) string;
403 return utf8_bytes[c];
406 /* Decode UTF-8 multi-byte representation into a Unicode character. */
407 static inline unsigned long
408 utf8_to_unicode(const char *string, size_t length)
410 unsigned long unicode;
412 switch (length) {
413 case 1:
414 unicode = string[0];
415 break;
416 case 2:
417 unicode = (string[0] & 0x1f) << 6;
418 unicode += (string[1] & 0x3f);
419 break;
420 case 3:
421 unicode = (string[0] & 0x0f) << 12;
422 unicode += ((string[1] & 0x3f) << 6);
423 unicode += (string[2] & 0x3f);
424 break;
425 case 4:
426 unicode = (string[0] & 0x0f) << 18;
427 unicode += ((string[1] & 0x3f) << 12);
428 unicode += ((string[2] & 0x3f) << 6);
429 unicode += (string[3] & 0x3f);
430 break;
431 case 5:
432 unicode = (string[0] & 0x0f) << 24;
433 unicode += ((string[1] & 0x3f) << 18);
434 unicode += ((string[2] & 0x3f) << 12);
435 unicode += ((string[3] & 0x3f) << 6);
436 unicode += (string[4] & 0x3f);
437 break;
438 case 6:
439 unicode = (string[0] & 0x01) << 30;
440 unicode += ((string[1] & 0x3f) << 24);
441 unicode += ((string[2] & 0x3f) << 18);
442 unicode += ((string[3] & 0x3f) << 12);
443 unicode += ((string[4] & 0x3f) << 6);
444 unicode += (string[5] & 0x3f);
445 break;
446 default:
447 return 0;
450 /* Invalid characters could return the special 0xfffd value but NUL
451 * should be just as good. */
452 return unicode > 0xffff ? 0 : unicode;
455 /* Calculates how much of string can be shown within the given maximum width
456 * and sets trimmed parameter to non-zero value if all of string could not be
457 * shown. If the reserve flag is TRUE, it will reserve at least one
458 * trailing character, which can be useful when drawing a delimiter.
460 * Returns the number of bytes to output from string to satisfy max_width. */
461 static size_t
462 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
464 const char *string = *start;
465 const char *end = strchr(string, '\0');
466 unsigned char last_bytes = 0;
467 size_t last_ucwidth = 0;
469 *width = 0;
470 *trimmed = 0;
472 while (string < end) {
473 unsigned char bytes = utf8_char_length(string, end);
474 size_t ucwidth;
475 unsigned long unicode;
477 if (string + bytes > end)
478 break;
480 /* Change representation to figure out whether
481 * it is a single- or double-width character. */
483 unicode = utf8_to_unicode(string, bytes);
484 /* FIXME: Graceful handling of invalid Unicode character. */
485 if (!unicode)
486 break;
488 ucwidth = unicode_width(unicode, tab_size);
489 if (skip > 0) {
490 skip -= ucwidth <= skip ? ucwidth : skip;
491 *start += bytes;
493 *width += ucwidth;
494 if (*width > max_width) {
495 *trimmed = 1;
496 *width -= ucwidth;
497 if (reserve && *width == max_width) {
498 string -= last_bytes;
499 *width -= last_ucwidth;
501 break;
504 string += bytes;
505 last_bytes = ucwidth ? bytes : 0;
506 last_ucwidth = ucwidth;
509 return string - *start;
513 #define DATE_INFO \
514 DATE_(NO), \
515 DATE_(DEFAULT), \
516 DATE_(LOCAL), \
517 DATE_(RELATIVE), \
518 DATE_(SHORT)
520 enum date {
521 #define DATE_(name) DATE_##name
522 DATE_INFO
523 #undef DATE_
526 static const struct enum_map date_map[] = {
527 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
528 DATE_INFO
529 #undef DATE_
532 struct time {
533 time_t sec;
534 int tz;
537 static inline int timecmp(const struct time *t1, const struct time *t2)
539 return t1->sec - t2->sec;
542 static const char *
543 mkdate(const struct time *time, enum date date)
545 static char buf[DATE_COLS + 1];
546 static const struct enum_map reldate[] = {
547 { "second", 1, 60 * 2 },
548 { "minute", 60, 60 * 60 * 2 },
549 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
550 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
551 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
552 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
554 struct tm tm;
556 if (!date || !time || !time->sec)
557 return "";
559 if (date == DATE_RELATIVE) {
560 struct timeval now;
561 time_t date = time->sec + time->tz;
562 time_t seconds;
563 int i;
565 gettimeofday(&now, NULL);
566 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
567 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
568 if (seconds >= reldate[i].value)
569 continue;
571 seconds /= reldate[i].namelen;
572 if (!string_format(buf, "%ld %s%s %s",
573 seconds, reldate[i].name,
574 seconds > 1 ? "s" : "",
575 now.tv_sec >= date ? "ago" : "ahead"))
576 break;
577 return buf;
581 if (date == DATE_LOCAL) {
582 time_t date = time->sec + time->tz;
583 localtime_r(&date, &tm);
585 else {
586 gmtime_r(&time->sec, &tm);
588 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593 AUTHOR_(NO), \
594 AUTHOR_(FULL), \
595 AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599 AUTHOR_VALUES,
600 #undef AUTHOR_
601 AUTHOR_DEFAULT = AUTHOR_FULL
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606 AUTHOR_VALUES
607 #undef AUTHOR_
610 static const char *
611 get_author_initials(const char *author)
613 static char initials[AUTHOR_COLS * 6 + 1];
614 size_t pos = 0;
615 const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619 memset(initials, 0, sizeof(initials));
620 while (author < end) {
621 unsigned char bytes;
622 size_t i;
624 while (is_initial_sep(*author))
625 author++;
627 bytes = utf8_char_length(author, end);
628 if (bytes < sizeof(initials) - 1 - pos) {
629 while (bytes--) {
630 initials[pos++] = *author++;
634 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635 if (i < sizeof(initials) - 1)
636 initials[i++] = *author;
639 initials[i++] = 0;
642 return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649 int valuelen;
651 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652 bool advance = cmd[valuelen] != 0;
654 cmd[valuelen] = 0;
655 argv[(*argc)++] = chomp_string(cmd);
656 cmd = chomp_string(cmd + valuelen + advance);
659 if (*argc < SIZEOF_ARG)
660 argv[*argc] = NULL;
661 return *argc < SIZEOF_ARG;
664 static bool
665 argv_from_env(const char **argv, const char *name)
667 char *env = argv ? getenv(name) : NULL;
668 int argc = 0;
670 if (env && *env)
671 env = strdup(env);
672 return !env || argv_from_string(argv, &argc, env);
675 static void
676 argv_free(const char *argv[])
678 int argc;
680 if (!argv)
681 return;
682 for (argc = 0; argv[argc]; argc++)
683 free((void *) argv[argc]);
684 argv[0] = NULL;
687 static size_t
688 argv_size(const char **argv)
690 int argc = 0;
692 while (argv && argv[argc])
693 argc++;
695 return argc;
698 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
700 static bool
701 argv_append(const char ***argv, const char *arg)
703 size_t argc = argv_size(*argv);
705 if (!argv_realloc(argv, argc, 2))
706 return FALSE;
708 (*argv)[argc++] = strdup(arg);
709 (*argv)[argc] = NULL;
710 return TRUE;
713 static bool
714 argv_append_array(const char ***dst_argv, const char *src_argv[])
716 int i;
718 for (i = 0; src_argv && src_argv[i]; i++)
719 if (!argv_append(dst_argv, src_argv[i]))
720 return FALSE;
721 return TRUE;
724 static bool
725 argv_copy(const char ***dst, const char *src[])
727 int argc;
729 for (argc = 0; src[argc]; argc++)
730 if (!argv_append(dst, src[argc]))
731 return FALSE;
732 return TRUE;
737 * Executing external commands.
740 enum io_type {
741 IO_FD, /* File descriptor based IO. */
742 IO_BG, /* Execute command in the background. */
743 IO_FG, /* Execute command with same std{in,out,err}. */
744 IO_RD, /* Read only fork+exec IO. */
745 IO_WR, /* Write only fork+exec IO. */
746 IO_AP, /* Append fork+exec output to file. */
749 struct io {
750 int pipe; /* Pipe end for reading or writing. */
751 pid_t pid; /* PID of spawned process. */
752 int error; /* Error status. */
753 char *buf; /* Read buffer. */
754 size_t bufalloc; /* Allocated buffer size. */
755 size_t bufsize; /* Buffer content size. */
756 char *bufpos; /* Current buffer position. */
757 unsigned int eof:1; /* Has end of file been reached. */
760 static void
761 io_init(struct io *io)
763 memset(io, 0, sizeof(*io));
764 io->pipe = -1;
767 static bool
768 io_open(struct io *io, const char *fmt, ...)
770 char name[SIZEOF_STR] = "";
771 bool fits;
772 va_list args;
774 io_init(io);
776 va_start(args, fmt);
777 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
778 va_end(args);
780 if (!fits) {
781 io->error = ENAMETOOLONG;
782 return FALSE;
784 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
785 if (io->pipe == -1)
786 io->error = errno;
787 return io->pipe != -1;
790 static bool
791 io_kill(struct io *io)
793 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
796 static bool
797 io_done(struct io *io)
799 pid_t pid = io->pid;
801 if (io->pipe != -1)
802 close(io->pipe);
803 free(io->buf);
804 io_init(io);
806 while (pid > 0) {
807 int status;
808 pid_t waiting = waitpid(pid, &status, 0);
810 if (waiting < 0) {
811 if (errno == EINTR)
812 continue;
813 io->error = errno;
814 return FALSE;
817 return waiting == pid &&
818 !WIFSIGNALED(status) &&
819 WIFEXITED(status) &&
820 !WEXITSTATUS(status);
823 return TRUE;
826 static bool
827 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
829 int pipefds[2] = { -1, -1 };
830 va_list args;
832 io_init(io);
834 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
835 io->error = errno;
836 return FALSE;
837 } else if (type == IO_AP) {
838 va_start(args, argv);
839 pipefds[1] = va_arg(args, int);
840 va_end(args);
843 if ((io->pid = fork())) {
844 if (io->pid == -1)
845 io->error = errno;
846 if (pipefds[!(type == IO_WR)] != -1)
847 close(pipefds[!(type == IO_WR)]);
848 if (io->pid != -1) {
849 io->pipe = pipefds[!!(type == IO_WR)];
850 return TRUE;
853 } else {
854 if (type != IO_FG) {
855 int devnull = open("/dev/null", O_RDWR);
856 int readfd = type == IO_WR ? pipefds[0] : devnull;
857 int writefd = (type == IO_RD || type == IO_AP)
858 ? pipefds[1] : devnull;
860 dup2(readfd, STDIN_FILENO);
861 dup2(writefd, STDOUT_FILENO);
862 dup2(devnull, STDERR_FILENO);
864 close(devnull);
865 if (pipefds[0] != -1)
866 close(pipefds[0]);
867 if (pipefds[1] != -1)
868 close(pipefds[1]);
871 if (dir && *dir && chdir(dir) == -1)
872 exit(errno);
874 execvp(argv[0], (char *const*) argv);
875 exit(errno);
878 if (pipefds[!!(type == IO_WR)] != -1)
879 close(pipefds[!!(type == IO_WR)]);
880 return FALSE;
883 static bool
884 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
886 struct io io;
888 return io_run(&io, type, dir, argv, fd) && io_done(&io);
891 static bool
892 io_run_bg(const char **argv)
894 return io_complete(IO_BG, argv, NULL, -1);
897 static bool
898 io_run_fg(const char **argv, const char *dir)
900 return io_complete(IO_FG, argv, dir, -1);
903 static bool
904 io_run_append(const char **argv, int fd)
906 return io_complete(IO_AP, argv, NULL, fd);
909 static bool
910 io_eof(struct io *io)
912 return io->eof;
915 static int
916 io_error(struct io *io)
918 return io->error;
921 static char *
922 io_strerror(struct io *io)
924 return strerror(io->error);
927 static bool
928 io_can_read(struct io *io)
930 struct timeval tv = { 0, 500 };
931 fd_set fds;
933 FD_ZERO(&fds);
934 FD_SET(io->pipe, &fds);
936 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
939 static ssize_t
940 io_read(struct io *io, void *buf, size_t bufsize)
942 do {
943 ssize_t readsize = read(io->pipe, buf, bufsize);
945 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
946 continue;
947 else if (readsize == -1)
948 io->error = errno;
949 else if (readsize == 0)
950 io->eof = 1;
951 return readsize;
952 } while (1);
955 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
957 static char *
958 io_get(struct io *io, int c, bool can_read)
960 char *eol;
961 ssize_t readsize;
963 while (TRUE) {
964 if (io->bufsize > 0) {
965 eol = memchr(io->bufpos, c, io->bufsize);
966 if (eol) {
967 char *line = io->bufpos;
969 *eol = 0;
970 io->bufpos = eol + 1;
971 io->bufsize -= io->bufpos - line;
972 return line;
976 if (io_eof(io)) {
977 if (io->bufsize) {
978 io->bufpos[io->bufsize] = 0;
979 io->bufsize = 0;
980 return io->bufpos;
982 return NULL;
985 if (!can_read)
986 return NULL;
988 if (io->bufsize > 0 && io->bufpos > io->buf)
989 memmove(io->buf, io->bufpos, io->bufsize);
991 if (io->bufalloc == io->bufsize) {
992 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
993 return NULL;
994 io->bufalloc += BUFSIZ;
997 io->bufpos = io->buf;
998 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
999 if (io_error(io))
1000 return NULL;
1001 io->bufsize += readsize;
1005 static bool
1006 io_write(struct io *io, const void *buf, size_t bufsize)
1008 size_t written = 0;
1010 while (!io_error(io) && written < bufsize) {
1011 ssize_t size;
1013 size = write(io->pipe, buf + written, bufsize - written);
1014 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1015 continue;
1016 else if (size == -1)
1017 io->error = errno;
1018 else
1019 written += size;
1022 return written == bufsize;
1025 static bool
1026 io_read_buf(struct io *io, char buf[], size_t bufsize)
1028 char *result = io_get(io, '\n', TRUE);
1030 if (result) {
1031 result = chomp_string(result);
1032 string_ncopy_do(buf, bufsize, result, strlen(result));
1035 return io_done(io) && result;
1038 static bool
1039 io_run_buf(const char **argv, char buf[], size_t bufsize)
1041 struct io io;
1043 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1046 static int
1047 io_load(struct io *io, const char *separators,
1048 int (*read_property)(char *, size_t, char *, size_t))
1050 char *name;
1051 int state = OK;
1053 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054 char *value;
1055 size_t namelen;
1056 size_t valuelen;
1058 name = chomp_string(name);
1059 namelen = strcspn(name, separators);
1061 if (name[namelen]) {
1062 name[namelen] = 0;
1063 value = chomp_string(name + namelen + 1);
1064 valuelen = strlen(value);
1066 } else {
1067 value = "";
1068 valuelen = 0;
1071 state = read_property(name, namelen, value, valuelen);
1074 if (state != ERR && io_error(io))
1075 state = ERR;
1076 io_done(io);
1078 return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083 int (*read_property)(char *, size_t, char *, size_t))
1085 struct io io;
1087 if (!io_run(&io, IO_RD, NULL, argv))
1088 return ERR;
1089 return io_load(&io, separators, read_property);
1094 * User requests
1097 #define REQ_INFO \
1098 /* XXX: Keep the view request first and in sync with views[]. */ \
1099 REQ_GROUP("View switching") \
1100 REQ_(VIEW_MAIN, "Show main view"), \
1101 REQ_(VIEW_DIFF, "Show diff view"), \
1102 REQ_(VIEW_LOG, "Show log view"), \
1103 REQ_(VIEW_TREE, "Show tree view"), \
1104 REQ_(VIEW_BLOB, "Show blob view"), \
1105 REQ_(VIEW_BLAME, "Show blame view"), \
1106 REQ_(VIEW_BRANCH, "Show branch view"), \
1107 REQ_(VIEW_HELP, "Show help page"), \
1108 REQ_(VIEW_PAGER, "Show pager view"), \
1109 REQ_(VIEW_STATUS, "Show status view"), \
1110 REQ_(VIEW_STAGE, "Show stage view"), \
1112 REQ_GROUP("View manipulation") \
1113 REQ_(ENTER, "Enter current line and scroll"), \
1114 REQ_(NEXT, "Move to next"), \
1115 REQ_(PREVIOUS, "Move to previous"), \
1116 REQ_(PARENT, "Move to parent"), \
1117 REQ_(VIEW_NEXT, "Move focus to next view"), \
1118 REQ_(REFRESH, "Reload and refresh"), \
1119 REQ_(MAXIMIZE, "Maximize the current view"), \
1120 REQ_(VIEW_CLOSE, "Close the current view"), \
1121 REQ_(QUIT, "Close all views and quit"), \
1123 REQ_GROUP("View specific requests") \
1124 REQ_(STATUS_UPDATE, "Update file status"), \
1125 REQ_(STATUS_REVERT, "Revert file changes"), \
1126 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1127 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1129 REQ_GROUP("Cursor navigation") \
1130 REQ_(MOVE_UP, "Move cursor one line up"), \
1131 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1132 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1133 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1134 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1135 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1137 REQ_GROUP("Scrolling") \
1138 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1139 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1140 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1141 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1142 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1143 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1145 REQ_GROUP("Searching") \
1146 REQ_(SEARCH, "Search the view"), \
1147 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1148 REQ_(FIND_NEXT, "Find next search match"), \
1149 REQ_(FIND_PREV, "Find previous search match"), \
1151 REQ_GROUP("Option manipulation") \
1152 REQ_(OPTIONS, "Open option menu"), \
1153 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1154 REQ_(TOGGLE_DATE, "Toggle date display"), \
1155 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1156 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1157 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1158 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1159 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1160 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1162 REQ_GROUP("Misc") \
1163 REQ_(PROMPT, "Bring up the prompt"), \
1164 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1165 REQ_(SHOW_VERSION, "Show version information"), \
1166 REQ_(STOP_LOADING, "Stop all loading views"), \
1167 REQ_(EDIT, "Open in editor"), \
1168 REQ_(NONE, "Do nothing")
1171 /* User action requests. */
1172 enum request {
1173 #define REQ_GROUP(help)
1174 #define REQ_(req, help) REQ_##req
1176 /* Offset all requests to avoid conflicts with ncurses getch values. */
1177 REQ_UNKNOWN = KEY_MAX + 1,
1178 REQ_OFFSET,
1179 REQ_INFO
1181 #undef REQ_GROUP
1182 #undef REQ_
1185 struct request_info {
1186 enum request request;
1187 const char *name;
1188 int namelen;
1189 const char *help;
1192 static const struct request_info req_info[] = {
1193 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1194 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1195 REQ_INFO
1196 #undef REQ_GROUP
1197 #undef REQ_
1200 static enum request
1201 get_request(const char *name)
1203 int namelen = strlen(name);
1204 int i;
1206 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1207 if (enum_equals(req_info[i], name, namelen))
1208 return req_info[i].request;
1210 return REQ_UNKNOWN;
1215 * Options
1218 /* Option and state variables. */
1219 static enum date opt_date = DATE_DEFAULT;
1220 static enum author opt_author = AUTHOR_DEFAULT;
1221 static bool opt_line_number = FALSE;
1222 static bool opt_line_graphics = TRUE;
1223 static bool opt_rev_graph = FALSE;
1224 static bool opt_show_refs = TRUE;
1225 static int opt_num_interval = 5;
1226 static double opt_hscroll = 0.50;
1227 static double opt_scale_split_view = 2.0 / 3.0;
1228 static int opt_tab_size = 8;
1229 static int opt_author_cols = AUTHOR_COLS;
1230 static char opt_path[SIZEOF_STR] = "";
1231 static char opt_file[SIZEOF_STR] = "";
1232 static char opt_ref[SIZEOF_REF] = "";
1233 static char opt_head[SIZEOF_REF] = "";
1234 static char opt_remote[SIZEOF_REF] = "";
1235 static char opt_encoding[20] = "UTF-8";
1236 static iconv_t opt_iconv_in = ICONV_NONE;
1237 static iconv_t opt_iconv_out = ICONV_NONE;
1238 static char opt_search[SIZEOF_STR] = "";
1239 static char opt_cdup[SIZEOF_STR] = "";
1240 static char opt_prefix[SIZEOF_STR] = "";
1241 static char opt_git_dir[SIZEOF_STR] = "";
1242 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1243 static char opt_editor[SIZEOF_STR] = "";
1244 static FILE *opt_tty = NULL;
1245 static const char **opt_diff_args = NULL;
1246 static const char **opt_rev_args = NULL;
1247 static const char **opt_file_args = NULL;
1249 #define is_initial_commit() (!get_ref_head())
1250 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1254 * Line-oriented content detection.
1257 #define LINE_INFO \
1258 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1273 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1274 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1275 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1279 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1280 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1281 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1282 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1283 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1284 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1285 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1289 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1290 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1291 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1293 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1296 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1297 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1301 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1302 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1306 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1307 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1308 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1309 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1310 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1311 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1312 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1313 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1315 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1316 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1318 enum line_type {
1319 #define LINE(type, line, fg, bg, attr) \
1320 LINE_##type
1321 LINE_INFO,
1322 LINE_NONE
1323 #undef LINE
1326 struct line_info {
1327 const char *name; /* Option name. */
1328 int namelen; /* Size of option name. */
1329 const char *line; /* The start of line to match. */
1330 int linelen; /* Size of string to match. */
1331 int fg, bg, attr; /* Color and text attributes for the lines. */
1334 static struct line_info line_info[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1337 LINE_INFO
1338 #undef LINE
1341 static enum line_type
1342 get_line_type(const char *line)
1344 int linelen = strlen(line);
1345 enum line_type type;
1347 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1348 /* Case insensitive search matches Signed-off-by lines better. */
1349 if (linelen >= line_info[type].linelen &&
1350 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1351 return type;
1353 return LINE_DEFAULT;
1356 static inline int
1357 get_line_attr(enum line_type type)
1359 assert(type < ARRAY_SIZE(line_info));
1360 return COLOR_PAIR(type) | line_info[type].attr;
1363 static struct line_info *
1364 get_line_info(const char *name)
1366 size_t namelen = strlen(name);
1367 enum line_type type;
1369 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1370 if (enum_equals(line_info[type], name, namelen))
1371 return &line_info[type];
1373 return NULL;
1376 static void
1377 init_colors(void)
1379 int default_bg = line_info[LINE_DEFAULT].bg;
1380 int default_fg = line_info[LINE_DEFAULT].fg;
1381 enum line_type type;
1383 start_color();
1385 if (assume_default_colors(default_fg, default_bg) == ERR) {
1386 default_bg = COLOR_BLACK;
1387 default_fg = COLOR_WHITE;
1390 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1391 struct line_info *info = &line_info[type];
1392 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1393 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1395 init_pair(type, fg, bg);
1399 struct line {
1400 enum line_type type;
1402 /* State flags */
1403 unsigned int selected:1;
1404 unsigned int dirty:1;
1405 unsigned int cleareol:1;
1406 unsigned int other:16;
1408 void *data; /* User data */
1413 * Keys
1416 struct keybinding {
1417 int alias;
1418 enum request request;
1421 static struct keybinding default_keybindings[] = {
1422 /* View switching */
1423 { 'm', REQ_VIEW_MAIN },
1424 { 'd', REQ_VIEW_DIFF },
1425 { 'l', REQ_VIEW_LOG },
1426 { 't', REQ_VIEW_TREE },
1427 { 'f', REQ_VIEW_BLOB },
1428 { 'B', REQ_VIEW_BLAME },
1429 { 'H', REQ_VIEW_BRANCH },
1430 { 'p', REQ_VIEW_PAGER },
1431 { 'h', REQ_VIEW_HELP },
1432 { 'S', REQ_VIEW_STATUS },
1433 { 'c', REQ_VIEW_STAGE },
1435 /* View manipulation */
1436 { 'q', REQ_VIEW_CLOSE },
1437 { KEY_TAB, REQ_VIEW_NEXT },
1438 { KEY_RETURN, REQ_ENTER },
1439 { KEY_UP, REQ_PREVIOUS },
1440 { KEY_DOWN, REQ_NEXT },
1441 { 'R', REQ_REFRESH },
1442 { KEY_F(5), REQ_REFRESH },
1443 { 'O', REQ_MAXIMIZE },
1445 /* Cursor navigation */
1446 { 'k', REQ_MOVE_UP },
1447 { 'j', REQ_MOVE_DOWN },
1448 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1449 { KEY_END, REQ_MOVE_LAST_LINE },
1450 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1451 { ' ', REQ_MOVE_PAGE_DOWN },
1452 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1453 { 'b', REQ_MOVE_PAGE_UP },
1454 { '-', REQ_MOVE_PAGE_UP },
1456 /* Scrolling */
1457 { KEY_LEFT, REQ_SCROLL_LEFT },
1458 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1459 { KEY_IC, REQ_SCROLL_LINE_UP },
1460 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1461 { 'w', REQ_SCROLL_PAGE_UP },
1462 { 's', REQ_SCROLL_PAGE_DOWN },
1464 /* Searching */
1465 { '/', REQ_SEARCH },
1466 { '?', REQ_SEARCH_BACK },
1467 { 'n', REQ_FIND_NEXT },
1468 { 'N', REQ_FIND_PREV },
1470 /* Misc */
1471 { 'Q', REQ_QUIT },
1472 { 'z', REQ_STOP_LOADING },
1473 { 'v', REQ_SHOW_VERSION },
1474 { 'r', REQ_SCREEN_REDRAW },
1475 { 'o', REQ_OPTIONS },
1476 { '.', REQ_TOGGLE_LINENO },
1477 { 'D', REQ_TOGGLE_DATE },
1478 { 'A', REQ_TOGGLE_AUTHOR },
1479 { 'g', REQ_TOGGLE_REV_GRAPH },
1480 { 'F', REQ_TOGGLE_REFS },
1481 { 'I', REQ_TOGGLE_SORT_ORDER },
1482 { 'i', REQ_TOGGLE_SORT_FIELD },
1483 { ':', REQ_PROMPT },
1484 { 'u', REQ_STATUS_UPDATE },
1485 { '!', REQ_STATUS_REVERT },
1486 { 'M', REQ_STATUS_MERGE },
1487 { '@', REQ_STAGE_NEXT },
1488 { ',', REQ_PARENT },
1489 { 'e', REQ_EDIT },
1492 #define KEYMAP_INFO \
1493 KEYMAP_(GENERIC), \
1494 KEYMAP_(MAIN), \
1495 KEYMAP_(DIFF), \
1496 KEYMAP_(LOG), \
1497 KEYMAP_(TREE), \
1498 KEYMAP_(BLOB), \
1499 KEYMAP_(BLAME), \
1500 KEYMAP_(BRANCH), \
1501 KEYMAP_(PAGER), \
1502 KEYMAP_(HELP), \
1503 KEYMAP_(STATUS), \
1504 KEYMAP_(STAGE)
1506 enum keymap {
1507 #define KEYMAP_(name) KEYMAP_##name
1508 KEYMAP_INFO
1509 #undef KEYMAP_
1512 static const struct enum_map keymap_table[] = {
1513 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1514 KEYMAP_INFO
1515 #undef KEYMAP_
1518 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1520 struct keybinding_table {
1521 struct keybinding *data;
1522 size_t size;
1525 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1527 static void
1528 add_keybinding(enum keymap keymap, enum request request, int key)
1530 struct keybinding_table *table = &keybindings[keymap];
1531 size_t i;
1533 for (i = 0; i < keybindings[keymap].size; i++) {
1534 if (keybindings[keymap].data[i].alias == key) {
1535 keybindings[keymap].data[i].request = request;
1536 return;
1540 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1541 if (!table->data)
1542 die("Failed to allocate keybinding");
1543 table->data[table->size].alias = key;
1544 table->data[table->size++].request = request;
1546 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1547 int i;
1549 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1550 if (default_keybindings[i].alias == key)
1551 default_keybindings[i].request = REQ_NONE;
1555 /* Looks for a key binding first in the given map, then in the generic map, and
1556 * lastly in the default keybindings. */
1557 static enum request
1558 get_keybinding(enum keymap keymap, int key)
1560 size_t i;
1562 for (i = 0; i < keybindings[keymap].size; i++)
1563 if (keybindings[keymap].data[i].alias == key)
1564 return keybindings[keymap].data[i].request;
1566 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1567 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1568 return keybindings[KEYMAP_GENERIC].data[i].request;
1570 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1571 if (default_keybindings[i].alias == key)
1572 return default_keybindings[i].request;
1574 return (enum request) key;
1578 struct key {
1579 const char *name;
1580 int value;
1583 static const struct key key_table[] = {
1584 { "Enter", KEY_RETURN },
1585 { "Space", ' ' },
1586 { "Backspace", KEY_BACKSPACE },
1587 { "Tab", KEY_TAB },
1588 { "Escape", KEY_ESC },
1589 { "Left", KEY_LEFT },
1590 { "Right", KEY_RIGHT },
1591 { "Up", KEY_UP },
1592 { "Down", KEY_DOWN },
1593 { "Insert", KEY_IC },
1594 { "Delete", KEY_DC },
1595 { "Hash", '#' },
1596 { "Home", KEY_HOME },
1597 { "End", KEY_END },
1598 { "PageUp", KEY_PPAGE },
1599 { "PageDown", KEY_NPAGE },
1600 { "F1", KEY_F(1) },
1601 { "F2", KEY_F(2) },
1602 { "F3", KEY_F(3) },
1603 { "F4", KEY_F(4) },
1604 { "F5", KEY_F(5) },
1605 { "F6", KEY_F(6) },
1606 { "F7", KEY_F(7) },
1607 { "F8", KEY_F(8) },
1608 { "F9", KEY_F(9) },
1609 { "F10", KEY_F(10) },
1610 { "F11", KEY_F(11) },
1611 { "F12", KEY_F(12) },
1614 static int
1615 get_key_value(const char *name)
1617 int i;
1619 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1620 if (!strcasecmp(key_table[i].name, name))
1621 return key_table[i].value;
1623 if (strlen(name) == 1 && isprint(*name))
1624 return (int) *name;
1626 return ERR;
1629 static const char *
1630 get_key_name(int key_value)
1632 static char key_char[] = "'X'";
1633 const char *seq = NULL;
1634 int key;
1636 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1637 if (key_table[key].value == key_value)
1638 seq = key_table[key].name;
1640 if (seq == NULL &&
1641 key_value < 127 &&
1642 isprint(key_value)) {
1643 key_char[1] = (char) key_value;
1644 seq = key_char;
1647 return seq ? seq : "(no key)";
1650 static bool
1651 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1653 const char *sep = *pos > 0 ? ", " : "";
1654 const char *keyname = get_key_name(keybinding->alias);
1656 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1659 static bool
1660 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1661 enum keymap keymap, bool all)
1663 int i;
1665 for (i = 0; i < keybindings[keymap].size; i++) {
1666 if (keybindings[keymap].data[i].request == request) {
1667 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1668 return FALSE;
1669 if (!all)
1670 break;
1674 return TRUE;
1677 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1679 static const char *
1680 get_keys(enum keymap keymap, enum request request, bool all)
1682 static char buf[BUFSIZ];
1683 size_t pos = 0;
1684 int i;
1686 buf[pos] = 0;
1688 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1689 return "Too many keybindings!";
1690 if (pos > 0 && !all)
1691 return buf;
1693 if (keymap != KEYMAP_GENERIC) {
1694 /* Only the generic keymap includes the default keybindings when
1695 * listing all keys. */
1696 if (all)
1697 return buf;
1699 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1700 return "Too many keybindings!";
1701 if (pos)
1702 return buf;
1705 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1706 if (default_keybindings[i].request == request) {
1707 if (!append_key(buf, &pos, &default_keybindings[i]))
1708 return "Too many keybindings!";
1709 if (!all)
1710 return buf;
1714 return buf;
1717 struct run_request {
1718 enum keymap keymap;
1719 int key;
1720 const char **argv;
1723 static struct run_request *run_request;
1724 static size_t run_requests;
1726 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1728 static enum request
1729 add_run_request(enum keymap keymap, int key, const char **argv)
1731 struct run_request *req;
1733 if (!realloc_run_requests(&run_request, run_requests, 1))
1734 return REQ_NONE;
1736 req = &run_request[run_requests];
1737 req->keymap = keymap;
1738 req->key = key;
1739 req->argv = NULL;
1741 if (!argv_copy(&req->argv, argv))
1742 return REQ_NONE;
1744 return REQ_NONE + ++run_requests;
1747 static struct run_request *
1748 get_run_request(enum request request)
1750 if (request <= REQ_NONE)
1751 return NULL;
1752 return &run_request[request - REQ_NONE - 1];
1755 static void
1756 add_builtin_run_requests(void)
1758 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1759 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1760 const char *commit[] = { "git", "commit", NULL };
1761 const char *gc[] = { "git", "gc", NULL };
1762 struct run_request reqs[] = {
1763 { KEYMAP_MAIN, 'C', cherry_pick },
1764 { KEYMAP_STATUS, 'C', commit },
1765 { KEYMAP_BRANCH, 'C', checkout },
1766 { KEYMAP_GENERIC, 'G', gc },
1768 int i;
1770 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1771 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1773 if (req != reqs[i].key)
1774 continue;
1775 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1776 if (req != REQ_NONE)
1777 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1782 * User config file handling.
1785 static int config_lineno;
1786 static bool config_errors;
1787 static const char *config_msg;
1789 static const struct enum_map color_map[] = {
1790 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1791 COLOR_MAP(DEFAULT),
1792 COLOR_MAP(BLACK),
1793 COLOR_MAP(BLUE),
1794 COLOR_MAP(CYAN),
1795 COLOR_MAP(GREEN),
1796 COLOR_MAP(MAGENTA),
1797 COLOR_MAP(RED),
1798 COLOR_MAP(WHITE),
1799 COLOR_MAP(YELLOW),
1802 static const struct enum_map attr_map[] = {
1803 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1804 ATTR_MAP(NORMAL),
1805 ATTR_MAP(BLINK),
1806 ATTR_MAP(BOLD),
1807 ATTR_MAP(DIM),
1808 ATTR_MAP(REVERSE),
1809 ATTR_MAP(STANDOUT),
1810 ATTR_MAP(UNDERLINE),
1813 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1815 static int parse_step(double *opt, const char *arg)
1817 *opt = atoi(arg);
1818 if (!strchr(arg, '%'))
1819 return OK;
1821 /* "Shift down" so 100% and 1 does not conflict. */
1822 *opt = (*opt - 1) / 100;
1823 if (*opt >= 1.0) {
1824 *opt = 0.99;
1825 config_msg = "Step value larger than 100%";
1826 return ERR;
1828 if (*opt < 0.0) {
1829 *opt = 1;
1830 config_msg = "Invalid step value";
1831 return ERR;
1833 return OK;
1836 static int
1837 parse_int(int *opt, const char *arg, int min, int max)
1839 int value = atoi(arg);
1841 if (min <= value && value <= max) {
1842 *opt = value;
1843 return OK;
1846 config_msg = "Integer value out of bound";
1847 return ERR;
1850 static bool
1851 set_color(int *color, const char *name)
1853 if (map_enum(color, color_map, name))
1854 return TRUE;
1855 if (!prefixcmp(name, "color"))
1856 return parse_int(color, name + 5, 0, 255) == OK;
1857 return FALSE;
1860 /* Wants: object fgcolor bgcolor [attribute] */
1861 static int
1862 option_color_command(int argc, const char *argv[])
1864 struct line_info *info;
1866 if (argc < 3) {
1867 config_msg = "Wrong number of arguments given to color command";
1868 return ERR;
1871 info = get_line_info(argv[0]);
1872 if (!info) {
1873 static const struct enum_map obsolete[] = {
1874 ENUM_MAP("main-delim", LINE_DELIMITER),
1875 ENUM_MAP("main-date", LINE_DATE),
1876 ENUM_MAP("main-author", LINE_AUTHOR),
1878 int index;
1880 if (!map_enum(&index, obsolete, argv[0])) {
1881 config_msg = "Unknown color name";
1882 return ERR;
1884 info = &line_info[index];
1887 if (!set_color(&info->fg, argv[1]) ||
1888 !set_color(&info->bg, argv[2])) {
1889 config_msg = "Unknown color";
1890 return ERR;
1893 info->attr = 0;
1894 while (argc-- > 3) {
1895 int attr;
1897 if (!set_attribute(&attr, argv[argc])) {
1898 config_msg = "Unknown attribute";
1899 return ERR;
1901 info->attr |= attr;
1904 return OK;
1907 static int parse_bool(bool *opt, const char *arg)
1909 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1910 ? TRUE : FALSE;
1911 return OK;
1914 static int parse_enum_do(unsigned int *opt, const char *arg,
1915 const struct enum_map *map, size_t map_size)
1917 bool is_true;
1919 assert(map_size > 1);
1921 if (map_enum_do(map, map_size, (int *) opt, arg))
1922 return OK;
1924 if (parse_bool(&is_true, arg) != OK)
1925 return ERR;
1927 *opt = is_true ? map[1].value : map[0].value;
1928 return OK;
1931 #define parse_enum(opt, arg, map) \
1932 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1934 static int
1935 parse_string(char *opt, const char *arg, size_t optsize)
1937 int arglen = strlen(arg);
1939 switch (arg[0]) {
1940 case '\"':
1941 case '\'':
1942 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1943 config_msg = "Unmatched quotation";
1944 return ERR;
1946 arg += 1; arglen -= 2;
1947 default:
1948 string_ncopy_do(opt, optsize, arg, arglen);
1949 return OK;
1953 /* Wants: name = value */
1954 static int
1955 option_set_command(int argc, const char *argv[])
1957 if (argc != 3) {
1958 config_msg = "Wrong number of arguments given to set command";
1959 return ERR;
1962 if (strcmp(argv[1], "=")) {
1963 config_msg = "No value assigned";
1964 return ERR;
1967 if (!strcmp(argv[0], "show-author"))
1968 return parse_enum(&opt_author, argv[2], author_map);
1970 if (!strcmp(argv[0], "show-date"))
1971 return parse_enum(&opt_date, argv[2], date_map);
1973 if (!strcmp(argv[0], "show-rev-graph"))
1974 return parse_bool(&opt_rev_graph, argv[2]);
1976 if (!strcmp(argv[0], "show-refs"))
1977 return parse_bool(&opt_show_refs, argv[2]);
1979 if (!strcmp(argv[0], "show-line-numbers"))
1980 return parse_bool(&opt_line_number, argv[2]);
1982 if (!strcmp(argv[0], "line-graphics"))
1983 return parse_bool(&opt_line_graphics, argv[2]);
1985 if (!strcmp(argv[0], "line-number-interval"))
1986 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1988 if (!strcmp(argv[0], "author-width"))
1989 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1991 if (!strcmp(argv[0], "horizontal-scroll"))
1992 return parse_step(&opt_hscroll, argv[2]);
1994 if (!strcmp(argv[0], "split-view-height"))
1995 return parse_step(&opt_scale_split_view, argv[2]);
1997 if (!strcmp(argv[0], "tab-size"))
1998 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2000 if (!strcmp(argv[0], "commit-encoding"))
2001 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2003 config_msg = "Unknown variable name";
2004 return ERR;
2007 /* Wants: mode request key */
2008 static int
2009 option_bind_command(int argc, const char *argv[])
2011 enum request request;
2012 int keymap = -1;
2013 int key;
2015 if (argc < 3) {
2016 config_msg = "Wrong number of arguments given to bind command";
2017 return ERR;
2020 if (!set_keymap(&keymap, argv[0])) {
2021 config_msg = "Unknown key map";
2022 return ERR;
2025 key = get_key_value(argv[1]);
2026 if (key == ERR) {
2027 config_msg = "Unknown key";
2028 return ERR;
2031 request = get_request(argv[2]);
2032 if (request == REQ_UNKNOWN) {
2033 static const struct enum_map obsolete[] = {
2034 ENUM_MAP("cherry-pick", REQ_NONE),
2035 ENUM_MAP("screen-resize", REQ_NONE),
2036 ENUM_MAP("tree-parent", REQ_PARENT),
2038 int alias;
2040 if (map_enum(&alias, obsolete, argv[2])) {
2041 if (alias != REQ_NONE)
2042 add_keybinding(keymap, alias, key);
2043 config_msg = "Obsolete request name";
2044 return ERR;
2047 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2048 request = add_run_request(keymap, key, argv + 2);
2049 if (request == REQ_UNKNOWN) {
2050 config_msg = "Unknown request name";
2051 return ERR;
2054 add_keybinding(keymap, request, key);
2056 return OK;
2059 static int
2060 set_option(const char *opt, char *value)
2062 const char *argv[SIZEOF_ARG];
2063 int argc = 0;
2065 if (!argv_from_string(argv, &argc, value)) {
2066 config_msg = "Too many option arguments";
2067 return ERR;
2070 if (!strcmp(opt, "color"))
2071 return option_color_command(argc, argv);
2073 if (!strcmp(opt, "set"))
2074 return option_set_command(argc, argv);
2076 if (!strcmp(opt, "bind"))
2077 return option_bind_command(argc, argv);
2079 config_msg = "Unknown option command";
2080 return ERR;
2083 static int
2084 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2086 int status = OK;
2088 config_lineno++;
2089 config_msg = "Internal error";
2091 /* Check for comment markers, since read_properties() will
2092 * only ensure opt and value are split at first " \t". */
2093 optlen = strcspn(opt, "#");
2094 if (optlen == 0)
2095 return OK;
2097 if (opt[optlen] != 0) {
2098 config_msg = "No option value";
2099 status = ERR;
2101 } else {
2102 /* Look for comment endings in the value. */
2103 size_t len = strcspn(value, "#");
2105 if (len < valuelen) {
2106 valuelen = len;
2107 value[valuelen] = 0;
2110 status = set_option(opt, value);
2113 if (status == ERR) {
2114 warn("Error on line %d, near '%.*s': %s",
2115 config_lineno, (int) optlen, opt, config_msg);
2116 config_errors = TRUE;
2119 /* Always keep going if errors are encountered. */
2120 return OK;
2123 static void
2124 load_option_file(const char *path)
2126 struct io io;
2128 /* It's OK that the file doesn't exist. */
2129 if (!io_open(&io, "%s", path))
2130 return;
2132 config_lineno = 0;
2133 config_errors = FALSE;
2135 if (io_load(&io, " \t", read_option) == ERR ||
2136 config_errors == TRUE)
2137 warn("Errors while loading %s.", path);
2140 static int
2141 load_options(void)
2143 const char *home = getenv("HOME");
2144 const char *tigrc_user = getenv("TIGRC_USER");
2145 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2146 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2147 char buf[SIZEOF_STR];
2149 if (!tigrc_system)
2150 tigrc_system = SYSCONFDIR "/tigrc";
2151 load_option_file(tigrc_system);
2153 if (!tigrc_user) {
2154 if (!home || !string_format(buf, "%s/.tigrc", home))
2155 return ERR;
2156 tigrc_user = buf;
2158 load_option_file(tigrc_user);
2160 /* Add _after_ loading config files to avoid adding run requests
2161 * that conflict with keybindings. */
2162 add_builtin_run_requests();
2164 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2165 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2166 int argc = 0;
2168 if (!string_format(buf, "%s", tig_diff_opts) ||
2169 !argv_from_string(diff_opts, &argc, buf))
2170 die("TIG_DIFF_OPTS contains too many arguments");
2171 else if (!argv_copy(&opt_diff_args, diff_opts))
2172 die("Failed to format TIG_DIFF_OPTS arguments");
2175 return OK;
2180 * The viewer
2183 struct view;
2184 struct view_ops;
2186 /* The display array of active views and the index of the current view. */
2187 static struct view *display[2];
2188 static unsigned int current_view;
2190 #define foreach_displayed_view(view, i) \
2191 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views() (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob[SIZEOF_REF] = "";
2197 static char ref_commit[SIZEOF_REF] = "HEAD";
2198 static char ref_head[SIZEOF_REF] = "HEAD";
2199 static char ref_branch[SIZEOF_REF] = "";
2201 enum view_type {
2202 VIEW_MAIN,
2203 VIEW_DIFF,
2204 VIEW_LOG,
2205 VIEW_TREE,
2206 VIEW_BLOB,
2207 VIEW_BLAME,
2208 VIEW_BRANCH,
2209 VIEW_HELP,
2210 VIEW_PAGER,
2211 VIEW_STATUS,
2212 VIEW_STAGE,
2215 struct view {
2216 enum view_type type; /* View type */
2217 const char *name; /* View name */
2218 const char *cmd_env; /* Command line set via environment */
2219 const char *id; /* Points to either of ref_{head,commit,blob} */
2221 struct view_ops *ops; /* View operations */
2223 enum keymap keymap; /* What keymap does this view have */
2224 bool git_dir; /* Whether the view requires a git directory. */
2226 char ref[SIZEOF_REF]; /* Hovered commit reference */
2227 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2229 int height, width; /* The width and height of the main window */
2230 WINDOW *win; /* The main window */
2231 WINDOW *title; /* The title window living below the main window */
2233 /* Navigation */
2234 unsigned long offset; /* Offset of the window top */
2235 unsigned long yoffset; /* Offset from the window side. */
2236 unsigned long lineno; /* Current line number */
2237 unsigned long p_offset; /* Previous offset of the window top */
2238 unsigned long p_yoffset;/* Previous offset from the window side */
2239 unsigned long p_lineno; /* Previous current line number */
2240 bool p_restore; /* Should the previous position be restored. */
2242 /* Searching */
2243 char grep[SIZEOF_STR]; /* Search string */
2244 regex_t *regex; /* Pre-compiled regexp */
2246 /* If non-NULL, points to the view that opened this view. If this view
2247 * is closed tig will switch back to the parent view. */
2248 struct view *parent;
2249 struct view *prev;
2251 /* Buffering */
2252 size_t lines; /* Total number of lines */
2253 struct line *line; /* Line index */
2254 unsigned int digits; /* Number of digits in the lines member. */
2256 /* Drawing */
2257 struct line *curline; /* Line currently being drawn. */
2258 enum line_type curtype; /* Attribute currently used for drawing. */
2259 unsigned long col; /* Column when drawing. */
2260 bool has_scrolled; /* View was scrolled. */
2262 /* Loading */
2263 const char **argv; /* Shell command arguments. */
2264 const char *dir; /* Directory from which to execute. */
2265 struct io io;
2266 struct io *pipe;
2267 time_t start_time;
2268 time_t update_secs;
2271 struct view_ops {
2272 /* What type of content being displayed. Used in the title bar. */
2273 const char *type;
2274 /* Default command arguments. */
2275 const char **argv;
2276 /* Open and reads in all view content. */
2277 bool (*open)(struct view *view);
2278 /* Read one line; updates view->line. */
2279 bool (*read)(struct view *view, char *data);
2280 /* Draw one line; @lineno must be < view->height. */
2281 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2282 /* Depending on view handle a special requests. */
2283 enum request (*request)(struct view *view, enum request request, struct line *line);
2284 /* Search for regexp in a line. */
2285 bool (*grep)(struct view *view, struct line *line);
2286 /* Select line */
2287 void (*select)(struct view *view, struct line *line);
2288 /* Prepare view for loading */
2289 bool (*prepare)(struct view *view);
2292 static struct view_ops blame_ops;
2293 static struct view_ops blob_ops;
2294 static struct view_ops diff_ops;
2295 static struct view_ops help_ops;
2296 static struct view_ops log_ops;
2297 static struct view_ops main_ops;
2298 static struct view_ops pager_ops;
2299 static struct view_ops stage_ops;
2300 static struct view_ops status_ops;
2301 static struct view_ops tree_ops;
2302 static struct view_ops branch_ops;
2304 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2305 { type, name, #env, ref, ops, map, git }
2307 #define VIEW_(id, name, ops, git, ref) \
2308 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2310 static struct view views[] = {
2311 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2312 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2313 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2314 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2315 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2316 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2317 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2318 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2319 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2320 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2321 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2324 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2326 #define foreach_view(view, i) \
2327 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2329 #define view_is_displayed(view) \
2330 (view == display[0] || view == display[1])
2332 static enum request
2333 view_request(struct view *view, enum request request)
2335 if (!view || !view->lines)
2336 return request;
2337 return view->ops->request(view, request, &view->line[view->lineno]);
2342 * View drawing.
2345 static inline void
2346 set_view_attr(struct view *view, enum line_type type)
2348 if (!view->curline->selected && view->curtype != type) {
2349 (void) wattrset(view->win, get_line_attr(type));
2350 wchgat(view->win, -1, 0, type, NULL);
2351 view->curtype = type;
2355 static int
2356 draw_chars(struct view *view, enum line_type type, const char *string,
2357 int max_len, bool use_tilde)
2359 static char out_buffer[BUFSIZ * 2];
2360 int len = 0;
2361 int col = 0;
2362 int trimmed = FALSE;
2363 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2365 if (max_len <= 0)
2366 return 0;
2368 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2370 set_view_attr(view, type);
2371 if (len > 0) {
2372 if (opt_iconv_out != ICONV_NONE) {
2373 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2374 size_t inlen = len + 1;
2376 char *outbuf = out_buffer;
2377 size_t outlen = sizeof(out_buffer);
2379 size_t ret;
2381 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2382 if (ret != (size_t) -1) {
2383 string = out_buffer;
2384 len = sizeof(out_buffer) - outlen;
2388 waddnstr(view->win, string, len);
2390 if (trimmed && use_tilde) {
2391 set_view_attr(view, LINE_DELIMITER);
2392 waddch(view->win, '~');
2393 col++;
2396 return col;
2399 static int
2400 draw_space(struct view *view, enum line_type type, int max, int spaces)
2402 static char space[] = " ";
2403 int col = 0;
2405 spaces = MIN(max, spaces);
2407 while (spaces > 0) {
2408 int len = MIN(spaces, sizeof(space) - 1);
2410 col += draw_chars(view, type, space, len, FALSE);
2411 spaces -= len;
2414 return col;
2417 static bool
2418 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2420 char text[SIZEOF_STR];
2422 do {
2423 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2425 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2426 string += pos;
2427 } while (*string && view->width + view->yoffset > view->col);
2429 return view->width + view->yoffset <= view->col;
2432 static bool
2433 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2435 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2436 int max = view->width + view->yoffset - view->col;
2437 int i;
2439 if (max < size)
2440 size = max;
2442 set_view_attr(view, type);
2443 /* Using waddch() instead of waddnstr() ensures that
2444 * they'll be rendered correctly for the cursor line. */
2445 for (i = skip; i < size; i++)
2446 waddch(view->win, graphic[i]);
2448 view->col += size;
2449 if (size < max && skip <= size)
2450 waddch(view->win, ' ');
2451 view->col++;
2453 return view->width + view->yoffset <= view->col;
2456 static bool
2457 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2459 int max = MIN(view->width + view->yoffset - view->col, len);
2460 int col;
2462 if (text)
2463 col = draw_chars(view, type, text, max - 1, trim);
2464 else
2465 col = draw_space(view, type, max - 1, max - 1);
2467 view->col += col;
2468 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2469 return view->width + view->yoffset <= view->col;
2472 static bool
2473 draw_date(struct view *view, struct time *time)
2475 const char *date = mkdate(time, opt_date);
2476 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2478 return draw_field(view, LINE_DATE, date, cols, FALSE);
2481 static bool
2482 draw_author(struct view *view, const char *author)
2484 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2485 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2487 if (abbreviate && author)
2488 author = get_author_initials(author);
2490 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2493 static bool
2494 draw_mode(struct view *view, mode_t mode)
2496 const char *str;
2498 if (S_ISDIR(mode))
2499 str = "drwxr-xr-x";
2500 else if (S_ISLNK(mode))
2501 str = "lrwxrwxrwx";
2502 else if (S_ISGITLINK(mode))
2503 str = "m---------";
2504 else if (S_ISREG(mode) && mode & S_IXUSR)
2505 str = "-rwxr-xr-x";
2506 else if (S_ISREG(mode))
2507 str = "-rw-r--r--";
2508 else
2509 str = "----------";
2511 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2514 static bool
2515 draw_lineno(struct view *view, unsigned int lineno)
2517 char number[10];
2518 int digits3 = view->digits < 3 ? 3 : view->digits;
2519 int max = MIN(view->width + view->yoffset - view->col, digits3);
2520 char *text = NULL;
2521 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2523 lineno += view->offset + 1;
2524 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2525 static char fmt[] = "%1ld";
2527 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2528 if (string_format(number, fmt, lineno))
2529 text = number;
2531 if (text)
2532 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2533 else
2534 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2535 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2538 static bool
2539 draw_view_line(struct view *view, unsigned int lineno)
2541 struct line *line;
2542 bool selected = (view->offset + lineno == view->lineno);
2544 assert(view_is_displayed(view));
2546 if (view->offset + lineno >= view->lines)
2547 return FALSE;
2549 line = &view->line[view->offset + lineno];
2551 wmove(view->win, lineno, 0);
2552 if (line->cleareol)
2553 wclrtoeol(view->win);
2554 view->col = 0;
2555 view->curline = line;
2556 view->curtype = LINE_NONE;
2557 line->selected = FALSE;
2558 line->dirty = line->cleareol = 0;
2560 if (selected) {
2561 set_view_attr(view, LINE_CURSOR);
2562 line->selected = TRUE;
2563 view->ops->select(view, line);
2566 return view->ops->draw(view, line, lineno);
2569 static void
2570 redraw_view_dirty(struct view *view)
2572 bool dirty = FALSE;
2573 int lineno;
2575 for (lineno = 0; lineno < view->height; lineno++) {
2576 if (view->offset + lineno >= view->lines)
2577 break;
2578 if (!view->line[view->offset + lineno].dirty)
2579 continue;
2580 dirty = TRUE;
2581 if (!draw_view_line(view, lineno))
2582 break;
2585 if (!dirty)
2586 return;
2587 wnoutrefresh(view->win);
2590 static void
2591 redraw_view_from(struct view *view, int lineno)
2593 assert(0 <= lineno && lineno < view->height);
2595 for (; lineno < view->height; lineno++) {
2596 if (!draw_view_line(view, lineno))
2597 break;
2600 wnoutrefresh(view->win);
2603 static void
2604 redraw_view(struct view *view)
2606 werase(view->win);
2607 redraw_view_from(view, 0);
2611 static void
2612 update_view_title(struct view *view)
2614 char buf[SIZEOF_STR];
2615 char state[SIZEOF_STR];
2616 size_t bufpos = 0, statelen = 0;
2618 assert(view_is_displayed(view));
2620 if (view->type != VIEW_STATUS && view->lines) {
2621 unsigned int view_lines = view->offset + view->height;
2622 unsigned int lines = view->lines
2623 ? MIN(view_lines, view->lines) * 100 / view->lines
2624 : 0;
2626 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2627 view->ops->type,
2628 view->lineno + 1,
2629 view->lines,
2630 lines);
2634 if (view->pipe) {
2635 time_t secs = time(NULL) - view->start_time;
2637 /* Three git seconds are a long time ... */
2638 if (secs > 2)
2639 string_format_from(state, &statelen, " loading %lds", secs);
2642 string_format_from(buf, &bufpos, "[%s]", view->name);
2643 if (*view->ref && bufpos < view->width) {
2644 size_t refsize = strlen(view->ref);
2645 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2647 if (minsize < view->width)
2648 refsize = view->width - minsize + 7;
2649 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2652 if (statelen && bufpos < view->width) {
2653 string_format_from(buf, &bufpos, "%s", state);
2656 if (view == display[current_view])
2657 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2658 else
2659 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2661 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2662 wclrtoeol(view->title);
2663 wnoutrefresh(view->title);
2666 static int
2667 apply_step(double step, int value)
2669 if (step >= 1)
2670 return (int) step;
2671 value *= step + 0.01;
2672 return value ? value : 1;
2675 static void
2676 resize_display(void)
2678 int offset, i;
2679 struct view *base = display[0];
2680 struct view *view = display[1] ? display[1] : display[0];
2682 /* Setup window dimensions */
2684 getmaxyx(stdscr, base->height, base->width);
2686 /* Make room for the status window. */
2687 base->height -= 1;
2689 if (view != base) {
2690 /* Horizontal split. */
2691 view->width = base->width;
2692 view->height = apply_step(opt_scale_split_view, base->height);
2693 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2694 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2695 base->height -= view->height;
2697 /* Make room for the title bar. */
2698 view->height -= 1;
2701 /* Make room for the title bar. */
2702 base->height -= 1;
2704 offset = 0;
2706 foreach_displayed_view (view, i) {
2707 if (!view->win) {
2708 view->win = newwin(view->height, 0, offset, 0);
2709 if (!view->win)
2710 die("Failed to create %s view", view->name);
2712 scrollok(view->win, FALSE);
2714 view->title = newwin(1, 0, offset + view->height, 0);
2715 if (!view->title)
2716 die("Failed to create title window");
2718 } else {
2719 wresize(view->win, view->height, view->width);
2720 mvwin(view->win, offset, 0);
2721 mvwin(view->title, offset + view->height, 0);
2724 offset += view->height + 1;
2728 static void
2729 redraw_display(bool clear)
2731 struct view *view;
2732 int i;
2734 foreach_displayed_view (view, i) {
2735 if (clear)
2736 wclear(view->win);
2737 redraw_view(view);
2738 update_view_title(view);
2744 * Option management
2747 static void
2748 toggle_enum_option_do(unsigned int *opt, const char *help,
2749 const struct enum_map *map, size_t size)
2751 *opt = (*opt + 1) % size;
2752 redraw_display(FALSE);
2753 report("Displaying %s %s", enum_name(map[*opt]), help);
2756 #define toggle_enum_option(opt, help, map) \
2757 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2759 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2760 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2762 static void
2763 toggle_view_option(bool *option, const char *help)
2765 *option = !*option;
2766 redraw_display(FALSE);
2767 report("%sabling %s", *option ? "En" : "Dis", help);
2770 static void
2771 open_option_menu(void)
2773 const struct menu_item menu[] = {
2774 { '.', "line numbers", &opt_line_number },
2775 { 'D', "date display", &opt_date },
2776 { 'A', "author display", &opt_author },
2777 { 'g', "revision graph display", &opt_rev_graph },
2778 { 'F', "reference display", &opt_show_refs },
2779 { 0 }
2781 int selected = 0;
2783 if (prompt_menu("Toggle option", menu, &selected)) {
2784 if (menu[selected].data == &opt_date)
2785 toggle_date();
2786 else if (menu[selected].data == &opt_author)
2787 toggle_author();
2788 else
2789 toggle_view_option(menu[selected].data, menu[selected].text);
2793 static void
2794 maximize_view(struct view *view)
2796 memset(display, 0, sizeof(display));
2797 current_view = 0;
2798 display[current_view] = view;
2799 resize_display();
2800 redraw_display(FALSE);
2801 report("");
2806 * Navigation
2809 static bool
2810 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2812 if (lineno >= view->lines)
2813 lineno = view->lines > 0 ? view->lines - 1 : 0;
2815 if (offset > lineno || offset + view->height <= lineno) {
2816 unsigned long half = view->height / 2;
2818 if (lineno > half)
2819 offset = lineno - half;
2820 else
2821 offset = 0;
2824 if (offset != view->offset || lineno != view->lineno) {
2825 view->offset = offset;
2826 view->lineno = lineno;
2827 return TRUE;
2830 return FALSE;
2833 /* Scrolling backend */
2834 static void
2835 do_scroll_view(struct view *view, int lines)
2837 bool redraw_current_line = FALSE;
2839 /* The rendering expects the new offset. */
2840 view->offset += lines;
2842 assert(0 <= view->offset && view->offset < view->lines);
2843 assert(lines);
2845 /* Move current line into the view. */
2846 if (view->lineno < view->offset) {
2847 view->lineno = view->offset;
2848 redraw_current_line = TRUE;
2849 } else if (view->lineno >= view->offset + view->height) {
2850 view->lineno = view->offset + view->height - 1;
2851 redraw_current_line = TRUE;
2854 assert(view->offset <= view->lineno && view->lineno < view->lines);
2856 /* Redraw the whole screen if scrolling is pointless. */
2857 if (view->height < ABS(lines)) {
2858 redraw_view(view);
2860 } else {
2861 int line = lines > 0 ? view->height - lines : 0;
2862 int end = line + ABS(lines);
2864 scrollok(view->win, TRUE);
2865 wscrl(view->win, lines);
2866 scrollok(view->win, FALSE);
2868 while (line < end && draw_view_line(view, line))
2869 line++;
2871 if (redraw_current_line)
2872 draw_view_line(view, view->lineno - view->offset);
2873 wnoutrefresh(view->win);
2876 view->has_scrolled = TRUE;
2877 report("");
2880 /* Scroll frontend */
2881 static void
2882 scroll_view(struct view *view, enum request request)
2884 int lines = 1;
2886 assert(view_is_displayed(view));
2888 switch (request) {
2889 case REQ_SCROLL_LEFT:
2890 if (view->yoffset == 0) {
2891 report("Cannot scroll beyond the first column");
2892 return;
2894 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2895 view->yoffset = 0;
2896 else
2897 view->yoffset -= apply_step(opt_hscroll, view->width);
2898 redraw_view_from(view, 0);
2899 report("");
2900 return;
2901 case REQ_SCROLL_RIGHT:
2902 view->yoffset += apply_step(opt_hscroll, view->width);
2903 redraw_view(view);
2904 report("");
2905 return;
2906 case REQ_SCROLL_PAGE_DOWN:
2907 lines = view->height;
2908 case REQ_SCROLL_LINE_DOWN:
2909 if (view->offset + lines > view->lines)
2910 lines = view->lines - view->offset;
2912 if (lines == 0 || view->offset + view->height >= view->lines) {
2913 report("Cannot scroll beyond the last line");
2914 return;
2916 break;
2918 case REQ_SCROLL_PAGE_UP:
2919 lines = view->height;
2920 case REQ_SCROLL_LINE_UP:
2921 if (lines > view->offset)
2922 lines = view->offset;
2924 if (lines == 0) {
2925 report("Cannot scroll beyond the first line");
2926 return;
2929 lines = -lines;
2930 break;
2932 default:
2933 die("request %d not handled in switch", request);
2936 do_scroll_view(view, lines);
2939 /* Cursor moving */
2940 static void
2941 move_view(struct view *view, enum request request)
2943 int scroll_steps = 0;
2944 int steps;
2946 switch (request) {
2947 case REQ_MOVE_FIRST_LINE:
2948 steps = -view->lineno;
2949 break;
2951 case REQ_MOVE_LAST_LINE:
2952 steps = view->lines - view->lineno - 1;
2953 break;
2955 case REQ_MOVE_PAGE_UP:
2956 steps = view->height > view->lineno
2957 ? -view->lineno : -view->height;
2958 break;
2960 case REQ_MOVE_PAGE_DOWN:
2961 steps = view->lineno + view->height >= view->lines
2962 ? view->lines - view->lineno - 1 : view->height;
2963 break;
2965 case REQ_MOVE_UP:
2966 steps = -1;
2967 break;
2969 case REQ_MOVE_DOWN:
2970 steps = 1;
2971 break;
2973 default:
2974 die("request %d not handled in switch", request);
2977 if (steps <= 0 && view->lineno == 0) {
2978 report("Cannot move beyond the first line");
2979 return;
2981 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2982 report("Cannot move beyond the last line");
2983 return;
2986 /* Move the current line */
2987 view->lineno += steps;
2988 assert(0 <= view->lineno && view->lineno < view->lines);
2990 /* Check whether the view needs to be scrolled */
2991 if (view->lineno < view->offset ||
2992 view->lineno >= view->offset + view->height) {
2993 scroll_steps = steps;
2994 if (steps < 0 && -steps > view->offset) {
2995 scroll_steps = -view->offset;
2997 } else if (steps > 0) {
2998 if (view->lineno == view->lines - 1 &&
2999 view->lines > view->height) {
3000 scroll_steps = view->lines - view->offset - 1;
3001 if (scroll_steps >= view->height)
3002 scroll_steps -= view->height - 1;
3007 if (!view_is_displayed(view)) {
3008 view->offset += scroll_steps;
3009 assert(0 <= view->offset && view->offset < view->lines);
3010 view->ops->select(view, &view->line[view->lineno]);
3011 return;
3014 /* Repaint the old "current" line if we be scrolling */
3015 if (ABS(steps) < view->height)
3016 draw_view_line(view, view->lineno - steps - view->offset);
3018 if (scroll_steps) {
3019 do_scroll_view(view, scroll_steps);
3020 return;
3023 /* Draw the current line */
3024 draw_view_line(view, view->lineno - view->offset);
3026 wnoutrefresh(view->win);
3027 report("");
3032 * Searching
3035 static void search_view(struct view *view, enum request request);
3037 static bool
3038 grep_text(struct view *view, const char *text[])
3040 regmatch_t pmatch;
3041 size_t i;
3043 for (i = 0; text[i]; i++)
3044 if (*text[i] &&
3045 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3046 return TRUE;
3047 return FALSE;
3050 static void
3051 select_view_line(struct view *view, unsigned long lineno)
3053 unsigned long old_lineno = view->lineno;
3054 unsigned long old_offset = view->offset;
3056 if (goto_view_line(view, view->offset, lineno)) {
3057 if (view_is_displayed(view)) {
3058 if (old_offset != view->offset) {
3059 redraw_view(view);
3060 } else {
3061 draw_view_line(view, old_lineno - view->offset);
3062 draw_view_line(view, view->lineno - view->offset);
3063 wnoutrefresh(view->win);
3065 } else {
3066 view->ops->select(view, &view->line[view->lineno]);
3071 static void
3072 find_next(struct view *view, enum request request)
3074 unsigned long lineno = view->lineno;
3075 int direction;
3077 if (!*view->grep) {
3078 if (!*opt_search)
3079 report("No previous search");
3080 else
3081 search_view(view, request);
3082 return;
3085 switch (request) {
3086 case REQ_SEARCH:
3087 case REQ_FIND_NEXT:
3088 direction = 1;
3089 break;
3091 case REQ_SEARCH_BACK:
3092 case REQ_FIND_PREV:
3093 direction = -1;
3094 break;
3096 default:
3097 return;
3100 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3101 lineno += direction;
3103 /* Note, lineno is unsigned long so will wrap around in which case it
3104 * will become bigger than view->lines. */
3105 for (; lineno < view->lines; lineno += direction) {
3106 if (view->ops->grep(view, &view->line[lineno])) {
3107 select_view_line(view, lineno);
3108 report("Line %ld matches '%s'", lineno + 1, view->grep);
3109 return;
3113 report("No match found for '%s'", view->grep);
3116 static void
3117 search_view(struct view *view, enum request request)
3119 int regex_err;
3121 if (view->regex) {
3122 regfree(view->regex);
3123 *view->grep = 0;
3124 } else {
3125 view->regex = calloc(1, sizeof(*view->regex));
3126 if (!view->regex)
3127 return;
3130 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3131 if (regex_err != 0) {
3132 char buf[SIZEOF_STR] = "unknown error";
3134 regerror(regex_err, view->regex, buf, sizeof(buf));
3135 report("Search failed: %s", buf);
3136 return;
3139 string_copy(view->grep, opt_search);
3141 find_next(view, request);
3145 * Incremental updating
3148 static void
3149 reset_view(struct view *view)
3151 int i;
3153 for (i = 0; i < view->lines; i++)
3154 free(view->line[i].data);
3155 free(view->line);
3157 view->p_offset = view->offset;
3158 view->p_yoffset = view->yoffset;
3159 view->p_lineno = view->lineno;
3161 view->line = NULL;
3162 view->offset = 0;
3163 view->yoffset = 0;
3164 view->lines = 0;
3165 view->lineno = 0;
3166 view->vid[0] = 0;
3167 view->update_secs = 0;
3170 static const char *
3171 format_arg(const char *name)
3173 static struct {
3174 const char *name;
3175 size_t namelen;
3176 const char *value;
3177 const char *value_if_empty;
3178 } vars[] = {
3179 #define FORMAT_VAR(name, value, value_if_empty) \
3180 { name, STRING_SIZE(name), value, value_if_empty }
3181 FORMAT_VAR("%(directory)", opt_path, ""),
3182 FORMAT_VAR("%(file)", opt_file, ""),
3183 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3184 FORMAT_VAR("%(head)", ref_head, ""),
3185 FORMAT_VAR("%(commit)", ref_commit, ""),
3186 FORMAT_VAR("%(blob)", ref_blob, ""),
3187 FORMAT_VAR("%(branch)", ref_branch, ""),
3189 int i;
3191 for (i = 0; i < ARRAY_SIZE(vars); i++)
3192 if (!strncmp(name, vars[i].name, vars[i].namelen))
3193 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3195 report("Unknown replacement: `%s`", name);
3196 return NULL;
3199 static bool
3200 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3202 char buf[SIZEOF_STR];
3203 int argc;
3205 argv_free(*dst_argv);
3207 for (argc = 0; src_argv[argc]; argc++) {
3208 const char *arg = src_argv[argc];
3209 size_t bufpos = 0;
3211 if (!strcmp(arg, "%(fileargs)")) {
3212 if (!argv_append_array(dst_argv, opt_file_args))
3213 break;
3214 continue;
3216 } else if (!strcmp(arg, "%(diffargs)")) {
3217 if (!argv_append_array(dst_argv, opt_diff_args))
3218 break;
3219 continue;
3221 } else if (!strcmp(arg, "%(revargs)")) {
3222 if (!argv_append_array(dst_argv, opt_rev_args))
3223 break;
3224 continue;
3227 while (arg) {
3228 char *next = strstr(arg, "%(");
3229 int len = next - arg;
3230 const char *value;
3232 if (!next || !replace) {
3233 len = strlen(arg);
3234 value = "";
3236 } else {
3237 value = format_arg(next);
3239 if (!value) {
3240 return FALSE;
3244 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3245 return FALSE;
3247 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3250 if (!argv_append(dst_argv, buf))
3251 break;
3254 return src_argv[argc] == NULL;
3257 static bool
3258 restore_view_position(struct view *view)
3260 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3261 return FALSE;
3263 /* Changing the view position cancels the restoring. */
3264 /* FIXME: Changing back to the first line is not detected. */
3265 if (view->offset != 0 || view->lineno != 0) {
3266 view->p_restore = FALSE;
3267 return FALSE;
3270 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3271 view_is_displayed(view))
3272 werase(view->win);
3274 view->yoffset = view->p_yoffset;
3275 view->p_restore = FALSE;
3277 return TRUE;
3280 static void
3281 end_update(struct view *view, bool force)
3283 if (!view->pipe)
3284 return;
3285 while (!view->ops->read(view, NULL))
3286 if (!force)
3287 return;
3288 if (force)
3289 io_kill(view->pipe);
3290 io_done(view->pipe);
3291 view->pipe = NULL;
3294 static void
3295 setup_update(struct view *view, const char *vid)
3297 reset_view(view);
3298 string_copy_rev(view->vid, vid);
3299 view->pipe = &view->io;
3300 view->start_time = time(NULL);
3303 static bool
3304 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3306 view->dir = dir;
3307 return format_argv(&view->argv, argv, replace);
3310 static bool
3311 prepare_update(struct view *view, const char *argv[], const char *dir)
3313 if (view->pipe)
3314 end_update(view, TRUE);
3315 return prepare_io(view, dir, argv, FALSE);
3318 static bool
3319 start_update(struct view *view, const char **argv, const char *dir)
3321 if (view->pipe)
3322 io_done(view->pipe);
3323 return prepare_io(view, dir, argv, FALSE) &&
3324 io_run(&view->io, IO_RD, dir, view->argv);
3327 static bool
3328 prepare_update_file(struct view *view, const char *name)
3330 if (view->pipe)
3331 end_update(view, TRUE);
3332 argv_free(view->argv);
3333 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3336 static bool
3337 begin_update(struct view *view, bool refresh)
3339 if (view->pipe)
3340 end_update(view, TRUE);
3342 if (!refresh) {
3343 if (view->ops->prepare) {
3344 if (!view->ops->prepare(view))
3345 return FALSE;
3346 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3347 return FALSE;
3350 /* Put the current ref_* value to the view title ref
3351 * member. This is needed by the blob view. Most other
3352 * views sets it automatically after loading because the
3353 * first line is a commit line. */
3354 string_copy_rev(view->ref, view->id);
3357 if (view->argv && view->argv[0] &&
3358 !io_run(&view->io, IO_RD, view->dir, view->argv))
3359 return FALSE;
3361 setup_update(view, view->id);
3363 return TRUE;
3366 static bool
3367 update_view(struct view *view)
3369 char out_buffer[BUFSIZ * 2];
3370 char *line;
3371 /* Clear the view and redraw everything since the tree sorting
3372 * might have rearranged things. */
3373 bool redraw = view->lines == 0;
3374 bool can_read = TRUE;
3376 if (!view->pipe)
3377 return TRUE;
3379 if (!io_can_read(view->pipe)) {
3380 if (view->lines == 0 && view_is_displayed(view)) {
3381 time_t secs = time(NULL) - view->start_time;
3383 if (secs > 1 && secs > view->update_secs) {
3384 if (view->update_secs == 0)
3385 redraw_view(view);
3386 update_view_title(view);
3387 view->update_secs = secs;
3390 return TRUE;
3393 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3394 if (opt_iconv_in != ICONV_NONE) {
3395 ICONV_CONST char *inbuf = line;
3396 size_t inlen = strlen(line) + 1;
3398 char *outbuf = out_buffer;
3399 size_t outlen = sizeof(out_buffer);
3401 size_t ret;
3403 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3404 if (ret != (size_t) -1)
3405 line = out_buffer;
3408 if (!view->ops->read(view, line)) {
3409 report("Allocation failure");
3410 end_update(view, TRUE);
3411 return FALSE;
3416 unsigned long lines = view->lines;
3417 int digits;
3419 for (digits = 0; lines; digits++)
3420 lines /= 10;
3422 /* Keep the displayed view in sync with line number scaling. */
3423 if (digits != view->digits) {
3424 view->digits = digits;
3425 if (opt_line_number || view->type == VIEW_BLAME)
3426 redraw = TRUE;
3430 if (io_error(view->pipe)) {
3431 report("Failed to read: %s", io_strerror(view->pipe));
3432 end_update(view, TRUE);
3434 } else if (io_eof(view->pipe)) {
3435 if (view_is_displayed(view))
3436 report("");
3437 end_update(view, FALSE);
3440 if (restore_view_position(view))
3441 redraw = TRUE;
3443 if (!view_is_displayed(view))
3444 return TRUE;
3446 if (redraw)
3447 redraw_view_from(view, 0);
3448 else
3449 redraw_view_dirty(view);
3451 /* Update the title _after_ the redraw so that if the redraw picks up a
3452 * commit reference in view->ref it'll be available here. */
3453 update_view_title(view);
3454 return TRUE;
3457 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3459 static struct line *
3460 add_line_data(struct view *view, void *data, enum line_type type)
3462 struct line *line;
3464 if (!realloc_lines(&view->line, view->lines, 1))
3465 return NULL;
3467 line = &view->line[view->lines++];
3468 memset(line, 0, sizeof(*line));
3469 line->type = type;
3470 line->data = data;
3471 line->dirty = 1;
3473 return line;
3476 static struct line *
3477 add_line_text(struct view *view, const char *text, enum line_type type)
3479 char *data = text ? strdup(text) : NULL;
3481 return data ? add_line_data(view, data, type) : NULL;
3484 static struct line *
3485 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3487 char buf[SIZEOF_STR];
3488 va_list args;
3490 va_start(args, fmt);
3491 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3492 buf[0] = 0;
3493 va_end(args);
3495 return buf[0] ? add_line_text(view, buf, type) : NULL;
3499 * View opening
3502 enum open_flags {
3503 OPEN_DEFAULT = 0, /* Use default view switching. */
3504 OPEN_SPLIT = 1, /* Split current view. */
3505 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3506 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3507 OPEN_PREPARED = 32, /* Open already prepared command. */
3510 static void
3511 open_view(struct view *prev, enum request request, enum open_flags flags)
3513 bool split = !!(flags & OPEN_SPLIT);
3514 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3515 bool nomaximize = !!(flags & OPEN_REFRESH);
3516 struct view *view = VIEW(request);
3517 int nviews = displayed_views();
3518 struct view *base_view = display[0];
3520 if (view == prev && nviews == 1 && !reload) {
3521 report("Already in %s view", view->name);
3522 return;
3525 if (view->git_dir && !opt_git_dir[0]) {
3526 report("The %s view is disabled in pager view", view->name);
3527 return;
3530 if (split) {
3531 display[1] = view;
3532 current_view = 1;
3533 view->parent = prev;
3534 } else if (!nomaximize) {
3535 /* Maximize the current view. */
3536 memset(display, 0, sizeof(display));
3537 current_view = 0;
3538 display[current_view] = view;
3541 /* No prev signals that this is the first loaded view. */
3542 if (prev && view != prev) {
3543 view->prev = prev;
3546 /* Resize the view when switching between split- and full-screen,
3547 * or when switching between two different full-screen views. */
3548 if (nviews != displayed_views() ||
3549 (nviews == 1 && base_view != display[0]))
3550 resize_display();
3552 if (view->ops->open) {
3553 if (view->pipe)
3554 end_update(view, TRUE);
3555 if (!view->ops->open(view)) {
3556 report("Failed to load %s view", view->name);
3557 return;
3559 restore_view_position(view);
3561 } else if ((reload || strcmp(view->vid, view->id)) &&
3562 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3563 report("Failed to load %s view", view->name);
3564 return;
3567 if (split && prev->lineno - prev->offset >= prev->height) {
3568 /* Take the title line into account. */
3569 int lines = prev->lineno - prev->offset - prev->height + 1;
3571 /* Scroll the view that was split if the current line is
3572 * outside the new limited view. */
3573 do_scroll_view(prev, lines);
3576 if (prev && view != prev && split && view_is_displayed(prev)) {
3577 /* "Blur" the previous view. */
3578 update_view_title(prev);
3581 if (view->pipe && view->lines == 0) {
3582 /* Clear the old view and let the incremental updating refill
3583 * the screen. */
3584 werase(view->win);
3585 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3586 report("");
3587 } else if (view_is_displayed(view)) {
3588 redraw_view(view);
3589 report("");
3593 static void
3594 open_external_viewer(const char *argv[], const char *dir)
3596 def_prog_mode(); /* save current tty modes */
3597 endwin(); /* restore original tty modes */
3598 io_run_fg(argv, dir);
3599 fprintf(stderr, "Press Enter to continue");
3600 getc(opt_tty);
3601 reset_prog_mode();
3602 redraw_display(TRUE);
3605 static void
3606 open_mergetool(const char *file)
3608 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3610 open_external_viewer(mergetool_argv, opt_cdup);
3613 static void
3614 open_editor(const char *file)
3616 const char *editor_argv[] = { "vi", file, NULL };
3617 const char *editor;
3619 editor = getenv("GIT_EDITOR");
3620 if (!editor && *opt_editor)
3621 editor = opt_editor;
3622 if (!editor)
3623 editor = getenv("VISUAL");
3624 if (!editor)
3625 editor = getenv("EDITOR");
3626 if (!editor)
3627 editor = "vi";
3629 editor_argv[0] = editor;
3630 open_external_viewer(editor_argv, opt_cdup);
3633 static void
3634 open_run_request(enum request request)
3636 struct run_request *req = get_run_request(request);
3637 const char **argv = NULL;
3639 if (!req) {
3640 report("Unknown run request");
3641 return;
3644 if (format_argv(&argv, req->argv, TRUE))
3645 open_external_viewer(argv, NULL);
3646 if (argv)
3647 argv_free(argv);
3648 free(argv);
3652 * User request switch noodle
3655 static int
3656 view_driver(struct view *view, enum request request)
3658 int i;
3660 if (request == REQ_NONE)
3661 return TRUE;
3663 if (request > REQ_NONE) {
3664 open_run_request(request);
3665 view_request(view, REQ_REFRESH);
3666 return TRUE;
3669 request = view_request(view, request);
3670 if (request == REQ_NONE)
3671 return TRUE;
3673 switch (request) {
3674 case REQ_MOVE_UP:
3675 case REQ_MOVE_DOWN:
3676 case REQ_MOVE_PAGE_UP:
3677 case REQ_MOVE_PAGE_DOWN:
3678 case REQ_MOVE_FIRST_LINE:
3679 case REQ_MOVE_LAST_LINE:
3680 move_view(view, request);
3681 break;
3683 case REQ_SCROLL_LEFT:
3684 case REQ_SCROLL_RIGHT:
3685 case REQ_SCROLL_LINE_DOWN:
3686 case REQ_SCROLL_LINE_UP:
3687 case REQ_SCROLL_PAGE_DOWN:
3688 case REQ_SCROLL_PAGE_UP:
3689 scroll_view(view, request);
3690 break;
3692 case REQ_VIEW_BLAME:
3693 if (!opt_file[0]) {
3694 report("No file chosen, press %s to open tree view",
3695 get_key(view->keymap, REQ_VIEW_TREE));
3696 break;
3698 open_view(view, request, OPEN_DEFAULT);
3699 break;
3701 case REQ_VIEW_BLOB:
3702 if (!ref_blob[0]) {
3703 report("No file chosen, press %s to open tree view",
3704 get_key(view->keymap, REQ_VIEW_TREE));
3705 break;
3707 open_view(view, request, OPEN_DEFAULT);
3708 break;
3710 case REQ_VIEW_PAGER:
3711 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3712 report("No pager content, press %s to run command from prompt",
3713 get_key(view->keymap, REQ_PROMPT));
3714 break;
3716 open_view(view, request, OPEN_DEFAULT);
3717 break;
3719 case REQ_VIEW_STAGE:
3720 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3721 report("No stage content, press %s to open the status view and choose file",
3722 get_key(view->keymap, REQ_VIEW_STATUS));
3723 break;
3725 open_view(view, request, OPEN_DEFAULT);
3726 break;
3728 case REQ_VIEW_STATUS:
3729 if (opt_is_inside_work_tree == FALSE) {
3730 report("The status view requires a working tree");
3731 break;
3733 open_view(view, request, OPEN_DEFAULT);
3734 break;
3736 case REQ_VIEW_MAIN:
3737 case REQ_VIEW_DIFF:
3738 case REQ_VIEW_LOG:
3739 case REQ_VIEW_TREE:
3740 case REQ_VIEW_HELP:
3741 case REQ_VIEW_BRANCH:
3742 open_view(view, request, OPEN_DEFAULT);
3743 break;
3745 case REQ_NEXT:
3746 case REQ_PREVIOUS:
3747 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3749 if (view->parent) {
3750 int line;
3752 view = view->parent;
3753 line = view->lineno;
3754 move_view(view, request);
3755 if (view_is_displayed(view))
3756 update_view_title(view);
3757 if (line != view->lineno)
3758 view_request(view, REQ_ENTER);
3759 } else {
3760 move_view(view, request);
3762 break;
3764 case REQ_VIEW_NEXT:
3766 int nviews = displayed_views();
3767 int next_view = (current_view + 1) % nviews;
3769 if (next_view == current_view) {
3770 report("Only one view is displayed");
3771 break;
3774 current_view = next_view;
3775 /* Blur out the title of the previous view. */
3776 update_view_title(view);
3777 report("");
3778 break;
3780 case REQ_REFRESH:
3781 report("Refreshing is not yet supported for the %s view", view->name);
3782 break;
3784 case REQ_MAXIMIZE:
3785 if (displayed_views() == 2)
3786 maximize_view(view);
3787 break;
3789 case REQ_OPTIONS:
3790 open_option_menu();
3791 break;
3793 case REQ_TOGGLE_LINENO:
3794 toggle_view_option(&opt_line_number, "line numbers");
3795 break;
3797 case REQ_TOGGLE_DATE:
3798 toggle_date();
3799 break;
3801 case REQ_TOGGLE_AUTHOR:
3802 toggle_author();
3803 break;
3805 case REQ_TOGGLE_REV_GRAPH:
3806 toggle_view_option(&opt_rev_graph, "revision graph display");
3807 break;
3809 case REQ_TOGGLE_REFS:
3810 toggle_view_option(&opt_show_refs, "reference display");
3811 break;
3813 case REQ_TOGGLE_SORT_FIELD:
3814 case REQ_TOGGLE_SORT_ORDER:
3815 report("Sorting is not yet supported for the %s view", view->name);
3816 break;
3818 case REQ_SEARCH:
3819 case REQ_SEARCH_BACK:
3820 search_view(view, request);
3821 break;
3823 case REQ_FIND_NEXT:
3824 case REQ_FIND_PREV:
3825 find_next(view, request);
3826 break;
3828 case REQ_STOP_LOADING:
3829 foreach_view(view, i) {
3830 if (view->pipe)
3831 report("Stopped loading the %s view", view->name),
3832 end_update(view, TRUE);
3834 break;
3836 case REQ_SHOW_VERSION:
3837 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3838 return TRUE;
3840 case REQ_SCREEN_REDRAW:
3841 redraw_display(TRUE);
3842 break;
3844 case REQ_EDIT:
3845 report("Nothing to edit");
3846 break;
3848 case REQ_ENTER:
3849 report("Nothing to enter");
3850 break;
3852 case REQ_VIEW_CLOSE:
3853 /* XXX: Mark closed views by letting view->prev point to the
3854 * view itself. Parents to closed view should never be
3855 * followed. */
3856 if (view->prev && view->prev != view) {
3857 maximize_view(view->prev);
3858 view->prev = view;
3859 break;
3861 /* Fall-through */
3862 case REQ_QUIT:
3863 return FALSE;
3865 default:
3866 report("Unknown key, press %s for help",
3867 get_key(view->keymap, REQ_VIEW_HELP));
3868 return TRUE;
3871 return TRUE;
3876 * View backend utilities
3879 enum sort_field {
3880 ORDERBY_NAME,
3881 ORDERBY_DATE,
3882 ORDERBY_AUTHOR,
3885 struct sort_state {
3886 const enum sort_field *fields;
3887 size_t size, current;
3888 bool reverse;
3891 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3892 #define get_sort_field(state) ((state).fields[(state).current])
3893 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3895 static void
3896 sort_view(struct view *view, enum request request, struct sort_state *state,
3897 int (*compare)(const void *, const void *))
3899 switch (request) {
3900 case REQ_TOGGLE_SORT_FIELD:
3901 state->current = (state->current + 1) % state->size;
3902 break;
3904 case REQ_TOGGLE_SORT_ORDER:
3905 state->reverse = !state->reverse;
3906 break;
3907 default:
3908 die("Not a sort request");
3911 qsort(view->line, view->lines, sizeof(*view->line), compare);
3912 redraw_view(view);
3915 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3917 /* Small author cache to reduce memory consumption. It uses binary
3918 * search to lookup or find place to position new entries. No entries
3919 * are ever freed. */
3920 static const char *
3921 get_author(const char *name)
3923 static const char **authors;
3924 static size_t authors_size;
3925 int from = 0, to = authors_size - 1;
3927 while (from <= to) {
3928 size_t pos = (to + from) / 2;
3929 int cmp = strcmp(name, authors[pos]);
3931 if (!cmp)
3932 return authors[pos];
3934 if (cmp < 0)
3935 to = pos - 1;
3936 else
3937 from = pos + 1;
3940 if (!realloc_authors(&authors, authors_size, 1))
3941 return NULL;
3942 name = strdup(name);
3943 if (!name)
3944 return NULL;
3946 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3947 authors[from] = name;
3948 authors_size++;
3950 return name;
3953 static void
3954 parse_timesec(struct time *time, const char *sec)
3956 time->sec = (time_t) atol(sec);
3959 static void
3960 parse_timezone(struct time *time, const char *zone)
3962 long tz;
3964 tz = ('0' - zone[1]) * 60 * 60 * 10;
3965 tz += ('0' - zone[2]) * 60 * 60;
3966 tz += ('0' - zone[3]) * 60 * 10;
3967 tz += ('0' - zone[4]) * 60;
3969 if (zone[0] == '-')
3970 tz = -tz;
3972 time->tz = tz;
3973 time->sec -= tz;
3976 /* Parse author lines where the name may be empty:
3977 * author <email@address.tld> 1138474660 +0100
3979 static void
3980 parse_author_line(char *ident, const char **author, struct time *time)
3982 char *nameend = strchr(ident, '<');
3983 char *emailend = strchr(ident, '>');
3985 if (nameend && emailend)
3986 *nameend = *emailend = 0;
3987 ident = chomp_string(ident);
3988 if (!*ident) {
3989 if (nameend)
3990 ident = chomp_string(nameend + 1);
3991 if (!*ident)
3992 ident = "Unknown";
3995 *author = get_author(ident);
3997 /* Parse epoch and timezone */
3998 if (emailend && emailend[1] == ' ') {
3999 char *secs = emailend + 2;
4000 char *zone = strchr(secs, ' ');
4002 parse_timesec(time, secs);
4004 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4005 parse_timezone(time, zone + 1);
4010 * Pager backend
4013 static bool
4014 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4016 if (opt_line_number && draw_lineno(view, lineno))
4017 return TRUE;
4019 draw_text(view, line->type, line->data, TRUE);
4020 return TRUE;
4023 static bool
4024 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4026 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4027 char ref[SIZEOF_STR];
4029 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4030 return TRUE;
4032 /* This is the only fatal call, since it can "corrupt" the buffer. */
4033 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4034 return FALSE;
4036 return TRUE;
4039 static void
4040 add_pager_refs(struct view *view, struct line *line)
4042 char buf[SIZEOF_STR];
4043 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4044 struct ref_list *list;
4045 size_t bufpos = 0, i;
4046 const char *sep = "Refs: ";
4047 bool is_tag = FALSE;
4049 assert(line->type == LINE_COMMIT);
4051 list = get_ref_list(commit_id);
4052 if (!list) {
4053 if (view->type == VIEW_DIFF)
4054 goto try_add_describe_ref;
4055 return;
4058 for (i = 0; i < list->size; i++) {
4059 struct ref *ref = list->refs[i];
4060 const char *fmt = ref->tag ? "%s[%s]" :
4061 ref->remote ? "%s<%s>" : "%s%s";
4063 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4064 return;
4065 sep = ", ";
4066 if (ref->tag)
4067 is_tag = TRUE;
4070 if (!is_tag && view->type == VIEW_DIFF) {
4071 try_add_describe_ref:
4072 /* Add <tag>-g<commit_id> "fake" reference. */
4073 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4074 return;
4077 if (bufpos == 0)
4078 return;
4080 add_line_text(view, buf, LINE_PP_REFS);
4083 static bool
4084 pager_read(struct view *view, char *data)
4086 struct line *line;
4088 if (!data)
4089 return TRUE;
4091 line = add_line_text(view, data, get_line_type(data));
4092 if (!line)
4093 return FALSE;
4095 if (line->type == LINE_COMMIT &&
4096 (view->type == VIEW_DIFF ||
4097 view->type == VIEW_LOG))
4098 add_pager_refs(view, line);
4100 return TRUE;
4103 static enum request
4104 pager_request(struct view *view, enum request request, struct line *line)
4106 int split = 0;
4108 if (request != REQ_ENTER)
4109 return request;
4111 if (line->type == LINE_COMMIT &&
4112 (view->type == VIEW_LOG ||
4113 view->type == VIEW_PAGER)) {
4114 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4115 split = 1;
4118 /* Always scroll the view even if it was split. That way
4119 * you can use Enter to scroll through the log view and
4120 * split open each commit diff. */
4121 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4123 /* FIXME: A minor workaround. Scrolling the view will call report("")
4124 * but if we are scrolling a non-current view this won't properly
4125 * update the view title. */
4126 if (split)
4127 update_view_title(view);
4129 return REQ_NONE;
4132 static bool
4133 pager_grep(struct view *view, struct line *line)
4135 const char *text[] = { line->data, NULL };
4137 return grep_text(view, text);
4140 static void
4141 pager_select(struct view *view, struct line *line)
4143 if (line->type == LINE_COMMIT) {
4144 char *text = (char *)line->data + STRING_SIZE("commit ");
4146 if (view->type != VIEW_PAGER)
4147 string_copy_rev(view->ref, text);
4148 string_copy_rev(ref_commit, text);
4152 static struct view_ops pager_ops = {
4153 "line",
4154 NULL,
4155 NULL,
4156 pager_read,
4157 pager_draw,
4158 pager_request,
4159 pager_grep,
4160 pager_select,
4163 static const char *log_argv[SIZEOF_ARG] = {
4164 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4167 static enum request
4168 log_request(struct view *view, enum request request, struct line *line)
4170 switch (request) {
4171 case REQ_REFRESH:
4172 load_refs();
4173 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4174 return REQ_NONE;
4175 default:
4176 return pager_request(view, request, line);
4180 static struct view_ops log_ops = {
4181 "line",
4182 log_argv,
4183 NULL,
4184 pager_read,
4185 pager_draw,
4186 log_request,
4187 pager_grep,
4188 pager_select,
4191 static const char *diff_argv[SIZEOF_ARG] = {
4192 "git", "show", "--pretty=fuller", "--no-color", "--root",
4193 "--patch-with-stat", "--find-copies-harder", "-C",
4194 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4197 static struct view_ops diff_ops = {
4198 "line",
4199 diff_argv,
4200 NULL,
4201 pager_read,
4202 pager_draw,
4203 pager_request,
4204 pager_grep,
4205 pager_select,
4209 * Help backend
4212 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4214 static bool
4215 help_open_keymap_title(struct view *view, enum keymap keymap)
4217 struct line *line;
4219 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4220 help_keymap_hidden[keymap] ? '+' : '-',
4221 enum_name(keymap_table[keymap]));
4222 if (line)
4223 line->other = keymap;
4225 return help_keymap_hidden[keymap];
4228 static void
4229 help_open_keymap(struct view *view, enum keymap keymap)
4231 const char *group = NULL;
4232 char buf[SIZEOF_STR];
4233 size_t bufpos;
4234 bool add_title = TRUE;
4235 int i;
4237 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4238 const char *key = NULL;
4240 if (req_info[i].request == REQ_NONE)
4241 continue;
4243 if (!req_info[i].request) {
4244 group = req_info[i].help;
4245 continue;
4248 key = get_keys(keymap, req_info[i].request, TRUE);
4249 if (!key || !*key)
4250 continue;
4252 if (add_title && help_open_keymap_title(view, keymap))
4253 return;
4254 add_title = FALSE;
4256 if (group) {
4257 add_line_text(view, group, LINE_HELP_GROUP);
4258 group = NULL;
4261 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4262 enum_name(req_info[i]), req_info[i].help);
4265 group = "External commands:";
4267 for (i = 0; i < run_requests; i++) {
4268 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4269 const char *key;
4270 int argc;
4272 if (!req || req->keymap != keymap)
4273 continue;
4275 key = get_key_name(req->key);
4276 if (!*key)
4277 key = "(no key defined)";
4279 if (add_title && help_open_keymap_title(view, keymap))
4280 return;
4281 if (group) {
4282 add_line_text(view, group, LINE_HELP_GROUP);
4283 group = NULL;
4286 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4287 if (!string_format_from(buf, &bufpos, "%s%s",
4288 argc ? " " : "", req->argv[argc]))
4289 return;
4291 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4295 static bool
4296 help_open(struct view *view)
4298 enum keymap keymap;
4300 reset_view(view);
4301 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4302 add_line_text(view, "", LINE_DEFAULT);
4304 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4305 help_open_keymap(view, keymap);
4307 return TRUE;
4310 static enum request
4311 help_request(struct view *view, enum request request, struct line *line)
4313 switch (request) {
4314 case REQ_ENTER:
4315 if (line->type == LINE_HELP_KEYMAP) {
4316 help_keymap_hidden[line->other] =
4317 !help_keymap_hidden[line->other];
4318 view->p_restore = TRUE;
4319 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4322 return REQ_NONE;
4323 default:
4324 return pager_request(view, request, line);
4328 static struct view_ops help_ops = {
4329 "line",
4330 NULL,
4331 help_open,
4332 NULL,
4333 pager_draw,
4334 help_request,
4335 pager_grep,
4336 pager_select,
4341 * Tree backend
4344 struct tree_stack_entry {
4345 struct tree_stack_entry *prev; /* Entry below this in the stack */
4346 unsigned long lineno; /* Line number to restore */
4347 char *name; /* Position of name in opt_path */
4350 /* The top of the path stack. */
4351 static struct tree_stack_entry *tree_stack = NULL;
4352 unsigned long tree_lineno = 0;
4354 static void
4355 pop_tree_stack_entry(void)
4357 struct tree_stack_entry *entry = tree_stack;
4359 tree_lineno = entry->lineno;
4360 entry->name[0] = 0;
4361 tree_stack = entry->prev;
4362 free(entry);
4365 static void
4366 push_tree_stack_entry(const char *name, unsigned long lineno)
4368 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4369 size_t pathlen = strlen(opt_path);
4371 if (!entry)
4372 return;
4374 entry->prev = tree_stack;
4375 entry->name = opt_path + pathlen;
4376 tree_stack = entry;
4378 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4379 pop_tree_stack_entry();
4380 return;
4383 /* Move the current line to the first tree entry. */
4384 tree_lineno = 1;
4385 entry->lineno = lineno;
4388 /* Parse output from git-ls-tree(1):
4390 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4393 #define SIZEOF_TREE_ATTR \
4394 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4396 #define SIZEOF_TREE_MODE \
4397 STRING_SIZE("100644 ")
4399 #define TREE_ID_OFFSET \
4400 STRING_SIZE("100644 blob ")
4402 struct tree_entry {
4403 char id[SIZEOF_REV];
4404 mode_t mode;
4405 struct time time; /* Date from the author ident. */
4406 const char *author; /* Author of the commit. */
4407 char name[1];
4410 static const char *
4411 tree_path(const struct line *line)
4413 return ((struct tree_entry *) line->data)->name;
4416 static int
4417 tree_compare_entry(const struct line *line1, const struct line *line2)
4419 if (line1->type != line2->type)
4420 return line1->type == LINE_TREE_DIR ? -1 : 1;
4421 return strcmp(tree_path(line1), tree_path(line2));
4424 static const enum sort_field tree_sort_fields[] = {
4425 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4427 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4429 static int
4430 tree_compare(const void *l1, const void *l2)
4432 const struct line *line1 = (const struct line *) l1;
4433 const struct line *line2 = (const struct line *) l2;
4434 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4435 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4437 if (line1->type == LINE_TREE_HEAD)
4438 return -1;
4439 if (line2->type == LINE_TREE_HEAD)
4440 return 1;
4442 switch (get_sort_field(tree_sort_state)) {
4443 case ORDERBY_DATE:
4444 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4446 case ORDERBY_AUTHOR:
4447 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4449 case ORDERBY_NAME:
4450 default:
4451 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4456 static struct line *
4457 tree_entry(struct view *view, enum line_type type, const char *path,
4458 const char *mode, const char *id)
4460 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4461 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4463 if (!entry || !line) {
4464 free(entry);
4465 return NULL;
4468 strncpy(entry->name, path, strlen(path));
4469 if (mode)
4470 entry->mode = strtoul(mode, NULL, 8);
4471 if (id)
4472 string_copy_rev(entry->id, id);
4474 return line;
4477 static bool
4478 tree_read_date(struct view *view, char *text, bool *read_date)
4480 static const char *author_name;
4481 static struct time author_time;
4483 if (!text && *read_date) {
4484 *read_date = FALSE;
4485 return TRUE;
4487 } else if (!text) {
4488 char *path = *opt_path ? opt_path : ".";
4489 /* Find next entry to process */
4490 const char *log_file[] = {
4491 "git", "log", "--no-color", "--pretty=raw",
4492 "--cc", "--raw", view->id, "--", path, NULL
4495 if (!view->lines) {
4496 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4497 report("Tree is empty");
4498 return TRUE;
4501 if (!start_update(view, log_file, opt_cdup)) {
4502 report("Failed to load tree data");
4503 return TRUE;
4506 *read_date = TRUE;
4507 return FALSE;
4509 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4510 parse_author_line(text + STRING_SIZE("author "),
4511 &author_name, &author_time);
4513 } else if (*text == ':') {
4514 char *pos;
4515 size_t annotated = 1;
4516 size_t i;
4518 pos = strchr(text, '\t');
4519 if (!pos)
4520 return TRUE;
4521 text = pos + 1;
4522 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4523 text += strlen(opt_path);
4524 pos = strchr(text, '/');
4525 if (pos)
4526 *pos = 0;
4528 for (i = 1; i < view->lines; i++) {
4529 struct line *line = &view->line[i];
4530 struct tree_entry *entry = line->data;
4532 annotated += !!entry->author;
4533 if (entry->author || strcmp(entry->name, text))
4534 continue;
4536 entry->author = author_name;
4537 entry->time = author_time;
4538 line->dirty = 1;
4539 break;
4542 if (annotated == view->lines)
4543 io_kill(view->pipe);
4545 return TRUE;
4548 static bool
4549 tree_read(struct view *view, char *text)
4551 static bool read_date = FALSE;
4552 struct tree_entry *data;
4553 struct line *entry, *line;
4554 enum line_type type;
4555 size_t textlen = text ? strlen(text) : 0;
4556 char *path = text + SIZEOF_TREE_ATTR;
4558 if (read_date || !text)
4559 return tree_read_date(view, text, &read_date);
4561 if (textlen <= SIZEOF_TREE_ATTR)
4562 return FALSE;
4563 if (view->lines == 0 &&
4564 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4565 return FALSE;
4567 /* Strip the path part ... */
4568 if (*opt_path) {
4569 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4570 size_t striplen = strlen(opt_path);
4572 if (pathlen > striplen)
4573 memmove(path, path + striplen,
4574 pathlen - striplen + 1);
4576 /* Insert "link" to parent directory. */
4577 if (view->lines == 1 &&
4578 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4579 return FALSE;
4582 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4583 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4584 if (!entry)
4585 return FALSE;
4586 data = entry->data;
4588 /* Skip "Directory ..." and ".." line. */
4589 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4590 if (tree_compare_entry(line, entry) <= 0)
4591 continue;
4593 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4595 line->data = data;
4596 line->type = type;
4597 for (; line <= entry; line++)
4598 line->dirty = line->cleareol = 1;
4599 return TRUE;
4602 if (tree_lineno > view->lineno) {
4603 view->lineno = tree_lineno;
4604 tree_lineno = 0;
4607 return TRUE;
4610 static bool
4611 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4613 struct tree_entry *entry = line->data;
4615 if (line->type == LINE_TREE_HEAD) {
4616 if (draw_text(view, line->type, "Directory path /", TRUE))
4617 return TRUE;
4618 } else {
4619 if (draw_mode(view, entry->mode))
4620 return TRUE;
4622 if (opt_author && draw_author(view, entry->author))
4623 return TRUE;
4625 if (opt_date && draw_date(view, &entry->time))
4626 return TRUE;
4628 if (draw_text(view, line->type, entry->name, TRUE))
4629 return TRUE;
4630 return TRUE;
4633 static void
4634 open_blob_editor(const char *id)
4636 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4637 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4638 int fd = mkstemp(file);
4640 if (fd == -1)
4641 report("Failed to create temporary file");
4642 else if (!io_run_append(blob_argv, fd))
4643 report("Failed to save blob data to file");
4644 else
4645 open_editor(file);
4646 if (fd != -1)
4647 unlink(file);
4650 static enum request
4651 tree_request(struct view *view, enum request request, struct line *line)
4653 enum open_flags flags;
4654 struct tree_entry *entry = line->data;
4656 switch (request) {
4657 case REQ_VIEW_BLAME:
4658 if (line->type != LINE_TREE_FILE) {
4659 report("Blame only supported for files");
4660 return REQ_NONE;
4663 string_copy(opt_ref, view->vid);
4664 return request;
4666 case REQ_EDIT:
4667 if (line->type != LINE_TREE_FILE) {
4668 report("Edit only supported for files");
4669 } else if (!is_head_commit(view->vid)) {
4670 open_blob_editor(entry->id);
4671 } else {
4672 open_editor(opt_file);
4674 return REQ_NONE;
4676 case REQ_TOGGLE_SORT_FIELD:
4677 case REQ_TOGGLE_SORT_ORDER:
4678 sort_view(view, request, &tree_sort_state, tree_compare);
4679 return REQ_NONE;
4681 case REQ_PARENT:
4682 if (!*opt_path) {
4683 /* quit view if at top of tree */
4684 return REQ_VIEW_CLOSE;
4686 /* fake 'cd ..' */
4687 line = &view->line[1];
4688 break;
4690 case REQ_ENTER:
4691 break;
4693 default:
4694 return request;
4697 /* Cleanup the stack if the tree view is at a different tree. */
4698 while (!*opt_path && tree_stack)
4699 pop_tree_stack_entry();
4701 switch (line->type) {
4702 case LINE_TREE_DIR:
4703 /* Depending on whether it is a subdirectory or parent link
4704 * mangle the path buffer. */
4705 if (line == &view->line[1] && *opt_path) {
4706 pop_tree_stack_entry();
4708 } else {
4709 const char *basename = tree_path(line);
4711 push_tree_stack_entry(basename, view->lineno);
4714 /* Trees and subtrees share the same ID, so they are not not
4715 * unique like blobs. */
4716 flags = OPEN_RELOAD;
4717 request = REQ_VIEW_TREE;
4718 break;
4720 case LINE_TREE_FILE:
4721 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4722 request = REQ_VIEW_BLOB;
4723 break;
4725 default:
4726 return REQ_NONE;
4729 open_view(view, request, flags);
4730 if (request == REQ_VIEW_TREE)
4731 view->lineno = tree_lineno;
4733 return REQ_NONE;
4736 static bool
4737 tree_grep(struct view *view, struct line *line)
4739 struct tree_entry *entry = line->data;
4740 const char *text[] = {
4741 entry->name,
4742 opt_author ? entry->author : "",
4743 mkdate(&entry->time, opt_date),
4744 NULL
4747 return grep_text(view, text);
4750 static void
4751 tree_select(struct view *view, struct line *line)
4753 struct tree_entry *entry = line->data;
4755 if (line->type == LINE_TREE_FILE) {
4756 string_copy_rev(ref_blob, entry->id);
4757 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4759 } else if (line->type != LINE_TREE_DIR) {
4760 return;
4763 string_copy_rev(view->ref, entry->id);
4766 static bool
4767 tree_prepare(struct view *view)
4769 if (view->lines == 0 && opt_prefix[0]) {
4770 char *pos = opt_prefix;
4772 while (pos && *pos) {
4773 char *end = strchr(pos, '/');
4775 if (end)
4776 *end = 0;
4777 push_tree_stack_entry(pos, 0);
4778 pos = end;
4779 if (end) {
4780 *end = '/';
4781 pos++;
4785 } else if (strcmp(view->vid, view->id)) {
4786 opt_path[0] = 0;
4789 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4792 static const char *tree_argv[SIZEOF_ARG] = {
4793 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4796 static struct view_ops tree_ops = {
4797 "file",
4798 tree_argv,
4799 NULL,
4800 tree_read,
4801 tree_draw,
4802 tree_request,
4803 tree_grep,
4804 tree_select,
4805 tree_prepare,
4808 static bool
4809 blob_read(struct view *view, char *line)
4811 if (!line)
4812 return TRUE;
4813 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4816 static enum request
4817 blob_request(struct view *view, enum request request, struct line *line)
4819 switch (request) {
4820 case REQ_EDIT:
4821 open_blob_editor(view->vid);
4822 return REQ_NONE;
4823 default:
4824 return pager_request(view, request, line);
4828 static const char *blob_argv[SIZEOF_ARG] = {
4829 "git", "cat-file", "blob", "%(blob)", NULL
4832 static struct view_ops blob_ops = {
4833 "line",
4834 blob_argv,
4835 NULL,
4836 blob_read,
4837 pager_draw,
4838 blob_request,
4839 pager_grep,
4840 pager_select,
4844 * Blame backend
4846 * Loading the blame view is a two phase job:
4848 * 1. File content is read either using opt_file from the
4849 * filesystem or using git-cat-file.
4850 * 2. Then blame information is incrementally added by
4851 * reading output from git-blame.
4854 struct blame_commit {
4855 char id[SIZEOF_REV]; /* SHA1 ID. */
4856 char title[128]; /* First line of the commit message. */
4857 const char *author; /* Author of the commit. */
4858 struct time time; /* Date from the author ident. */
4859 char filename[128]; /* Name of file. */
4860 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4861 char parent_filename[128]; /* Parent/previous name of file. */
4864 struct blame {
4865 struct blame_commit *commit;
4866 unsigned long lineno;
4867 char text[1];
4870 static bool
4871 blame_open(struct view *view)
4873 char path[SIZEOF_STR];
4874 size_t i;
4876 if (!view->prev && *opt_prefix) {
4877 string_copy(path, opt_file);
4878 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4879 return FALSE;
4882 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4883 const char *blame_cat_file_argv[] = {
4884 "git", "cat-file", "blob", path, NULL
4887 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4888 !start_update(view, blame_cat_file_argv, opt_cdup))
4889 return FALSE;
4892 /* First pass: remove multiple references to the same commit. */
4893 for (i = 0; i < view->lines; i++) {
4894 struct blame *blame = view->line[i].data;
4896 if (blame->commit && blame->commit->id[0])
4897 blame->commit->id[0] = 0;
4898 else
4899 blame->commit = NULL;
4902 /* Second pass: free existing references. */
4903 for (i = 0; i < view->lines; i++) {
4904 struct blame *blame = view->line[i].data;
4906 if (blame->commit)
4907 free(blame->commit);
4910 setup_update(view, opt_file);
4911 string_format(view->ref, "%s ...", opt_file);
4913 return TRUE;
4916 static struct blame_commit *
4917 get_blame_commit(struct view *view, const char *id)
4919 size_t i;
4921 for (i = 0; i < view->lines; i++) {
4922 struct blame *blame = view->line[i].data;
4924 if (!blame->commit)
4925 continue;
4927 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4928 return blame->commit;
4932 struct blame_commit *commit = calloc(1, sizeof(*commit));
4934 if (commit)
4935 string_ncopy(commit->id, id, SIZEOF_REV);
4936 return commit;
4940 static bool
4941 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4943 const char *pos = *posref;
4945 *posref = NULL;
4946 pos = strchr(pos + 1, ' ');
4947 if (!pos || !isdigit(pos[1]))
4948 return FALSE;
4949 *number = atoi(pos + 1);
4950 if (*number < min || *number > max)
4951 return FALSE;
4953 *posref = pos;
4954 return TRUE;
4957 static struct blame_commit *
4958 parse_blame_commit(struct view *view, const char *text, int *blamed)
4960 struct blame_commit *commit;
4961 struct blame *blame;
4962 const char *pos = text + SIZEOF_REV - 2;
4963 size_t orig_lineno = 0;
4964 size_t lineno;
4965 size_t group;
4967 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4968 return NULL;
4970 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4971 !parse_number(&pos, &lineno, 1, view->lines) ||
4972 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4973 return NULL;
4975 commit = get_blame_commit(view, text);
4976 if (!commit)
4977 return NULL;
4979 *blamed += group;
4980 while (group--) {
4981 struct line *line = &view->line[lineno + group - 1];
4983 blame = line->data;
4984 blame->commit = commit;
4985 blame->lineno = orig_lineno + group - 1;
4986 line->dirty = 1;
4989 return commit;
4992 static bool
4993 blame_read_file(struct view *view, const char *line, bool *read_file)
4995 if (!line) {
4996 const char *blame_argv[] = {
4997 "git", "blame", "--incremental",
4998 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5001 if (view->lines == 0 && !view->prev)
5002 die("No blame exist for %s", view->vid);
5004 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5005 report("Failed to load blame data");
5006 return TRUE;
5009 *read_file = FALSE;
5010 return FALSE;
5012 } else {
5013 size_t linelen = strlen(line);
5014 struct blame *blame = malloc(sizeof(*blame) + linelen);
5016 if (!blame)
5017 return FALSE;
5019 blame->commit = NULL;
5020 strncpy(blame->text, line, linelen);
5021 blame->text[linelen] = 0;
5022 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5026 static bool
5027 match_blame_header(const char *name, char **line)
5029 size_t namelen = strlen(name);
5030 bool matched = !strncmp(name, *line, namelen);
5032 if (matched)
5033 *line += namelen;
5035 return matched;
5038 static bool
5039 blame_read(struct view *view, char *line)
5041 static struct blame_commit *commit = NULL;
5042 static int blamed = 0;
5043 static bool read_file = TRUE;
5045 if (read_file)
5046 return blame_read_file(view, line, &read_file);
5048 if (!line) {
5049 /* Reset all! */
5050 commit = NULL;
5051 blamed = 0;
5052 read_file = TRUE;
5053 string_format(view->ref, "%s", view->vid);
5054 if (view_is_displayed(view)) {
5055 update_view_title(view);
5056 redraw_view_from(view, 0);
5058 return TRUE;
5061 if (!commit) {
5062 commit = parse_blame_commit(view, line, &blamed);
5063 string_format(view->ref, "%s %2d%%", view->vid,
5064 view->lines ? blamed * 100 / view->lines : 0);
5066 } else if (match_blame_header("author ", &line)) {
5067 commit->author = get_author(line);
5069 } else if (match_blame_header("author-time ", &line)) {
5070 parse_timesec(&commit->time, line);
5072 } else if (match_blame_header("author-tz ", &line)) {
5073 parse_timezone(&commit->time, line);
5075 } else if (match_blame_header("summary ", &line)) {
5076 string_ncopy(commit->title, line, strlen(line));
5078 } else if (match_blame_header("previous ", &line)) {
5079 if (strlen(line) <= SIZEOF_REV)
5080 return FALSE;
5081 string_copy_rev(commit->parent_id, line);
5082 line += SIZEOF_REV;
5083 string_ncopy(commit->parent_filename, line, strlen(line));
5085 } else if (match_blame_header("filename ", &line)) {
5086 string_ncopy(commit->filename, line, strlen(line));
5087 commit = NULL;
5090 return TRUE;
5093 static bool
5094 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5096 struct blame *blame = line->data;
5097 struct time *time = NULL;
5098 const char *id = NULL, *author = NULL;
5100 if (blame->commit && *blame->commit->filename) {
5101 id = blame->commit->id;
5102 author = blame->commit->author;
5103 time = &blame->commit->time;
5106 if (opt_date && draw_date(view, time))
5107 return TRUE;
5109 if (opt_author && draw_author(view, author))
5110 return TRUE;
5112 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5113 return TRUE;
5115 if (draw_lineno(view, lineno))
5116 return TRUE;
5118 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5119 return TRUE;
5122 static bool
5123 check_blame_commit(struct blame *blame, bool check_null_id)
5125 if (!blame->commit)
5126 report("Commit data not loaded yet");
5127 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5128 report("No commit exist for the selected line");
5129 else
5130 return TRUE;
5131 return FALSE;
5134 static void
5135 setup_blame_parent_line(struct view *view, struct blame *blame)
5137 char from[SIZEOF_REF + SIZEOF_STR];
5138 char to[SIZEOF_REF + SIZEOF_STR];
5139 const char *diff_tree_argv[] = {
5140 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5141 "-U0", from, to, "--", NULL
5143 struct io io;
5144 int parent_lineno = -1;
5145 int blamed_lineno = -1;
5146 char *line;
5148 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5149 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5150 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5151 return;
5153 while ((line = io_get(&io, '\n', TRUE))) {
5154 if (*line == '@') {
5155 char *pos = strchr(line, '+');
5157 parent_lineno = atoi(line + 4);
5158 if (pos)
5159 blamed_lineno = atoi(pos + 1);
5161 } else if (*line == '+' && parent_lineno != -1) {
5162 if (blame->lineno == blamed_lineno - 1 &&
5163 !strcmp(blame->text, line + 1)) {
5164 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5165 break;
5167 blamed_lineno++;
5171 io_done(&io);
5174 static enum request
5175 blame_request(struct view *view, enum request request, struct line *line)
5177 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5178 struct blame *blame = line->data;
5180 switch (request) {
5181 case REQ_VIEW_BLAME:
5182 if (check_blame_commit(blame, TRUE)) {
5183 string_copy(opt_ref, blame->commit->id);
5184 string_copy(opt_file, blame->commit->filename);
5185 if (blame->lineno)
5186 view->lineno = blame->lineno;
5187 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5189 break;
5191 case REQ_PARENT:
5192 if (!check_blame_commit(blame, TRUE))
5193 break;
5194 if (!*blame->commit->parent_id) {
5195 report("The selected commit has no parents");
5196 } else {
5197 string_copy_rev(opt_ref, blame->commit->parent_id);
5198 string_copy(opt_file, blame->commit->parent_filename);
5199 setup_blame_parent_line(view, blame);
5200 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5202 break;
5204 case REQ_ENTER:
5205 if (!check_blame_commit(blame, FALSE))
5206 break;
5208 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5209 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5210 break;
5212 if (!strcmp(blame->commit->id, NULL_ID)) {
5213 struct view *diff = VIEW(REQ_VIEW_DIFF);
5214 const char *diff_index_argv[] = {
5215 "git", "diff-index", "--root", "--patch-with-stat",
5216 "-C", "-M", "HEAD", "--", view->vid, NULL
5219 if (!*blame->commit->parent_id) {
5220 diff_index_argv[1] = "diff";
5221 diff_index_argv[2] = "--no-color";
5222 diff_index_argv[6] = "--";
5223 diff_index_argv[7] = "/dev/null";
5226 if (!prepare_update(diff, diff_index_argv, NULL)) {
5227 report("Failed to allocate diff command");
5228 break;
5230 flags |= OPEN_PREPARED;
5233 open_view(view, REQ_VIEW_DIFF, flags);
5234 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5235 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5236 break;
5238 default:
5239 return request;
5242 return REQ_NONE;
5245 static bool
5246 blame_grep(struct view *view, struct line *line)
5248 struct blame *blame = line->data;
5249 struct blame_commit *commit = blame->commit;
5250 const char *text[] = {
5251 blame->text,
5252 commit ? commit->title : "",
5253 commit ? commit->id : "",
5254 commit && opt_author ? commit->author : "",
5255 commit ? mkdate(&commit->time, opt_date) : "",
5256 NULL
5259 return grep_text(view, text);
5262 static void
5263 blame_select(struct view *view, struct line *line)
5265 struct blame *blame = line->data;
5266 struct blame_commit *commit = blame->commit;
5268 if (!commit)
5269 return;
5271 if (!strcmp(commit->id, NULL_ID))
5272 string_ncopy(ref_commit, "HEAD", 4);
5273 else
5274 string_copy_rev(ref_commit, commit->id);
5277 static struct view_ops blame_ops = {
5278 "line",
5279 NULL,
5280 blame_open,
5281 blame_read,
5282 blame_draw,
5283 blame_request,
5284 blame_grep,
5285 blame_select,
5289 * Branch backend
5292 struct branch {
5293 const char *author; /* Author of the last commit. */
5294 struct time time; /* Date of the last activity. */
5295 const struct ref *ref; /* Name and commit ID information. */
5298 static const struct ref branch_all;
5300 static const enum sort_field branch_sort_fields[] = {
5301 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5303 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5305 static int
5306 branch_compare(const void *l1, const void *l2)
5308 const struct branch *branch1 = ((const struct line *) l1)->data;
5309 const struct branch *branch2 = ((const struct line *) l2)->data;
5311 switch (get_sort_field(branch_sort_state)) {
5312 case ORDERBY_DATE:
5313 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5315 case ORDERBY_AUTHOR:
5316 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5318 case ORDERBY_NAME:
5319 default:
5320 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5324 static bool
5325 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5327 struct branch *branch = line->data;
5328 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5330 if (opt_date && draw_date(view, &branch->time))
5331 return TRUE;
5333 if (opt_author && draw_author(view, branch->author))
5334 return TRUE;
5336 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5337 return TRUE;
5340 static enum request
5341 branch_request(struct view *view, enum request request, struct line *line)
5343 struct branch *branch = line->data;
5345 switch (request) {
5346 case REQ_REFRESH:
5347 load_refs();
5348 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5349 return REQ_NONE;
5351 case REQ_TOGGLE_SORT_FIELD:
5352 case REQ_TOGGLE_SORT_ORDER:
5353 sort_view(view, request, &branch_sort_state, branch_compare);
5354 return REQ_NONE;
5356 case REQ_ENTER:
5358 const struct ref *ref = branch->ref;
5359 const char *all_branches_argv[] = {
5360 "git", "log", "--no-color", "--pretty=raw", "--parents",
5361 "--topo-order",
5362 ref == &branch_all ? "--all" : ref->name, NULL
5364 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5366 if (!prepare_update(main_view, all_branches_argv, NULL))
5367 report("Failed to load view of all branches");
5368 else
5369 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5370 return REQ_NONE;
5372 default:
5373 return request;
5377 static bool
5378 branch_read(struct view *view, char *line)
5380 static char id[SIZEOF_REV];
5381 struct branch *reference;
5382 size_t i;
5384 if (!line)
5385 return TRUE;
5387 switch (get_line_type(line)) {
5388 case LINE_COMMIT:
5389 string_copy_rev(id, line + STRING_SIZE("commit "));
5390 return TRUE;
5392 case LINE_AUTHOR:
5393 for (i = 0, reference = NULL; i < view->lines; i++) {
5394 struct branch *branch = view->line[i].data;
5396 if (strcmp(branch->ref->id, id))
5397 continue;
5399 view->line[i].dirty = TRUE;
5400 if (reference) {
5401 branch->author = reference->author;
5402 branch->time = reference->time;
5403 continue;
5406 parse_author_line(line + STRING_SIZE("author "),
5407 &branch->author, &branch->time);
5408 reference = branch;
5410 return TRUE;
5412 default:
5413 return TRUE;
5418 static bool
5419 branch_open_visitor(void *data, const struct ref *ref)
5421 struct view *view = data;
5422 struct branch *branch;
5424 if (ref->tag || ref->ltag || ref->remote)
5425 return TRUE;
5427 branch = calloc(1, sizeof(*branch));
5428 if (!branch)
5429 return FALSE;
5431 branch->ref = ref;
5432 return !!add_line_data(view, branch, LINE_DEFAULT);
5435 static bool
5436 branch_open(struct view *view)
5438 const char *branch_log[] = {
5439 "git", "log", "--no-color", "--pretty=raw",
5440 "--simplify-by-decoration", "--all", NULL
5443 if (!start_update(view, branch_log, NULL)) {
5444 report("Failed to load branch data");
5445 return TRUE;
5448 setup_update(view, view->id);
5449 branch_open_visitor(view, &branch_all);
5450 foreach_ref(branch_open_visitor, view);
5451 view->p_restore = TRUE;
5453 return TRUE;
5456 static bool
5457 branch_grep(struct view *view, struct line *line)
5459 struct branch *branch = line->data;
5460 const char *text[] = {
5461 branch->ref->name,
5462 branch->author,
5463 NULL
5466 return grep_text(view, text);
5469 static void
5470 branch_select(struct view *view, struct line *line)
5472 struct branch *branch = line->data;
5474 string_copy_rev(view->ref, branch->ref->id);
5475 string_copy_rev(ref_commit, branch->ref->id);
5476 string_copy_rev(ref_head, branch->ref->id);
5477 string_copy_rev(ref_branch, branch->ref->name);
5480 static struct view_ops branch_ops = {
5481 "branch",
5482 NULL,
5483 branch_open,
5484 branch_read,
5485 branch_draw,
5486 branch_request,
5487 branch_grep,
5488 branch_select,
5492 * Status backend
5495 struct status {
5496 char status;
5497 struct {
5498 mode_t mode;
5499 char rev[SIZEOF_REV];
5500 char name[SIZEOF_STR];
5501 } old;
5502 struct {
5503 mode_t mode;
5504 char rev[SIZEOF_REV];
5505 char name[SIZEOF_STR];
5506 } new;
5509 static char status_onbranch[SIZEOF_STR];
5510 static struct status stage_status;
5511 static enum line_type stage_line_type;
5512 static size_t stage_chunks;
5513 static int *stage_chunk;
5515 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5517 /* This should work even for the "On branch" line. */
5518 static inline bool
5519 status_has_none(struct view *view, struct line *line)
5521 return line < view->line + view->lines && !line[1].data;
5524 /* Get fields from the diff line:
5525 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5527 static inline bool
5528 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5530 const char *old_mode = buf + 1;
5531 const char *new_mode = buf + 8;
5532 const char *old_rev = buf + 15;
5533 const char *new_rev = buf + 56;
5534 const char *status = buf + 97;
5536 if (bufsize < 98 ||
5537 old_mode[-1] != ':' ||
5538 new_mode[-1] != ' ' ||
5539 old_rev[-1] != ' ' ||
5540 new_rev[-1] != ' ' ||
5541 status[-1] != ' ')
5542 return FALSE;
5544 file->status = *status;
5546 string_copy_rev(file->old.rev, old_rev);
5547 string_copy_rev(file->new.rev, new_rev);
5549 file->old.mode = strtoul(old_mode, NULL, 8);
5550 file->new.mode = strtoul(new_mode, NULL, 8);
5552 file->old.name[0] = file->new.name[0] = 0;
5554 return TRUE;
5557 static bool
5558 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5560 struct status *unmerged = NULL;
5561 char *buf;
5562 struct io io;
5564 if (!io_run(&io, IO_RD, opt_cdup, argv))
5565 return FALSE;
5567 add_line_data(view, NULL, type);
5569 while ((buf = io_get(&io, 0, TRUE))) {
5570 struct status *file = unmerged;
5572 if (!file) {
5573 file = calloc(1, sizeof(*file));
5574 if (!file || !add_line_data(view, file, type))
5575 goto error_out;
5578 /* Parse diff info part. */
5579 if (status) {
5580 file->status = status;
5581 if (status == 'A')
5582 string_copy(file->old.rev, NULL_ID);
5584 } else if (!file->status || file == unmerged) {
5585 if (!status_get_diff(file, buf, strlen(buf)))
5586 goto error_out;
5588 buf = io_get(&io, 0, TRUE);
5589 if (!buf)
5590 break;
5592 /* Collapse all modified entries that follow an
5593 * associated unmerged entry. */
5594 if (unmerged == file) {
5595 unmerged->status = 'U';
5596 unmerged = NULL;
5597 } else if (file->status == 'U') {
5598 unmerged = file;
5602 /* Grab the old name for rename/copy. */
5603 if (!*file->old.name &&
5604 (file->status == 'R' || file->status == 'C')) {
5605 string_ncopy(file->old.name, buf, strlen(buf));
5607 buf = io_get(&io, 0, TRUE);
5608 if (!buf)
5609 break;
5612 /* git-ls-files just delivers a NUL separated list of
5613 * file names similar to the second half of the
5614 * git-diff-* output. */
5615 string_ncopy(file->new.name, buf, strlen(buf));
5616 if (!*file->old.name)
5617 string_copy(file->old.name, file->new.name);
5618 file = NULL;
5621 if (io_error(&io)) {
5622 error_out:
5623 io_done(&io);
5624 return FALSE;
5627 if (!view->line[view->lines - 1].data)
5628 add_line_data(view, NULL, LINE_STAT_NONE);
5630 io_done(&io);
5631 return TRUE;
5634 /* Don't show unmerged entries in the staged section. */
5635 static const char *status_diff_index_argv[] = {
5636 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5637 "--cached", "-M", "HEAD", NULL
5640 static const char *status_diff_files_argv[] = {
5641 "git", "diff-files", "-z", NULL
5644 static const char *status_list_other_argv[] = {
5645 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5648 static const char *status_list_no_head_argv[] = {
5649 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5652 static const char *update_index_argv[] = {
5653 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5656 /* Restore the previous line number to stay in the context or select a
5657 * line with something that can be updated. */
5658 static void
5659 status_restore(struct view *view)
5661 if (view->p_lineno >= view->lines)
5662 view->p_lineno = view->lines - 1;
5663 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5664 view->p_lineno++;
5665 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5666 view->p_lineno--;
5668 /* If the above fails, always skip the "On branch" line. */
5669 if (view->p_lineno < view->lines)
5670 view->lineno = view->p_lineno;
5671 else
5672 view->lineno = 1;
5674 if (view->lineno < view->offset)
5675 view->offset = view->lineno;
5676 else if (view->offset + view->height <= view->lineno)
5677 view->offset = view->lineno - view->height + 1;
5679 view->p_restore = FALSE;
5682 static void
5683 status_update_onbranch(void)
5685 static const char *paths[][2] = {
5686 { "rebase-apply/rebasing", "Rebasing" },
5687 { "rebase-apply/applying", "Applying mailbox" },
5688 { "rebase-apply/", "Rebasing mailbox" },
5689 { "rebase-merge/interactive", "Interactive rebase" },
5690 { "rebase-merge/", "Rebase merge" },
5691 { "MERGE_HEAD", "Merging" },
5692 { "BISECT_LOG", "Bisecting" },
5693 { "HEAD", "On branch" },
5695 char buf[SIZEOF_STR];
5696 struct stat stat;
5697 int i;
5699 if (is_initial_commit()) {
5700 string_copy(status_onbranch, "Initial commit");
5701 return;
5704 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5705 char *head = opt_head;
5707 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5708 lstat(buf, &stat) < 0)
5709 continue;
5711 if (!*opt_head) {
5712 struct io io;
5714 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5715 io_read_buf(&io, buf, sizeof(buf))) {
5716 head = buf;
5717 if (!prefixcmp(head, "refs/heads/"))
5718 head += STRING_SIZE("refs/heads/");
5722 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5723 string_copy(status_onbranch, opt_head);
5724 return;
5727 string_copy(status_onbranch, "Not currently on any branch");
5730 /* First parse staged info using git-diff-index(1), then parse unstaged
5731 * info using git-diff-files(1), and finally untracked files using
5732 * git-ls-files(1). */
5733 static bool
5734 status_open(struct view *view)
5736 reset_view(view);
5738 add_line_data(view, NULL, LINE_STAT_HEAD);
5739 status_update_onbranch();
5741 io_run_bg(update_index_argv);
5743 if (is_initial_commit()) {
5744 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5745 return FALSE;
5746 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5747 return FALSE;
5750 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5751 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5752 return FALSE;
5754 /* Restore the exact position or use the specialized restore
5755 * mode? */
5756 if (!view->p_restore)
5757 status_restore(view);
5758 return TRUE;
5761 static bool
5762 status_draw(struct view *view, struct line *line, unsigned int lineno)
5764 struct status *status = line->data;
5765 enum line_type type;
5766 const char *text;
5768 if (!status) {
5769 switch (line->type) {
5770 case LINE_STAT_STAGED:
5771 type = LINE_STAT_SECTION;
5772 text = "Changes to be committed:";
5773 break;
5775 case LINE_STAT_UNSTAGED:
5776 type = LINE_STAT_SECTION;
5777 text = "Changed but not updated:";
5778 break;
5780 case LINE_STAT_UNTRACKED:
5781 type = LINE_STAT_SECTION;
5782 text = "Untracked files:";
5783 break;
5785 case LINE_STAT_NONE:
5786 type = LINE_DEFAULT;
5787 text = " (no files)";
5788 break;
5790 case LINE_STAT_HEAD:
5791 type = LINE_STAT_HEAD;
5792 text = status_onbranch;
5793 break;
5795 default:
5796 return FALSE;
5798 } else {
5799 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5801 buf[0] = status->status;
5802 if (draw_text(view, line->type, buf, TRUE))
5803 return TRUE;
5804 type = LINE_DEFAULT;
5805 text = status->new.name;
5808 draw_text(view, type, text, TRUE);
5809 return TRUE;
5812 static enum request
5813 status_load_error(struct view *view, struct view *stage, const char *path)
5815 if (displayed_views() == 2 || display[current_view] != view)
5816 maximize_view(view);
5817 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5818 return REQ_NONE;
5821 static enum request
5822 status_enter(struct view *view, struct line *line)
5824 struct status *status = line->data;
5825 const char *oldpath = status ? status->old.name : NULL;
5826 /* Diffs for unmerged entries are empty when passing the new
5827 * path, so leave it empty. */
5828 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5829 const char *info;
5830 enum open_flags split;
5831 struct view *stage = VIEW(REQ_VIEW_STAGE);
5833 if (line->type == LINE_STAT_NONE ||
5834 (!status && line[1].type == LINE_STAT_NONE)) {
5835 report("No file to diff");
5836 return REQ_NONE;
5839 switch (line->type) {
5840 case LINE_STAT_STAGED:
5841 if (is_initial_commit()) {
5842 const char *no_head_diff_argv[] = {
5843 "git", "diff", "--no-color", "--patch-with-stat",
5844 "--", "/dev/null", newpath, NULL
5847 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5848 return status_load_error(view, stage, newpath);
5849 } else {
5850 const char *index_show_argv[] = {
5851 "git", "diff-index", "--root", "--patch-with-stat",
5852 "-C", "-M", "--cached", "HEAD", "--",
5853 oldpath, newpath, NULL
5856 if (!prepare_update(stage, index_show_argv, opt_cdup))
5857 return status_load_error(view, stage, newpath);
5860 if (status)
5861 info = "Staged changes to %s";
5862 else
5863 info = "Staged changes";
5864 break;
5866 case LINE_STAT_UNSTAGED:
5868 const char *files_show_argv[] = {
5869 "git", "diff-files", "--root", "--patch-with-stat",
5870 "-C", "-M", "--", oldpath, newpath, NULL
5873 if (!prepare_update(stage, files_show_argv, opt_cdup))
5874 return status_load_error(view, stage, newpath);
5875 if (status)
5876 info = "Unstaged changes to %s";
5877 else
5878 info = "Unstaged changes";
5879 break;
5881 case LINE_STAT_UNTRACKED:
5882 if (!newpath) {
5883 report("No file to show");
5884 return REQ_NONE;
5887 if (!suffixcmp(status->new.name, -1, "/")) {
5888 report("Cannot display a directory");
5889 return REQ_NONE;
5892 if (!prepare_update_file(stage, newpath))
5893 return status_load_error(view, stage, newpath);
5894 info = "Untracked file %s";
5895 break;
5897 case LINE_STAT_HEAD:
5898 return REQ_NONE;
5900 default:
5901 die("line type %d not handled in switch", line->type);
5904 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5905 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5906 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5907 if (status) {
5908 stage_status = *status;
5909 } else {
5910 memset(&stage_status, 0, sizeof(stage_status));
5913 stage_line_type = line->type;
5914 stage_chunks = 0;
5915 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5918 return REQ_NONE;
5921 static bool
5922 status_exists(struct status *status, enum line_type type)
5924 struct view *view = VIEW(REQ_VIEW_STATUS);
5925 unsigned long lineno;
5927 for (lineno = 0; lineno < view->lines; lineno++) {
5928 struct line *line = &view->line[lineno];
5929 struct status *pos = line->data;
5931 if (line->type != type)
5932 continue;
5933 if (!pos && (!status || !status->status) && line[1].data) {
5934 select_view_line(view, lineno);
5935 return TRUE;
5937 if (pos && !strcmp(status->new.name, pos->new.name)) {
5938 select_view_line(view, lineno);
5939 return TRUE;
5943 return FALSE;
5947 static bool
5948 status_update_prepare(struct io *io, enum line_type type)
5950 const char *staged_argv[] = {
5951 "git", "update-index", "-z", "--index-info", NULL
5953 const char *others_argv[] = {
5954 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5957 switch (type) {
5958 case LINE_STAT_STAGED:
5959 return io_run(io, IO_WR, opt_cdup, staged_argv);
5961 case LINE_STAT_UNSTAGED:
5962 case LINE_STAT_UNTRACKED:
5963 return io_run(io, IO_WR, opt_cdup, others_argv);
5965 default:
5966 die("line type %d not handled in switch", type);
5967 return FALSE;
5971 static bool
5972 status_update_write(struct io *io, struct status *status, enum line_type type)
5974 char buf[SIZEOF_STR];
5975 size_t bufsize = 0;
5977 switch (type) {
5978 case LINE_STAT_STAGED:
5979 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5980 status->old.mode,
5981 status->old.rev,
5982 status->old.name, 0))
5983 return FALSE;
5984 break;
5986 case LINE_STAT_UNSTAGED:
5987 case LINE_STAT_UNTRACKED:
5988 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5989 return FALSE;
5990 break;
5992 default:
5993 die("line type %d not handled in switch", type);
5996 return io_write(io, buf, bufsize);
5999 static bool
6000 status_update_file(struct status *status, enum line_type type)
6002 struct io io;
6003 bool result;
6005 if (!status_update_prepare(&io, type))
6006 return FALSE;
6008 result = status_update_write(&io, status, type);
6009 return io_done(&io) && result;
6012 static bool
6013 status_update_files(struct view *view, struct line *line)
6015 char buf[sizeof(view->ref)];
6016 struct io io;
6017 bool result = TRUE;
6018 struct line *pos = view->line + view->lines;
6019 int files = 0;
6020 int file, done;
6021 int cursor_y = -1, cursor_x = -1;
6023 if (!status_update_prepare(&io, line->type))
6024 return FALSE;
6026 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6027 files++;
6029 string_copy(buf, view->ref);
6030 getsyx(cursor_y, cursor_x);
6031 for (file = 0, done = 5; result && file < files; line++, file++) {
6032 int almost_done = file * 100 / files;
6034 if (almost_done > done) {
6035 done = almost_done;
6036 string_format(view->ref, "updating file %u of %u (%d%% done)",
6037 file, files, done);
6038 update_view_title(view);
6039 setsyx(cursor_y, cursor_x);
6040 doupdate();
6042 result = status_update_write(&io, line->data, line->type);
6044 string_copy(view->ref, buf);
6046 return io_done(&io) && result;
6049 static bool
6050 status_update(struct view *view)
6052 struct line *line = &view->line[view->lineno];
6054 assert(view->lines);
6056 if (!line->data) {
6057 /* This should work even for the "On branch" line. */
6058 if (line < view->line + view->lines && !line[1].data) {
6059 report("Nothing to update");
6060 return FALSE;
6063 if (!status_update_files(view, line + 1)) {
6064 report("Failed to update file status");
6065 return FALSE;
6068 } else if (!status_update_file(line->data, line->type)) {
6069 report("Failed to update file status");
6070 return FALSE;
6073 return TRUE;
6076 static bool
6077 status_revert(struct status *status, enum line_type type, bool has_none)
6079 if (!status || type != LINE_STAT_UNSTAGED) {
6080 if (type == LINE_STAT_STAGED) {
6081 report("Cannot revert changes to staged files");
6082 } else if (type == LINE_STAT_UNTRACKED) {
6083 report("Cannot revert changes to untracked files");
6084 } else if (has_none) {
6085 report("Nothing to revert");
6086 } else {
6087 report("Cannot revert changes to multiple files");
6090 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6091 char mode[10] = "100644";
6092 const char *reset_argv[] = {
6093 "git", "update-index", "--cacheinfo", mode,
6094 status->old.rev, status->old.name, NULL
6096 const char *checkout_argv[] = {
6097 "git", "checkout", "--", status->old.name, NULL
6100 if (status->status == 'U') {
6101 string_format(mode, "%5o", status->old.mode);
6103 if (status->old.mode == 0 && status->new.mode == 0) {
6104 reset_argv[2] = "--force-remove";
6105 reset_argv[3] = status->old.name;
6106 reset_argv[4] = NULL;
6109 if (!io_run_fg(reset_argv, opt_cdup))
6110 return FALSE;
6111 if (status->old.mode == 0 && status->new.mode == 0)
6112 return TRUE;
6115 return io_run_fg(checkout_argv, opt_cdup);
6118 return FALSE;
6121 static enum request
6122 status_request(struct view *view, enum request request, struct line *line)
6124 struct status *status = line->data;
6126 switch (request) {
6127 case REQ_STATUS_UPDATE:
6128 if (!status_update(view))
6129 return REQ_NONE;
6130 break;
6132 case REQ_STATUS_REVERT:
6133 if (!status_revert(status, line->type, status_has_none(view, line)))
6134 return REQ_NONE;
6135 break;
6137 case REQ_STATUS_MERGE:
6138 if (!status || status->status != 'U') {
6139 report("Merging only possible for files with unmerged status ('U').");
6140 return REQ_NONE;
6142 open_mergetool(status->new.name);
6143 break;
6145 case REQ_EDIT:
6146 if (!status)
6147 return request;
6148 if (status->status == 'D') {
6149 report("File has been deleted.");
6150 return REQ_NONE;
6153 open_editor(status->new.name);
6154 break;
6156 case REQ_VIEW_BLAME:
6157 if (status)
6158 opt_ref[0] = 0;
6159 return request;
6161 case REQ_ENTER:
6162 /* After returning the status view has been split to
6163 * show the stage view. No further reloading is
6164 * necessary. */
6165 return status_enter(view, line);
6167 case REQ_REFRESH:
6168 /* Simply reload the view. */
6169 break;
6171 default:
6172 return request;
6175 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6177 return REQ_NONE;
6180 static void
6181 status_select(struct view *view, struct line *line)
6183 struct status *status = line->data;
6184 char file[SIZEOF_STR] = "all files";
6185 const char *text;
6186 const char *key;
6188 if (status && !string_format(file, "'%s'", status->new.name))
6189 return;
6191 if (!status && line[1].type == LINE_STAT_NONE)
6192 line++;
6194 switch (line->type) {
6195 case LINE_STAT_STAGED:
6196 text = "Press %s to unstage %s for commit";
6197 break;
6199 case LINE_STAT_UNSTAGED:
6200 text = "Press %s to stage %s for commit";
6201 break;
6203 case LINE_STAT_UNTRACKED:
6204 text = "Press %s to stage %s for addition";
6205 break;
6207 case LINE_STAT_HEAD:
6208 case LINE_STAT_NONE:
6209 text = "Nothing to update";
6210 break;
6212 default:
6213 die("line type %d not handled in switch", line->type);
6216 if (status && status->status == 'U') {
6217 text = "Press %s to resolve conflict in %s";
6218 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6220 } else {
6221 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6224 string_format(view->ref, text, key, file);
6225 if (status)
6226 string_copy(opt_file, status->new.name);
6229 static bool
6230 status_grep(struct view *view, struct line *line)
6232 struct status *status = line->data;
6234 if (status) {
6235 const char buf[2] = { status->status, 0 };
6236 const char *text[] = { status->new.name, buf, NULL };
6238 return grep_text(view, text);
6241 return FALSE;
6244 static struct view_ops status_ops = {
6245 "file",
6246 NULL,
6247 status_open,
6248 NULL,
6249 status_draw,
6250 status_request,
6251 status_grep,
6252 status_select,
6256 static bool
6257 stage_diff_write(struct io *io, struct line *line, struct line *end)
6259 while (line < end) {
6260 if (!io_write(io, line->data, strlen(line->data)) ||
6261 !io_write(io, "\n", 1))
6262 return FALSE;
6263 line++;
6264 if (line->type == LINE_DIFF_CHUNK ||
6265 line->type == LINE_DIFF_HEADER)
6266 break;
6269 return TRUE;
6272 static struct line *
6273 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6275 for (; view->line < line; line--)
6276 if (line->type == type)
6277 return line;
6279 return NULL;
6282 static bool
6283 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6285 const char *apply_argv[SIZEOF_ARG] = {
6286 "git", "apply", "--whitespace=nowarn", NULL
6288 struct line *diff_hdr;
6289 struct io io;
6290 int argc = 3;
6292 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6293 if (!diff_hdr)
6294 return FALSE;
6296 if (!revert)
6297 apply_argv[argc++] = "--cached";
6298 if (revert || stage_line_type == LINE_STAT_STAGED)
6299 apply_argv[argc++] = "-R";
6300 apply_argv[argc++] = "-";
6301 apply_argv[argc++] = NULL;
6302 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6303 return FALSE;
6305 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6306 !stage_diff_write(&io, chunk, view->line + view->lines))
6307 chunk = NULL;
6309 io_done(&io);
6310 io_run_bg(update_index_argv);
6312 return chunk ? TRUE : FALSE;
6315 static bool
6316 stage_update(struct view *view, struct line *line)
6318 struct line *chunk = NULL;
6320 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6321 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6323 if (chunk) {
6324 if (!stage_apply_chunk(view, chunk, FALSE)) {
6325 report("Failed to apply chunk");
6326 return FALSE;
6329 } else if (!stage_status.status) {
6330 view = VIEW(REQ_VIEW_STATUS);
6332 for (line = view->line; line < view->line + view->lines; line++)
6333 if (line->type == stage_line_type)
6334 break;
6336 if (!status_update_files(view, line + 1)) {
6337 report("Failed to update files");
6338 return FALSE;
6341 } else if (!status_update_file(&stage_status, stage_line_type)) {
6342 report("Failed to update file");
6343 return FALSE;
6346 return TRUE;
6349 static bool
6350 stage_revert(struct view *view, struct line *line)
6352 struct line *chunk = NULL;
6354 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6355 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6357 if (chunk) {
6358 if (!prompt_yesno("Are you sure you want to revert changes?"))
6359 return FALSE;
6361 if (!stage_apply_chunk(view, chunk, TRUE)) {
6362 report("Failed to revert chunk");
6363 return FALSE;
6365 return TRUE;
6367 } else {
6368 return status_revert(stage_status.status ? &stage_status : NULL,
6369 stage_line_type, FALSE);
6374 static void
6375 stage_next(struct view *view, struct line *line)
6377 int i;
6379 if (!stage_chunks) {
6380 for (line = view->line; line < view->line + view->lines; line++) {
6381 if (line->type != LINE_DIFF_CHUNK)
6382 continue;
6384 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6385 report("Allocation failure");
6386 return;
6389 stage_chunk[stage_chunks++] = line - view->line;
6393 for (i = 0; i < stage_chunks; i++) {
6394 if (stage_chunk[i] > view->lineno) {
6395 do_scroll_view(view, stage_chunk[i] - view->lineno);
6396 report("Chunk %d of %d", i + 1, stage_chunks);
6397 return;
6401 report("No next chunk found");
6404 static enum request
6405 stage_request(struct view *view, enum request request, struct line *line)
6407 switch (request) {
6408 case REQ_STATUS_UPDATE:
6409 if (!stage_update(view, line))
6410 return REQ_NONE;
6411 break;
6413 case REQ_STATUS_REVERT:
6414 if (!stage_revert(view, line))
6415 return REQ_NONE;
6416 break;
6418 case REQ_STAGE_NEXT:
6419 if (stage_line_type == LINE_STAT_UNTRACKED) {
6420 report("File is untracked; press %s to add",
6421 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6422 return REQ_NONE;
6424 stage_next(view, line);
6425 return REQ_NONE;
6427 case REQ_EDIT:
6428 if (!stage_status.new.name[0])
6429 return request;
6430 if (stage_status.status == 'D') {
6431 report("File has been deleted.");
6432 return REQ_NONE;
6435 open_editor(stage_status.new.name);
6436 break;
6438 case REQ_REFRESH:
6439 /* Reload everything ... */
6440 break;
6442 case REQ_VIEW_BLAME:
6443 if (stage_status.new.name[0]) {
6444 string_copy(opt_file, stage_status.new.name);
6445 opt_ref[0] = 0;
6447 return request;
6449 case REQ_ENTER:
6450 return pager_request(view, request, line);
6452 default:
6453 return request;
6456 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6457 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6459 /* Check whether the staged entry still exists, and close the
6460 * stage view if it doesn't. */
6461 if (!status_exists(&stage_status, stage_line_type)) {
6462 status_restore(VIEW(REQ_VIEW_STATUS));
6463 return REQ_VIEW_CLOSE;
6466 if (stage_line_type == LINE_STAT_UNTRACKED) {
6467 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6468 report("Cannot display a directory");
6469 return REQ_NONE;
6472 if (!prepare_update_file(view, stage_status.new.name)) {
6473 report("Failed to open file: %s", strerror(errno));
6474 return REQ_NONE;
6477 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6479 return REQ_NONE;
6482 static struct view_ops stage_ops = {
6483 "line",
6484 NULL,
6485 NULL,
6486 pager_read,
6487 pager_draw,
6488 stage_request,
6489 pager_grep,
6490 pager_select,
6495 * Revision graph
6498 struct commit {
6499 char id[SIZEOF_REV]; /* SHA1 ID. */
6500 char title[128]; /* First line of the commit message. */
6501 const char *author; /* Author of the commit. */
6502 struct time time; /* Date from the author ident. */
6503 struct ref_list *refs; /* Repository references. */
6504 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6505 size_t graph_size; /* The width of the graph array. */
6506 bool has_parents; /* Rewritten --parents seen. */
6509 /* Size of rev graph with no "padding" columns */
6510 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6512 struct rev_graph {
6513 struct rev_graph *prev, *next, *parents;
6514 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6515 size_t size;
6516 struct commit *commit;
6517 size_t pos;
6518 unsigned int boundary:1;
6521 /* Parents of the commit being visualized. */
6522 static struct rev_graph graph_parents[4];
6524 /* The current stack of revisions on the graph. */
6525 static struct rev_graph graph_stacks[4] = {
6526 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6527 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6528 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6529 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6532 static inline bool
6533 graph_parent_is_merge(struct rev_graph *graph)
6535 return graph->parents->size > 1;
6538 static inline void
6539 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6541 struct commit *commit = graph->commit;
6543 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6544 commit->graph[commit->graph_size++] = symbol;
6547 static void
6548 clear_rev_graph(struct rev_graph *graph)
6550 graph->boundary = 0;
6551 graph->size = graph->pos = 0;
6552 graph->commit = NULL;
6553 memset(graph->parents, 0, sizeof(*graph->parents));
6556 static void
6557 done_rev_graph(struct rev_graph *graph)
6559 if (graph_parent_is_merge(graph) &&
6560 graph->pos < graph->size - 1 &&
6561 graph->next->size == graph->size + graph->parents->size - 1) {
6562 size_t i = graph->pos + graph->parents->size - 1;
6564 graph->commit->graph_size = i * 2;
6565 while (i < graph->next->size - 1) {
6566 append_to_rev_graph(graph, ' ');
6567 append_to_rev_graph(graph, '\\');
6568 i++;
6572 clear_rev_graph(graph);
6575 static void
6576 push_rev_graph(struct rev_graph *graph, const char *parent)
6578 int i;
6580 /* "Collapse" duplicate parents lines.
6582 * FIXME: This needs to also update update the drawn graph but
6583 * for now it just serves as a method for pruning graph lines. */
6584 for (i = 0; i < graph->size; i++)
6585 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6586 return;
6588 if (graph->size < SIZEOF_REVITEMS) {
6589 string_copy_rev(graph->rev[graph->size++], parent);
6593 static chtype
6594 get_rev_graph_symbol(struct rev_graph *graph)
6596 chtype symbol;
6598 if (graph->boundary)
6599 symbol = REVGRAPH_BOUND;
6600 else if (graph->parents->size == 0)
6601 symbol = REVGRAPH_INIT;
6602 else if (graph_parent_is_merge(graph))
6603 symbol = REVGRAPH_MERGE;
6604 else if (graph->pos >= graph->size)
6605 symbol = REVGRAPH_BRANCH;
6606 else
6607 symbol = REVGRAPH_COMMIT;
6609 return symbol;
6612 static void
6613 draw_rev_graph(struct rev_graph *graph)
6615 struct rev_filler {
6616 chtype separator, line;
6618 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6619 static struct rev_filler fillers[] = {
6620 { ' ', '|' },
6621 { '`', '.' },
6622 { '\'', ' ' },
6623 { '/', ' ' },
6625 chtype symbol = get_rev_graph_symbol(graph);
6626 struct rev_filler *filler;
6627 size_t i;
6629 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6630 filler = &fillers[DEFAULT];
6632 for (i = 0; i < graph->pos; i++) {
6633 append_to_rev_graph(graph, filler->line);
6634 if (graph_parent_is_merge(graph->prev) &&
6635 graph->prev->pos == i)
6636 filler = &fillers[RSHARP];
6638 append_to_rev_graph(graph, filler->separator);
6641 /* Place the symbol for this revision. */
6642 append_to_rev_graph(graph, symbol);
6644 if (graph->prev->size > graph->size)
6645 filler = &fillers[RDIAG];
6646 else
6647 filler = &fillers[DEFAULT];
6649 i++;
6651 for (; i < graph->size; i++) {
6652 append_to_rev_graph(graph, filler->separator);
6653 append_to_rev_graph(graph, filler->line);
6654 if (graph_parent_is_merge(graph->prev) &&
6655 i < graph->prev->pos + graph->parents->size)
6656 filler = &fillers[RSHARP];
6657 if (graph->prev->size > graph->size)
6658 filler = &fillers[LDIAG];
6661 if (graph->prev->size > graph->size) {
6662 append_to_rev_graph(graph, filler->separator);
6663 if (filler->line != ' ')
6664 append_to_rev_graph(graph, filler->line);
6668 /* Prepare the next rev graph */
6669 static void
6670 prepare_rev_graph(struct rev_graph *graph)
6672 size_t i;
6674 /* First, traverse all lines of revisions up to the active one. */
6675 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6676 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6677 break;
6679 push_rev_graph(graph->next, graph->rev[graph->pos]);
6682 /* Interleave the new revision parent(s). */
6683 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6684 push_rev_graph(graph->next, graph->parents->rev[i]);
6686 /* Lastly, put any remaining revisions. */
6687 for (i = graph->pos + 1; i < graph->size; i++)
6688 push_rev_graph(graph->next, graph->rev[i]);
6691 static void
6692 update_rev_graph(struct view *view, struct rev_graph *graph)
6694 /* If this is the finalizing update ... */
6695 if (graph->commit)
6696 prepare_rev_graph(graph);
6698 /* Graph visualization needs a one rev look-ahead,
6699 * so the first update doesn't visualize anything. */
6700 if (!graph->prev->commit)
6701 return;
6703 if (view->lines > 2)
6704 view->line[view->lines - 3].dirty = 1;
6705 if (view->lines > 1)
6706 view->line[view->lines - 2].dirty = 1;
6707 draw_rev_graph(graph->prev);
6708 done_rev_graph(graph->prev->prev);
6713 * Main view backend
6716 static const char *main_argv[SIZEOF_ARG] = {
6717 "git", "log", "--no-color", "--pretty=raw", "--parents",
6718 "--topo-order", "%(diffargs)", "%(revargs)",
6719 "--", "%(fileargs)", NULL
6722 static bool
6723 main_draw(struct view *view, struct line *line, unsigned int lineno)
6725 struct commit *commit = line->data;
6727 if (!commit->author)
6728 return FALSE;
6730 if (opt_date && draw_date(view, &commit->time))
6731 return TRUE;
6733 if (opt_author && draw_author(view, commit->author))
6734 return TRUE;
6736 if (opt_rev_graph && commit->graph_size &&
6737 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6738 return TRUE;
6740 if (opt_show_refs && commit->refs) {
6741 size_t i;
6743 for (i = 0; i < commit->refs->size; i++) {
6744 struct ref *ref = commit->refs->refs[i];
6745 enum line_type type;
6747 if (ref->head)
6748 type = LINE_MAIN_HEAD;
6749 else if (ref->ltag)
6750 type = LINE_MAIN_LOCAL_TAG;
6751 else if (ref->tag)
6752 type = LINE_MAIN_TAG;
6753 else if (ref->tracked)
6754 type = LINE_MAIN_TRACKED;
6755 else if (ref->remote)
6756 type = LINE_MAIN_REMOTE;
6757 else
6758 type = LINE_MAIN_REF;
6760 if (draw_text(view, type, "[", TRUE) ||
6761 draw_text(view, type, ref->name, TRUE) ||
6762 draw_text(view, type, "]", TRUE))
6763 return TRUE;
6765 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6766 return TRUE;
6770 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6771 return TRUE;
6774 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6775 static bool
6776 main_read(struct view *view, char *line)
6778 static struct rev_graph *graph = graph_stacks;
6779 enum line_type type;
6780 struct commit *commit;
6782 if (!line) {
6783 int i;
6785 if (!view->lines && !view->prev)
6786 die("No revisions match the given arguments.");
6787 if (view->lines > 0) {
6788 commit = view->line[view->lines - 1].data;
6789 view->line[view->lines - 1].dirty = 1;
6790 if (!commit->author) {
6791 view->lines--;
6792 free(commit);
6793 graph->commit = NULL;
6796 update_rev_graph(view, graph);
6798 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6799 clear_rev_graph(&graph_stacks[i]);
6800 return TRUE;
6803 type = get_line_type(line);
6804 if (type == LINE_COMMIT) {
6805 commit = calloc(1, sizeof(struct commit));
6806 if (!commit)
6807 return FALSE;
6809 line += STRING_SIZE("commit ");
6810 if (*line == '-') {
6811 graph->boundary = 1;
6812 line++;
6815 string_copy_rev(commit->id, line);
6816 commit->refs = get_ref_list(commit->id);
6817 graph->commit = commit;
6818 add_line_data(view, commit, LINE_MAIN_COMMIT);
6820 while ((line = strchr(line, ' '))) {
6821 line++;
6822 push_rev_graph(graph->parents, line);
6823 commit->has_parents = TRUE;
6825 return TRUE;
6828 if (!view->lines)
6829 return TRUE;
6830 commit = view->line[view->lines - 1].data;
6832 switch (type) {
6833 case LINE_PARENT:
6834 if (commit->has_parents)
6835 break;
6836 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6837 break;
6839 case LINE_AUTHOR:
6840 parse_author_line(line + STRING_SIZE("author "),
6841 &commit->author, &commit->time);
6842 update_rev_graph(view, graph);
6843 graph = graph->next;
6844 break;
6846 default:
6847 /* Fill in the commit title if it has not already been set. */
6848 if (commit->title[0])
6849 break;
6851 /* Require titles to start with a non-space character at the
6852 * offset used by git log. */
6853 if (strncmp(line, " ", 4))
6854 break;
6855 line += 4;
6856 /* Well, if the title starts with a whitespace character,
6857 * try to be forgiving. Otherwise we end up with no title. */
6858 while (isspace(*line))
6859 line++;
6860 if (*line == '\0')
6861 break;
6862 /* FIXME: More graceful handling of titles; append "..." to
6863 * shortened titles, etc. */
6865 string_expand(commit->title, sizeof(commit->title), line, 1);
6866 view->line[view->lines - 1].dirty = 1;
6869 return TRUE;
6872 static enum request
6873 main_request(struct view *view, enum request request, struct line *line)
6875 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6877 switch (request) {
6878 case REQ_ENTER:
6879 open_view(view, REQ_VIEW_DIFF, flags);
6880 break;
6881 case REQ_REFRESH:
6882 load_refs();
6883 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6884 break;
6885 default:
6886 return request;
6889 return REQ_NONE;
6892 static bool
6893 grep_refs(struct ref_list *list, regex_t *regex)
6895 regmatch_t pmatch;
6896 size_t i;
6898 if (!opt_show_refs || !list)
6899 return FALSE;
6901 for (i = 0; i < list->size; i++) {
6902 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6903 return TRUE;
6906 return FALSE;
6909 static bool
6910 main_grep(struct view *view, struct line *line)
6912 struct commit *commit = line->data;
6913 const char *text[] = {
6914 commit->title,
6915 opt_author ? commit->author : "",
6916 mkdate(&commit->time, opt_date),
6917 NULL
6920 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6923 static void
6924 main_select(struct view *view, struct line *line)
6926 struct commit *commit = line->data;
6928 string_copy_rev(view->ref, commit->id);
6929 string_copy_rev(ref_commit, view->ref);
6932 static struct view_ops main_ops = {
6933 "commit",
6934 main_argv,
6935 NULL,
6936 main_read,
6937 main_draw,
6938 main_request,
6939 main_grep,
6940 main_select,
6945 * Status management
6948 /* Whether or not the curses interface has been initialized. */
6949 static bool cursed = FALSE;
6951 /* Terminal hacks and workarounds. */
6952 static bool use_scroll_redrawwin;
6953 static bool use_scroll_status_wclear;
6955 /* The status window is used for polling keystrokes. */
6956 static WINDOW *status_win;
6958 /* Reading from the prompt? */
6959 static bool input_mode = FALSE;
6961 static bool status_empty = FALSE;
6963 /* Update status and title window. */
6964 static void
6965 report(const char *msg, ...)
6967 struct view *view = display[current_view];
6969 if (input_mode)
6970 return;
6972 if (!view) {
6973 char buf[SIZEOF_STR];
6974 va_list args;
6976 va_start(args, msg);
6977 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6978 buf[sizeof(buf) - 1] = 0;
6979 buf[sizeof(buf) - 2] = '.';
6980 buf[sizeof(buf) - 3] = '.';
6981 buf[sizeof(buf) - 4] = '.';
6983 va_end(args);
6984 die("%s", buf);
6987 if (!status_empty || *msg) {
6988 va_list args;
6990 va_start(args, msg);
6992 wmove(status_win, 0, 0);
6993 if (view->has_scrolled && use_scroll_status_wclear)
6994 wclear(status_win);
6995 if (*msg) {
6996 vwprintw(status_win, msg, args);
6997 status_empty = FALSE;
6998 } else {
6999 status_empty = TRUE;
7001 wclrtoeol(status_win);
7002 wnoutrefresh(status_win);
7004 va_end(args);
7007 update_view_title(view);
7010 static void
7011 init_display(void)
7013 const char *term;
7014 int x, y;
7016 /* Initialize the curses library */
7017 if (isatty(STDIN_FILENO)) {
7018 cursed = !!initscr();
7019 opt_tty = stdin;
7020 } else {
7021 /* Leave stdin and stdout alone when acting as a pager. */
7022 opt_tty = fopen("/dev/tty", "r+");
7023 if (!opt_tty)
7024 die("Failed to open /dev/tty");
7025 cursed = !!newterm(NULL, opt_tty, opt_tty);
7028 if (!cursed)
7029 die("Failed to initialize curses");
7031 nonl(); /* Disable conversion and detect newlines from input. */
7032 cbreak(); /* Take input chars one at a time, no wait for \n */
7033 noecho(); /* Don't echo input */
7034 leaveok(stdscr, FALSE);
7036 if (has_colors())
7037 init_colors();
7039 getmaxyx(stdscr, y, x);
7040 status_win = newwin(1, 0, y - 1, 0);
7041 if (!status_win)
7042 die("Failed to create status window");
7044 /* Enable keyboard mapping */
7045 keypad(status_win, TRUE);
7046 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7048 TABSIZE = opt_tab_size;
7050 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7051 if (term && !strcmp(term, "gnome-terminal")) {
7052 /* In the gnome-terminal-emulator, the message from
7053 * scrolling up one line when impossible followed by
7054 * scrolling down one line causes corruption of the
7055 * status line. This is fixed by calling wclear. */
7056 use_scroll_status_wclear = TRUE;
7057 use_scroll_redrawwin = FALSE;
7059 } else if (term && !strcmp(term, "xrvt-xpm")) {
7060 /* No problems with full optimizations in xrvt-(unicode)
7061 * and aterm. */
7062 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7064 } else {
7065 /* When scrolling in (u)xterm the last line in the
7066 * scrolling direction will update slowly. */
7067 use_scroll_redrawwin = TRUE;
7068 use_scroll_status_wclear = FALSE;
7072 static int
7073 get_input(int prompt_position)
7075 struct view *view;
7076 int i, key, cursor_y, cursor_x;
7078 if (prompt_position)
7079 input_mode = TRUE;
7081 while (TRUE) {
7082 bool loading = FALSE;
7084 foreach_view (view, i) {
7085 update_view(view);
7086 if (view_is_displayed(view) && view->has_scrolled &&
7087 use_scroll_redrawwin)
7088 redrawwin(view->win);
7089 view->has_scrolled = FALSE;
7090 if (view->pipe)
7091 loading = TRUE;
7094 /* Update the cursor position. */
7095 if (prompt_position) {
7096 getbegyx(status_win, cursor_y, cursor_x);
7097 cursor_x = prompt_position;
7098 } else {
7099 view = display[current_view];
7100 getbegyx(view->win, cursor_y, cursor_x);
7101 cursor_x = view->width - 1;
7102 cursor_y += view->lineno - view->offset;
7104 setsyx(cursor_y, cursor_x);
7106 /* Refresh, accept single keystroke of input */
7107 doupdate();
7108 nodelay(status_win, loading);
7109 key = wgetch(status_win);
7111 /* wgetch() with nodelay() enabled returns ERR when
7112 * there's no input. */
7113 if (key == ERR) {
7115 } else if (key == KEY_RESIZE) {
7116 int height, width;
7118 getmaxyx(stdscr, height, width);
7120 wresize(status_win, 1, width);
7121 mvwin(status_win, height - 1, 0);
7122 wnoutrefresh(status_win);
7123 resize_display();
7124 redraw_display(TRUE);
7126 } else {
7127 input_mode = FALSE;
7128 return key;
7133 static char *
7134 prompt_input(const char *prompt, input_handler handler, void *data)
7136 enum input_status status = INPUT_OK;
7137 static char buf[SIZEOF_STR];
7138 size_t pos = 0;
7140 buf[pos] = 0;
7142 while (status == INPUT_OK || status == INPUT_SKIP) {
7143 int key;
7145 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7146 wclrtoeol(status_win);
7148 key = get_input(pos + 1);
7149 switch (key) {
7150 case KEY_RETURN:
7151 case KEY_ENTER:
7152 case '\n':
7153 status = pos ? INPUT_STOP : INPUT_CANCEL;
7154 break;
7156 case KEY_BACKSPACE:
7157 if (pos > 0)
7158 buf[--pos] = 0;
7159 else
7160 status = INPUT_CANCEL;
7161 break;
7163 case KEY_ESC:
7164 status = INPUT_CANCEL;
7165 break;
7167 default:
7168 if (pos >= sizeof(buf)) {
7169 report("Input string too long");
7170 return NULL;
7173 status = handler(data, buf, key);
7174 if (status == INPUT_OK)
7175 buf[pos++] = (char) key;
7179 /* Clear the status window */
7180 status_empty = FALSE;
7181 report("");
7183 if (status == INPUT_CANCEL)
7184 return NULL;
7186 buf[pos++] = 0;
7188 return buf;
7191 static enum input_status
7192 prompt_yesno_handler(void *data, char *buf, int c)
7194 if (c == 'y' || c == 'Y')
7195 return INPUT_STOP;
7196 if (c == 'n' || c == 'N')
7197 return INPUT_CANCEL;
7198 return INPUT_SKIP;
7201 static bool
7202 prompt_yesno(const char *prompt)
7204 char prompt2[SIZEOF_STR];
7206 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7207 return FALSE;
7209 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7212 static enum input_status
7213 read_prompt_handler(void *data, char *buf, int c)
7215 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7218 static char *
7219 read_prompt(const char *prompt)
7221 return prompt_input(prompt, read_prompt_handler, NULL);
7224 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7226 enum input_status status = INPUT_OK;
7227 int size = 0;
7229 while (items[size].text)
7230 size++;
7232 while (status == INPUT_OK) {
7233 const struct menu_item *item = &items[*selected];
7234 int key;
7235 int i;
7237 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7238 prompt, *selected + 1, size);
7239 if (item->hotkey)
7240 wprintw(status_win, "[%c] ", (char) item->hotkey);
7241 wprintw(status_win, "%s", item->text);
7242 wclrtoeol(status_win);
7244 key = get_input(COLS - 1);
7245 switch (key) {
7246 case KEY_RETURN:
7247 case KEY_ENTER:
7248 case '\n':
7249 status = INPUT_STOP;
7250 break;
7252 case KEY_LEFT:
7253 case KEY_UP:
7254 *selected = *selected - 1;
7255 if (*selected < 0)
7256 *selected = size - 1;
7257 break;
7259 case KEY_RIGHT:
7260 case KEY_DOWN:
7261 *selected = (*selected + 1) % size;
7262 break;
7264 case KEY_ESC:
7265 status = INPUT_CANCEL;
7266 break;
7268 default:
7269 for (i = 0; items[i].text; i++)
7270 if (items[i].hotkey == key) {
7271 *selected = i;
7272 status = INPUT_STOP;
7273 break;
7278 /* Clear the status window */
7279 status_empty = FALSE;
7280 report("");
7282 return status != INPUT_CANCEL;
7286 * Repository properties
7289 static struct ref **refs = NULL;
7290 static size_t refs_size = 0;
7291 static struct ref *refs_head = NULL;
7293 static struct ref_list **ref_lists = NULL;
7294 static size_t ref_lists_size = 0;
7296 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7297 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7298 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7300 static int
7301 compare_refs(const void *ref1_, const void *ref2_)
7303 const struct ref *ref1 = *(const struct ref **)ref1_;
7304 const struct ref *ref2 = *(const struct ref **)ref2_;
7306 if (ref1->tag != ref2->tag)
7307 return ref2->tag - ref1->tag;
7308 if (ref1->ltag != ref2->ltag)
7309 return ref2->ltag - ref2->ltag;
7310 if (ref1->head != ref2->head)
7311 return ref2->head - ref1->head;
7312 if (ref1->tracked != ref2->tracked)
7313 return ref2->tracked - ref1->tracked;
7314 if (ref1->remote != ref2->remote)
7315 return ref2->remote - ref1->remote;
7316 return strcmp(ref1->name, ref2->name);
7319 static void
7320 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7322 size_t i;
7324 for (i = 0; i < refs_size; i++)
7325 if (!visitor(data, refs[i]))
7326 break;
7329 static struct ref *
7330 get_ref_head()
7332 return refs_head;
7335 static struct ref_list *
7336 get_ref_list(const char *id)
7338 struct ref_list *list;
7339 size_t i;
7341 for (i = 0; i < ref_lists_size; i++)
7342 if (!strcmp(id, ref_lists[i]->id))
7343 return ref_lists[i];
7345 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7346 return NULL;
7347 list = calloc(1, sizeof(*list));
7348 if (!list)
7349 return NULL;
7351 for (i = 0; i < refs_size; i++) {
7352 if (!strcmp(id, refs[i]->id) &&
7353 realloc_refs_list(&list->refs, list->size, 1))
7354 list->refs[list->size++] = refs[i];
7357 if (!list->refs) {
7358 free(list);
7359 return NULL;
7362 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7363 ref_lists[ref_lists_size++] = list;
7364 return list;
7367 static int
7368 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7370 struct ref *ref = NULL;
7371 bool tag = FALSE;
7372 bool ltag = FALSE;
7373 bool remote = FALSE;
7374 bool tracked = FALSE;
7375 bool head = FALSE;
7376 int from = 0, to = refs_size - 1;
7378 if (!prefixcmp(name, "refs/tags/")) {
7379 if (!suffixcmp(name, namelen, "^{}")) {
7380 namelen -= 3;
7381 name[namelen] = 0;
7382 } else {
7383 ltag = TRUE;
7386 tag = TRUE;
7387 namelen -= STRING_SIZE("refs/tags/");
7388 name += STRING_SIZE("refs/tags/");
7390 } else if (!prefixcmp(name, "refs/remotes/")) {
7391 remote = TRUE;
7392 namelen -= STRING_SIZE("refs/remotes/");
7393 name += STRING_SIZE("refs/remotes/");
7394 tracked = !strcmp(opt_remote, name);
7396 } else if (!prefixcmp(name, "refs/heads/")) {
7397 namelen -= STRING_SIZE("refs/heads/");
7398 name += STRING_SIZE("refs/heads/");
7399 if (!strncmp(opt_head, name, namelen))
7400 return OK;
7402 } else if (!strcmp(name, "HEAD")) {
7403 head = TRUE;
7404 if (*opt_head) {
7405 namelen = strlen(opt_head);
7406 name = opt_head;
7410 /* If we are reloading or it's an annotated tag, replace the
7411 * previous SHA1 with the resolved commit id; relies on the fact
7412 * git-ls-remote lists the commit id of an annotated tag right
7413 * before the commit id it points to. */
7414 while (from <= to) {
7415 size_t pos = (to + from) / 2;
7416 int cmp = strcmp(name, refs[pos]->name);
7418 if (!cmp) {
7419 ref = refs[pos];
7420 break;
7423 if (cmp < 0)
7424 to = pos - 1;
7425 else
7426 from = pos + 1;
7429 if (!ref) {
7430 if (!realloc_refs(&refs, refs_size, 1))
7431 return ERR;
7432 ref = calloc(1, sizeof(*ref) + namelen);
7433 if (!ref)
7434 return ERR;
7435 memmove(refs + from + 1, refs + from,
7436 (refs_size - from) * sizeof(*refs));
7437 refs[from] = ref;
7438 strncpy(ref->name, name, namelen);
7439 refs_size++;
7442 ref->head = head;
7443 ref->tag = tag;
7444 ref->ltag = ltag;
7445 ref->remote = remote;
7446 ref->tracked = tracked;
7447 string_copy_rev(ref->id, id);
7449 if (head)
7450 refs_head = ref;
7451 return OK;
7454 static int
7455 load_refs(void)
7457 const char *head_argv[] = {
7458 "git", "symbolic-ref", "HEAD", NULL
7460 static const char *ls_remote_argv[SIZEOF_ARG] = {
7461 "git", "ls-remote", opt_git_dir, NULL
7463 static bool init = FALSE;
7464 size_t i;
7466 if (!init) {
7467 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7468 die("TIG_LS_REMOTE contains too many arguments");
7469 init = TRUE;
7472 if (!*opt_git_dir)
7473 return OK;
7475 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7476 !prefixcmp(opt_head, "refs/heads/")) {
7477 char *offset = opt_head + STRING_SIZE("refs/heads/");
7479 memmove(opt_head, offset, strlen(offset) + 1);
7482 refs_head = NULL;
7483 for (i = 0; i < refs_size; i++)
7484 refs[i]->id[0] = 0;
7486 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7487 return ERR;
7489 /* Update the ref lists to reflect changes. */
7490 for (i = 0; i < ref_lists_size; i++) {
7491 struct ref_list *list = ref_lists[i];
7492 size_t old, new;
7494 for (old = new = 0; old < list->size; old++)
7495 if (!strcmp(list->id, list->refs[old]->id))
7496 list->refs[new++] = list->refs[old];
7497 list->size = new;
7500 return OK;
7503 static void
7504 set_remote_branch(const char *name, const char *value, size_t valuelen)
7506 if (!strcmp(name, ".remote")) {
7507 string_ncopy(opt_remote, value, valuelen);
7509 } else if (*opt_remote && !strcmp(name, ".merge")) {
7510 size_t from = strlen(opt_remote);
7512 if (!prefixcmp(value, "refs/heads/"))
7513 value += STRING_SIZE("refs/heads/");
7515 if (!string_format_from(opt_remote, &from, "/%s", value))
7516 opt_remote[0] = 0;
7520 static void
7521 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7523 const char *argv[SIZEOF_ARG] = { name, "=" };
7524 int argc = 1 + (cmd == option_set_command);
7525 int error = ERR;
7527 if (!argv_from_string(argv, &argc, value))
7528 config_msg = "Too many option arguments";
7529 else
7530 error = cmd(argc, argv);
7532 if (error == ERR)
7533 warn("Option 'tig.%s': %s", name, config_msg);
7536 static bool
7537 set_environment_variable(const char *name, const char *value)
7539 size_t len = strlen(name) + 1 + strlen(value) + 1;
7540 char *env = malloc(len);
7542 if (env &&
7543 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7544 putenv(env) == 0)
7545 return TRUE;
7546 free(env);
7547 return FALSE;
7550 static void
7551 set_work_tree(const char *value)
7553 char cwd[SIZEOF_STR];
7555 if (!getcwd(cwd, sizeof(cwd)))
7556 die("Failed to get cwd path: %s", strerror(errno));
7557 if (chdir(opt_git_dir) < 0)
7558 die("Failed to chdir(%s): %s", strerror(errno));
7559 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7560 die("Failed to get git path: %s", strerror(errno));
7561 if (chdir(cwd) < 0)
7562 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7563 if (chdir(value) < 0)
7564 die("Failed to chdir(%s): %s", value, strerror(errno));
7565 if (!getcwd(cwd, sizeof(cwd)))
7566 die("Failed to get cwd path: %s", strerror(errno));
7567 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7568 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7569 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7570 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7571 opt_is_inside_work_tree = TRUE;
7574 static int
7575 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7577 if (!strcmp(name, "i18n.commitencoding"))
7578 string_ncopy(opt_encoding, value, valuelen);
7580 else if (!strcmp(name, "core.editor"))
7581 string_ncopy(opt_editor, value, valuelen);
7583 else if (!strcmp(name, "core.worktree"))
7584 set_work_tree(value);
7586 else if (!prefixcmp(name, "tig.color."))
7587 set_repo_config_option(name + 10, value, option_color_command);
7589 else if (!prefixcmp(name, "tig.bind."))
7590 set_repo_config_option(name + 9, value, option_bind_command);
7592 else if (!prefixcmp(name, "tig."))
7593 set_repo_config_option(name + 4, value, option_set_command);
7595 else if (*opt_head && !prefixcmp(name, "branch.") &&
7596 !strncmp(name + 7, opt_head, strlen(opt_head)))
7597 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7599 return OK;
7602 static int
7603 load_git_config(void)
7605 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7607 return io_run_load(config_list_argv, "=", read_repo_config_option);
7610 static int
7611 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7613 if (!opt_git_dir[0]) {
7614 string_ncopy(opt_git_dir, name, namelen);
7616 } else if (opt_is_inside_work_tree == -1) {
7617 /* This can be 3 different values depending on the
7618 * version of git being used. If git-rev-parse does not
7619 * understand --is-inside-work-tree it will simply echo
7620 * the option else either "true" or "false" is printed.
7621 * Default to true for the unknown case. */
7622 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7624 } else if (*name == '.') {
7625 string_ncopy(opt_cdup, name, namelen);
7627 } else {
7628 string_ncopy(opt_prefix, name, namelen);
7631 return OK;
7634 static int
7635 load_repo_info(void)
7637 const char *rev_parse_argv[] = {
7638 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7639 "--show-cdup", "--show-prefix", NULL
7642 return io_run_load(rev_parse_argv, "=", read_repo_info);
7647 * Main
7650 static const char usage[] =
7651 "tig " TIG_VERSION " (" __DATE__ ")\n"
7652 "\n"
7653 "Usage: tig [options] [revs] [--] [paths]\n"
7654 " or: tig show [options] [revs] [--] [paths]\n"
7655 " or: tig blame [rev] path\n"
7656 " or: tig status\n"
7657 " or: tig < [git command output]\n"
7658 "\n"
7659 "Options:\n"
7660 " -v, --version Show version and exit\n"
7661 " -h, --help Show help message and exit";
7663 static void __NORETURN
7664 quit(int sig)
7666 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7667 if (cursed)
7668 endwin();
7669 exit(0);
7672 static void __NORETURN
7673 die(const char *err, ...)
7675 va_list args;
7677 endwin();
7679 va_start(args, err);
7680 fputs("tig: ", stderr);
7681 vfprintf(stderr, err, args);
7682 fputs("\n", stderr);
7683 va_end(args);
7685 exit(1);
7688 static void
7689 warn(const char *msg, ...)
7691 va_list args;
7693 va_start(args, msg);
7694 fputs("tig warning: ", stderr);
7695 vfprintf(stderr, msg, args);
7696 fputs("\n", stderr);
7697 va_end(args);
7700 static const char ***filter_args;
7702 static int
7703 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7705 return argv_append(filter_args, name) ? OK : ERR;
7708 static void
7709 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7711 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7712 const char **all_argv = NULL;
7714 filter_args = args;
7715 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7716 !argv_append_array(&all_argv, argv) ||
7717 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7718 die("Failed to split arguments");
7719 argv_free(all_argv);
7720 free(all_argv);
7723 static void
7724 filter_options(const char *argv[])
7726 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7727 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7728 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7731 static enum request
7732 parse_options(int argc, const char *argv[])
7734 enum request request = REQ_VIEW_MAIN;
7735 const char *subcommand;
7736 bool seen_dashdash = FALSE;
7737 const char **filter_argv = NULL;
7738 int i;
7740 if (!isatty(STDIN_FILENO)) {
7741 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7742 return REQ_VIEW_PAGER;
7745 if (argc <= 1)
7746 return REQ_VIEW_MAIN;
7748 subcommand = argv[1];
7749 if (!strcmp(subcommand, "status")) {
7750 if (argc > 2)
7751 warn("ignoring arguments after `%s'", subcommand);
7752 return REQ_VIEW_STATUS;
7754 } else if (!strcmp(subcommand, "blame")) {
7755 if (argc <= 2 || argc > 4)
7756 die("invalid number of options to blame\n\n%s", usage);
7758 i = 2;
7759 if (argc == 4) {
7760 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7761 i++;
7764 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7765 return REQ_VIEW_BLAME;
7767 } else if (!strcmp(subcommand, "show")) {
7768 request = REQ_VIEW_DIFF;
7770 } else {
7771 subcommand = NULL;
7774 for (i = 1 + !!subcommand; i < argc; i++) {
7775 const char *opt = argv[i];
7777 if (seen_dashdash) {
7778 argv_append(&opt_file_args, opt);
7779 continue;
7781 } else if (!strcmp(opt, "--")) {
7782 seen_dashdash = TRUE;
7783 continue;
7785 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7786 printf("tig version %s\n", TIG_VERSION);
7787 quit(0);
7789 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7790 printf("%s\n", usage);
7791 quit(0);
7793 } else if (!strcmp(opt, "--all")) {
7794 argv_append(&opt_rev_args, opt);
7795 continue;
7798 if (!argv_append(&filter_argv, opt))
7799 die("command too long");
7802 if (filter_argv)
7803 filter_options(filter_argv);
7805 return request;
7809 main(int argc, const char *argv[])
7811 const char *codeset = "UTF-8";
7812 enum request request = parse_options(argc, argv);
7813 struct view *view;
7814 size_t i;
7816 signal(SIGINT, quit);
7817 signal(SIGPIPE, SIG_IGN);
7819 if (setlocale(LC_ALL, "")) {
7820 codeset = nl_langinfo(CODESET);
7823 if (load_repo_info() == ERR)
7824 die("Failed to load repo info.");
7826 if (load_options() == ERR)
7827 die("Failed to load user config.");
7829 if (load_git_config() == ERR)
7830 die("Failed to load repo config.");
7832 /* Require a git repository unless when running in pager mode. */
7833 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7834 die("Not a git repository");
7836 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7837 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7838 if (opt_iconv_in == ICONV_NONE)
7839 die("Failed to initialize character set conversion");
7842 if (codeset && strcmp(codeset, "UTF-8")) {
7843 opt_iconv_out = iconv_open(codeset, "UTF-8");
7844 if (opt_iconv_out == ICONV_NONE)
7845 die("Failed to initialize character set conversion");
7848 if (load_refs() == ERR)
7849 die("Failed to load refs.");
7851 foreach_view (view, i) {
7852 if (getenv(view->cmd_env))
7853 warn("Use of the %s environment variable is deprecated,"
7854 " use options or TIG_DIFF_ARGS instead",
7855 view->cmd_env);
7856 if (!argv_from_env(view->ops->argv, view->cmd_env))
7857 die("Too many arguments in the `%s` environment variable",
7858 view->cmd_env);
7861 init_display();
7863 while (view_driver(display[current_view], request)) {
7864 int key = get_input(0);
7866 view = display[current_view];
7867 request = get_keybinding(view->keymap, key);
7869 /* Some low-level request handling. This keeps access to
7870 * status_win restricted. */
7871 switch (request) {
7872 case REQ_NONE:
7873 report("Unknown key, press %s for help",
7874 get_key(view->keymap, REQ_VIEW_HELP));
7875 break;
7876 case REQ_PROMPT:
7878 char *cmd = read_prompt(":");
7880 if (cmd && isdigit(*cmd)) {
7881 int lineno = view->lineno + 1;
7883 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7884 select_view_line(view, lineno - 1);
7885 report("");
7886 } else {
7887 report("Unable to parse '%s' as a line number", cmd);
7890 } else if (cmd) {
7891 struct view *next = VIEW(REQ_VIEW_PAGER);
7892 const char *argv[SIZEOF_ARG] = { "git" };
7893 int argc = 1;
7895 /* When running random commands, initially show the
7896 * command in the title. However, it maybe later be
7897 * overwritten if a commit line is selected. */
7898 string_ncopy(next->ref, cmd, strlen(cmd));
7900 if (!argv_from_string(argv, &argc, cmd)) {
7901 report("Too many arguments");
7902 } else if (!prepare_update(next, argv, NULL)) {
7903 report("Failed to format command");
7904 } else {
7905 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7909 request = REQ_NONE;
7910 break;
7912 case REQ_SEARCH:
7913 case REQ_SEARCH_BACK:
7915 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7916 char *search = read_prompt(prompt);
7918 if (search)
7919 string_ncopy(opt_search, search, strlen(search));
7920 else if (*opt_search)
7921 request = request == REQ_SEARCH ?
7922 REQ_FIND_NEXT :
7923 REQ_FIND_PREV;
7924 else
7925 request = REQ_NONE;
7926 break;
7928 default:
7929 break;
7933 quit(0);
7935 return 0;