Load diff arguments from TIG_DIFF_OPTS if defined and no diff args was passed
[tig.git] / tig.c
blobd7752a2f9131b75d4f7b9a58edad7e1bffdfff12
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 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
689 static bool
690 argv_append(const char ***argv, const char *arg)
692 int argc = 0;
694 while (*argv && (*argv)[argc])
695 argc++;
697 if (!argv_realloc(argv, argc, 2))
698 return FALSE;
700 (*argv)[argc++] = strdup(arg);
701 (*argv)[argc] = NULL;
702 return TRUE;
705 static bool
706 argv_append_array(const char ***dst_argv, const char *src_argv[])
708 int i;
710 for (i = 0; src_argv && src_argv[i]; i++)
711 if (!argv_append(dst_argv, src_argv[i]))
712 return FALSE;
713 return TRUE;
716 static bool
717 argv_copy(const char ***dst, const char *src[])
719 int argc;
721 for (argc = 0; src[argc]; argc++)
722 if (!argv_append(dst, src[argc]))
723 return FALSE;
724 return TRUE;
729 * Executing external commands.
732 enum io_type {
733 IO_FD, /* File descriptor based IO. */
734 IO_BG, /* Execute command in the background. */
735 IO_FG, /* Execute command with same std{in,out,err}. */
736 IO_RD, /* Read only fork+exec IO. */
737 IO_WR, /* Write only fork+exec IO. */
738 IO_AP, /* Append fork+exec output to file. */
741 struct io {
742 int pipe; /* Pipe end for reading or writing. */
743 pid_t pid; /* PID of spawned process. */
744 int error; /* Error status. */
745 char *buf; /* Read buffer. */
746 size_t bufalloc; /* Allocated buffer size. */
747 size_t bufsize; /* Buffer content size. */
748 char *bufpos; /* Current buffer position. */
749 unsigned int eof:1; /* Has end of file been reached. */
752 static void
753 io_init(struct io *io)
755 memset(io, 0, sizeof(*io));
756 io->pipe = -1;
759 static bool
760 io_open(struct io *io, const char *fmt, ...)
762 char name[SIZEOF_STR] = "";
763 bool fits;
764 va_list args;
766 io_init(io);
768 va_start(args, fmt);
769 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
770 va_end(args);
772 if (!fits) {
773 io->error = ENAMETOOLONG;
774 return FALSE;
776 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
777 if (io->pipe == -1)
778 io->error = errno;
779 return io->pipe != -1;
782 static bool
783 io_kill(struct io *io)
785 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
788 static bool
789 io_done(struct io *io)
791 pid_t pid = io->pid;
793 if (io->pipe != -1)
794 close(io->pipe);
795 free(io->buf);
796 io_init(io);
798 while (pid > 0) {
799 int status;
800 pid_t waiting = waitpid(pid, &status, 0);
802 if (waiting < 0) {
803 if (errno == EINTR)
804 continue;
805 io->error = errno;
806 return FALSE;
809 return waiting == pid &&
810 !WIFSIGNALED(status) &&
811 WIFEXITED(status) &&
812 !WEXITSTATUS(status);
815 return TRUE;
818 static bool
819 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
821 int pipefds[2] = { -1, -1 };
822 va_list args;
824 io_init(io);
826 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
827 io->error = errno;
828 return FALSE;
829 } else if (type == IO_AP) {
830 va_start(args, argv);
831 pipefds[1] = va_arg(args, int);
832 va_end(args);
835 if ((io->pid = fork())) {
836 if (io->pid == -1)
837 io->error = errno;
838 if (pipefds[!(type == IO_WR)] != -1)
839 close(pipefds[!(type == IO_WR)]);
840 if (io->pid != -1) {
841 io->pipe = pipefds[!!(type == IO_WR)];
842 return TRUE;
845 } else {
846 if (type != IO_FG) {
847 int devnull = open("/dev/null", O_RDWR);
848 int readfd = type == IO_WR ? pipefds[0] : devnull;
849 int writefd = (type == IO_RD || type == IO_AP)
850 ? pipefds[1] : devnull;
852 dup2(readfd, STDIN_FILENO);
853 dup2(writefd, STDOUT_FILENO);
854 dup2(devnull, STDERR_FILENO);
856 close(devnull);
857 if (pipefds[0] != -1)
858 close(pipefds[0]);
859 if (pipefds[1] != -1)
860 close(pipefds[1]);
863 if (dir && *dir && chdir(dir) == -1)
864 exit(errno);
866 execvp(argv[0], (char *const*) argv);
867 exit(errno);
870 if (pipefds[!!(type == IO_WR)] != -1)
871 close(pipefds[!!(type == IO_WR)]);
872 return FALSE;
875 static bool
876 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
878 struct io io;
880 return io_run(&io, type, dir, argv, fd) && io_done(&io);
883 static bool
884 io_run_bg(const char **argv)
886 return io_complete(IO_BG, argv, NULL, -1);
889 static bool
890 io_run_fg(const char **argv, const char *dir)
892 return io_complete(IO_FG, argv, dir, -1);
895 static bool
896 io_run_append(const char **argv, int fd)
898 return io_complete(IO_AP, argv, NULL, fd);
901 static bool
902 io_eof(struct io *io)
904 return io->eof;
907 static int
908 io_error(struct io *io)
910 return io->error;
913 static char *
914 io_strerror(struct io *io)
916 return strerror(io->error);
919 static bool
920 io_can_read(struct io *io)
922 struct timeval tv = { 0, 500 };
923 fd_set fds;
925 FD_ZERO(&fds);
926 FD_SET(io->pipe, &fds);
928 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
931 static ssize_t
932 io_read(struct io *io, void *buf, size_t bufsize)
934 do {
935 ssize_t readsize = read(io->pipe, buf, bufsize);
937 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
938 continue;
939 else if (readsize == -1)
940 io->error = errno;
941 else if (readsize == 0)
942 io->eof = 1;
943 return readsize;
944 } while (1);
947 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
949 static char *
950 io_get(struct io *io, int c, bool can_read)
952 char *eol;
953 ssize_t readsize;
955 while (TRUE) {
956 if (io->bufsize > 0) {
957 eol = memchr(io->bufpos, c, io->bufsize);
958 if (eol) {
959 char *line = io->bufpos;
961 *eol = 0;
962 io->bufpos = eol + 1;
963 io->bufsize -= io->bufpos - line;
964 return line;
968 if (io_eof(io)) {
969 if (io->bufsize) {
970 io->bufpos[io->bufsize] = 0;
971 io->bufsize = 0;
972 return io->bufpos;
974 return NULL;
977 if (!can_read)
978 return NULL;
980 if (io->bufsize > 0 && io->bufpos > io->buf)
981 memmove(io->buf, io->bufpos, io->bufsize);
983 if (io->bufalloc == io->bufsize) {
984 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
985 return NULL;
986 io->bufalloc += BUFSIZ;
989 io->bufpos = io->buf;
990 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
991 if (io_error(io))
992 return NULL;
993 io->bufsize += readsize;
997 static bool
998 io_write(struct io *io, const void *buf, size_t bufsize)
1000 size_t written = 0;
1002 while (!io_error(io) && written < bufsize) {
1003 ssize_t size;
1005 size = write(io->pipe, buf + written, bufsize - written);
1006 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1007 continue;
1008 else if (size == -1)
1009 io->error = errno;
1010 else
1011 written += size;
1014 return written == bufsize;
1017 static bool
1018 io_read_buf(struct io *io, char buf[], size_t bufsize)
1020 char *result = io_get(io, '\n', TRUE);
1022 if (result) {
1023 result = chomp_string(result);
1024 string_ncopy_do(buf, bufsize, result, strlen(result));
1027 return io_done(io) && result;
1030 static bool
1031 io_run_buf(const char **argv, char buf[], size_t bufsize)
1033 struct io io;
1035 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1038 static int
1039 io_load(struct io *io, const char *separators,
1040 int (*read_property)(char *, size_t, char *, size_t))
1042 char *name;
1043 int state = OK;
1045 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1046 char *value;
1047 size_t namelen;
1048 size_t valuelen;
1050 name = chomp_string(name);
1051 namelen = strcspn(name, separators);
1053 if (name[namelen]) {
1054 name[namelen] = 0;
1055 value = chomp_string(name + namelen + 1);
1056 valuelen = strlen(value);
1058 } else {
1059 value = "";
1060 valuelen = 0;
1063 state = read_property(name, namelen, value, valuelen);
1066 if (state != ERR && io_error(io))
1067 state = ERR;
1068 io_done(io);
1070 return state;
1073 static int
1074 io_run_load(const char **argv, const char *separators,
1075 int (*read_property)(char *, size_t, char *, size_t))
1077 struct io io;
1079 if (!io_run(&io, IO_RD, NULL, argv))
1080 return ERR;
1081 return io_load(&io, separators, read_property);
1086 * User requests
1089 #define REQ_INFO \
1090 /* XXX: Keep the view request first and in sync with views[]. */ \
1091 REQ_GROUP("View switching") \
1092 REQ_(VIEW_MAIN, "Show main view"), \
1093 REQ_(VIEW_DIFF, "Show diff view"), \
1094 REQ_(VIEW_LOG, "Show log view"), \
1095 REQ_(VIEW_TREE, "Show tree view"), \
1096 REQ_(VIEW_BLOB, "Show blob view"), \
1097 REQ_(VIEW_BLAME, "Show blame view"), \
1098 REQ_(VIEW_BRANCH, "Show branch view"), \
1099 REQ_(VIEW_HELP, "Show help page"), \
1100 REQ_(VIEW_PAGER, "Show pager view"), \
1101 REQ_(VIEW_STATUS, "Show status view"), \
1102 REQ_(VIEW_STAGE, "Show stage view"), \
1104 REQ_GROUP("View manipulation") \
1105 REQ_(ENTER, "Enter current line and scroll"), \
1106 REQ_(NEXT, "Move to next"), \
1107 REQ_(PREVIOUS, "Move to previous"), \
1108 REQ_(PARENT, "Move to parent"), \
1109 REQ_(VIEW_NEXT, "Move focus to next view"), \
1110 REQ_(REFRESH, "Reload and refresh"), \
1111 REQ_(MAXIMIZE, "Maximize the current view"), \
1112 REQ_(VIEW_CLOSE, "Close the current view"), \
1113 REQ_(QUIT, "Close all views and quit"), \
1115 REQ_GROUP("View specific requests") \
1116 REQ_(STATUS_UPDATE, "Update file status"), \
1117 REQ_(STATUS_REVERT, "Revert file changes"), \
1118 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1119 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1121 REQ_GROUP("Cursor navigation") \
1122 REQ_(MOVE_UP, "Move cursor one line up"), \
1123 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1124 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1125 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1126 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1127 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1129 REQ_GROUP("Scrolling") \
1130 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1131 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1132 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1133 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1134 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1135 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1137 REQ_GROUP("Searching") \
1138 REQ_(SEARCH, "Search the view"), \
1139 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1140 REQ_(FIND_NEXT, "Find next search match"), \
1141 REQ_(FIND_PREV, "Find previous search match"), \
1143 REQ_GROUP("Option manipulation") \
1144 REQ_(OPTIONS, "Open option menu"), \
1145 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1146 REQ_(TOGGLE_DATE, "Toggle date display"), \
1147 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1149 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1150 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1151 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1154 REQ_GROUP("Misc") \
1155 REQ_(PROMPT, "Bring up the prompt"), \
1156 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1157 REQ_(SHOW_VERSION, "Show version information"), \
1158 REQ_(STOP_LOADING, "Stop all loading views"), \
1159 REQ_(EDIT, "Open in editor"), \
1160 REQ_(NONE, "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168 /* Offset all requests to avoid conflicts with ncurses getch values. */
1169 REQ_UNKNOWN = KEY_MAX + 1,
1170 REQ_OFFSET,
1171 REQ_INFO
1173 #undef REQ_GROUP
1174 #undef REQ_
1177 struct request_info {
1178 enum request request;
1179 const char *name;
1180 int namelen;
1181 const char *help;
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187 REQ_INFO
1188 #undef REQ_GROUP
1189 #undef REQ_
1192 static enum request
1193 get_request(const char *name)
1195 int namelen = strlen(name);
1196 int i;
1198 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199 if (enum_equals(req_info[i], name, namelen))
1200 return req_info[i].request;
1202 return REQ_UNKNOWN;
1207 * Options
1210 /* Option and state variables. */
1211 static enum date opt_date = DATE_DEFAULT;
1212 static enum author opt_author = AUTHOR_DEFAULT;
1213 static bool opt_line_number = FALSE;
1214 static bool opt_line_graphics = TRUE;
1215 static bool opt_rev_graph = FALSE;
1216 static bool opt_show_refs = TRUE;
1217 static int opt_num_interval = 5;
1218 static double opt_hscroll = 0.50;
1219 static double opt_scale_split_view = 2.0 / 3.0;
1220 static int opt_tab_size = 8;
1221 static int opt_author_cols = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR] = "";
1223 static char opt_file[SIZEOF_STR] = "";
1224 static char opt_ref[SIZEOF_REF] = "";
1225 static char opt_head[SIZEOF_REF] = "";
1226 static char opt_remote[SIZEOF_REF] = "";
1227 static char opt_encoding[20] = "UTF-8";
1228 static iconv_t opt_iconv_in = ICONV_NONE;
1229 static iconv_t opt_iconv_out = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR] = "";
1231 static char opt_cdup[SIZEOF_STR] = "";
1232 static char opt_prefix[SIZEOF_STR] = "";
1233 static char opt_git_dir[SIZEOF_STR] = "";
1234 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR] = "";
1236 static FILE *opt_tty = NULL;
1237 static const char **opt_diff_args = NULL;
1238 static const char **opt_rev_args = NULL;
1239 static const char **opt_file_args = NULL;
1241 #define is_initial_commit() (!get_ref_head())
1242 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1246 * Line-oriented content detection.
1249 #define LINE_INFO \
1250 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1265 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1266 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1267 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1271 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1272 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1274 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1275 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1276 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1281 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1282 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1283 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1284 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1285 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1286 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1287 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1288 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1289 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1290 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1291 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1293 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1294 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1296 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1297 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1298 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1299 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1300 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1301 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1302 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1303 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1306 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1307 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1308 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1310 enum line_type {
1311 #define LINE(type, line, fg, bg, attr) \
1312 LINE_##type
1313 LINE_INFO,
1314 LINE_NONE
1315 #undef LINE
1318 struct line_info {
1319 const char *name; /* Option name. */
1320 int namelen; /* Size of option name. */
1321 const char *line; /* The start of line to match. */
1322 int linelen; /* Size of string to match. */
1323 int fg, bg, attr; /* Color and text attributes for the lines. */
1326 static struct line_info line_info[] = {
1327 #define LINE(type, line, fg, bg, attr) \
1328 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1329 LINE_INFO
1330 #undef LINE
1333 static enum line_type
1334 get_line_type(const char *line)
1336 int linelen = strlen(line);
1337 enum line_type type;
1339 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1340 /* Case insensitive search matches Signed-off-by lines better. */
1341 if (linelen >= line_info[type].linelen &&
1342 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1343 return type;
1345 return LINE_DEFAULT;
1348 static inline int
1349 get_line_attr(enum line_type type)
1351 assert(type < ARRAY_SIZE(line_info));
1352 return COLOR_PAIR(type) | line_info[type].attr;
1355 static struct line_info *
1356 get_line_info(const char *name)
1358 size_t namelen = strlen(name);
1359 enum line_type type;
1361 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1362 if (enum_equals(line_info[type], name, namelen))
1363 return &line_info[type];
1365 return NULL;
1368 static void
1369 init_colors(void)
1371 int default_bg = line_info[LINE_DEFAULT].bg;
1372 int default_fg = line_info[LINE_DEFAULT].fg;
1373 enum line_type type;
1375 start_color();
1377 if (assume_default_colors(default_fg, default_bg) == ERR) {
1378 default_bg = COLOR_BLACK;
1379 default_fg = COLOR_WHITE;
1382 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1383 struct line_info *info = &line_info[type];
1384 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1385 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1387 init_pair(type, fg, bg);
1391 struct line {
1392 enum line_type type;
1394 /* State flags */
1395 unsigned int selected:1;
1396 unsigned int dirty:1;
1397 unsigned int cleareol:1;
1398 unsigned int other:16;
1400 void *data; /* User data */
1405 * Keys
1408 struct keybinding {
1409 int alias;
1410 enum request request;
1413 static struct keybinding default_keybindings[] = {
1414 /* View switching */
1415 { 'm', REQ_VIEW_MAIN },
1416 { 'd', REQ_VIEW_DIFF },
1417 { 'l', REQ_VIEW_LOG },
1418 { 't', REQ_VIEW_TREE },
1419 { 'f', REQ_VIEW_BLOB },
1420 { 'B', REQ_VIEW_BLAME },
1421 { 'H', REQ_VIEW_BRANCH },
1422 { 'p', REQ_VIEW_PAGER },
1423 { 'h', REQ_VIEW_HELP },
1424 { 'S', REQ_VIEW_STATUS },
1425 { 'c', REQ_VIEW_STAGE },
1427 /* View manipulation */
1428 { 'q', REQ_VIEW_CLOSE },
1429 { KEY_TAB, REQ_VIEW_NEXT },
1430 { KEY_RETURN, REQ_ENTER },
1431 { KEY_UP, REQ_PREVIOUS },
1432 { KEY_DOWN, REQ_NEXT },
1433 { 'R', REQ_REFRESH },
1434 { KEY_F(5), REQ_REFRESH },
1435 { 'O', REQ_MAXIMIZE },
1437 /* Cursor navigation */
1438 { 'k', REQ_MOVE_UP },
1439 { 'j', REQ_MOVE_DOWN },
1440 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1441 { KEY_END, REQ_MOVE_LAST_LINE },
1442 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1443 { ' ', REQ_MOVE_PAGE_DOWN },
1444 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1445 { 'b', REQ_MOVE_PAGE_UP },
1446 { '-', REQ_MOVE_PAGE_UP },
1448 /* Scrolling */
1449 { KEY_LEFT, REQ_SCROLL_LEFT },
1450 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1451 { KEY_IC, REQ_SCROLL_LINE_UP },
1452 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1453 { 'w', REQ_SCROLL_PAGE_UP },
1454 { 's', REQ_SCROLL_PAGE_DOWN },
1456 /* Searching */
1457 { '/', REQ_SEARCH },
1458 { '?', REQ_SEARCH_BACK },
1459 { 'n', REQ_FIND_NEXT },
1460 { 'N', REQ_FIND_PREV },
1462 /* Misc */
1463 { 'Q', REQ_QUIT },
1464 { 'z', REQ_STOP_LOADING },
1465 { 'v', REQ_SHOW_VERSION },
1466 { 'r', REQ_SCREEN_REDRAW },
1467 { 'o', REQ_OPTIONS },
1468 { '.', REQ_TOGGLE_LINENO },
1469 { 'D', REQ_TOGGLE_DATE },
1470 { 'A', REQ_TOGGLE_AUTHOR },
1471 { 'g', REQ_TOGGLE_REV_GRAPH },
1472 { 'F', REQ_TOGGLE_REFS },
1473 { 'I', REQ_TOGGLE_SORT_ORDER },
1474 { 'i', REQ_TOGGLE_SORT_FIELD },
1475 { ':', REQ_PROMPT },
1476 { 'u', REQ_STATUS_UPDATE },
1477 { '!', REQ_STATUS_REVERT },
1478 { 'M', REQ_STATUS_MERGE },
1479 { '@', REQ_STAGE_NEXT },
1480 { ',', REQ_PARENT },
1481 { 'e', REQ_EDIT },
1484 #define KEYMAP_INFO \
1485 KEYMAP_(GENERIC), \
1486 KEYMAP_(MAIN), \
1487 KEYMAP_(DIFF), \
1488 KEYMAP_(LOG), \
1489 KEYMAP_(TREE), \
1490 KEYMAP_(BLOB), \
1491 KEYMAP_(BLAME), \
1492 KEYMAP_(BRANCH), \
1493 KEYMAP_(PAGER), \
1494 KEYMAP_(HELP), \
1495 KEYMAP_(STATUS), \
1496 KEYMAP_(STAGE)
1498 enum keymap {
1499 #define KEYMAP_(name) KEYMAP_##name
1500 KEYMAP_INFO
1501 #undef KEYMAP_
1504 static const struct enum_map keymap_table[] = {
1505 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1506 KEYMAP_INFO
1507 #undef KEYMAP_
1510 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1512 struct keybinding_table {
1513 struct keybinding *data;
1514 size_t size;
1517 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1519 static void
1520 add_keybinding(enum keymap keymap, enum request request, int key)
1522 struct keybinding_table *table = &keybindings[keymap];
1523 size_t i;
1525 for (i = 0; i < keybindings[keymap].size; i++) {
1526 if (keybindings[keymap].data[i].alias == key) {
1527 keybindings[keymap].data[i].request = request;
1528 return;
1532 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1533 if (!table->data)
1534 die("Failed to allocate keybinding");
1535 table->data[table->size].alias = key;
1536 table->data[table->size++].request = request;
1538 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1539 int i;
1541 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542 if (default_keybindings[i].alias == key)
1543 default_keybindings[i].request = REQ_NONE;
1547 /* Looks for a key binding first in the given map, then in the generic map, and
1548 * lastly in the default keybindings. */
1549 static enum request
1550 get_keybinding(enum keymap keymap, int key)
1552 size_t i;
1554 for (i = 0; i < keybindings[keymap].size; i++)
1555 if (keybindings[keymap].data[i].alias == key)
1556 return keybindings[keymap].data[i].request;
1558 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1559 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1560 return keybindings[KEYMAP_GENERIC].data[i].request;
1562 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1563 if (default_keybindings[i].alias == key)
1564 return default_keybindings[i].request;
1566 return (enum request) key;
1570 struct key {
1571 const char *name;
1572 int value;
1575 static const struct key key_table[] = {
1576 { "Enter", KEY_RETURN },
1577 { "Space", ' ' },
1578 { "Backspace", KEY_BACKSPACE },
1579 { "Tab", KEY_TAB },
1580 { "Escape", KEY_ESC },
1581 { "Left", KEY_LEFT },
1582 { "Right", KEY_RIGHT },
1583 { "Up", KEY_UP },
1584 { "Down", KEY_DOWN },
1585 { "Insert", KEY_IC },
1586 { "Delete", KEY_DC },
1587 { "Hash", '#' },
1588 { "Home", KEY_HOME },
1589 { "End", KEY_END },
1590 { "PageUp", KEY_PPAGE },
1591 { "PageDown", KEY_NPAGE },
1592 { "F1", KEY_F(1) },
1593 { "F2", KEY_F(2) },
1594 { "F3", KEY_F(3) },
1595 { "F4", KEY_F(4) },
1596 { "F5", KEY_F(5) },
1597 { "F6", KEY_F(6) },
1598 { "F7", KEY_F(7) },
1599 { "F8", KEY_F(8) },
1600 { "F9", KEY_F(9) },
1601 { "F10", KEY_F(10) },
1602 { "F11", KEY_F(11) },
1603 { "F12", KEY_F(12) },
1606 static int
1607 get_key_value(const char *name)
1609 int i;
1611 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1612 if (!strcasecmp(key_table[i].name, name))
1613 return key_table[i].value;
1615 if (strlen(name) == 1 && isprint(*name))
1616 return (int) *name;
1618 return ERR;
1621 static const char *
1622 get_key_name(int key_value)
1624 static char key_char[] = "'X'";
1625 const char *seq = NULL;
1626 int key;
1628 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1629 if (key_table[key].value == key_value)
1630 seq = key_table[key].name;
1632 if (seq == NULL &&
1633 key_value < 127 &&
1634 isprint(key_value)) {
1635 key_char[1] = (char) key_value;
1636 seq = key_char;
1639 return seq ? seq : "(no key)";
1642 static bool
1643 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1645 const char *sep = *pos > 0 ? ", " : "";
1646 const char *keyname = get_key_name(keybinding->alias);
1648 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1651 static bool
1652 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1653 enum keymap keymap, bool all)
1655 int i;
1657 for (i = 0; i < keybindings[keymap].size; i++) {
1658 if (keybindings[keymap].data[i].request == request) {
1659 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1660 return FALSE;
1661 if (!all)
1662 break;
1666 return TRUE;
1669 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1671 static const char *
1672 get_keys(enum keymap keymap, enum request request, bool all)
1674 static char buf[BUFSIZ];
1675 size_t pos = 0;
1676 int i;
1678 buf[pos] = 0;
1680 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1681 return "Too many keybindings!";
1682 if (pos > 0 && !all)
1683 return buf;
1685 if (keymap != KEYMAP_GENERIC) {
1686 /* Only the generic keymap includes the default keybindings when
1687 * listing all keys. */
1688 if (all)
1689 return buf;
1691 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1692 return "Too many keybindings!";
1693 if (pos)
1694 return buf;
1697 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1698 if (default_keybindings[i].request == request) {
1699 if (!append_key(buf, &pos, &default_keybindings[i]))
1700 return "Too many keybindings!";
1701 if (!all)
1702 return buf;
1706 return buf;
1709 struct run_request {
1710 enum keymap keymap;
1711 int key;
1712 const char **argv;
1715 static struct run_request *run_request;
1716 static size_t run_requests;
1718 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1720 static enum request
1721 add_run_request(enum keymap keymap, int key, const char **argv)
1723 struct run_request *req;
1725 if (!realloc_run_requests(&run_request, run_requests, 1))
1726 return REQ_NONE;
1728 req = &run_request[run_requests];
1729 req->keymap = keymap;
1730 req->key = key;
1731 req->argv = NULL;
1733 if (!argv_copy(&req->argv, argv))
1734 return REQ_NONE;
1736 return REQ_NONE + ++run_requests;
1739 static struct run_request *
1740 get_run_request(enum request request)
1742 if (request <= REQ_NONE)
1743 return NULL;
1744 return &run_request[request - REQ_NONE - 1];
1747 static void
1748 add_builtin_run_requests(void)
1750 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1751 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1752 const char *commit[] = { "git", "commit", NULL };
1753 const char *gc[] = { "git", "gc", NULL };
1754 struct run_request reqs[] = {
1755 { KEYMAP_MAIN, 'C', cherry_pick },
1756 { KEYMAP_STATUS, 'C', commit },
1757 { KEYMAP_BRANCH, 'C', checkout },
1758 { KEYMAP_GENERIC, 'G', gc },
1760 int i;
1762 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1763 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1765 if (req != reqs[i].key)
1766 continue;
1767 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1768 if (req != REQ_NONE)
1769 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1774 * User config file handling.
1777 static int config_lineno;
1778 static bool config_errors;
1779 static const char *config_msg;
1781 static const struct enum_map color_map[] = {
1782 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1783 COLOR_MAP(DEFAULT),
1784 COLOR_MAP(BLACK),
1785 COLOR_MAP(BLUE),
1786 COLOR_MAP(CYAN),
1787 COLOR_MAP(GREEN),
1788 COLOR_MAP(MAGENTA),
1789 COLOR_MAP(RED),
1790 COLOR_MAP(WHITE),
1791 COLOR_MAP(YELLOW),
1794 static const struct enum_map attr_map[] = {
1795 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1796 ATTR_MAP(NORMAL),
1797 ATTR_MAP(BLINK),
1798 ATTR_MAP(BOLD),
1799 ATTR_MAP(DIM),
1800 ATTR_MAP(REVERSE),
1801 ATTR_MAP(STANDOUT),
1802 ATTR_MAP(UNDERLINE),
1805 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1807 static int parse_step(double *opt, const char *arg)
1809 *opt = atoi(arg);
1810 if (!strchr(arg, '%'))
1811 return OK;
1813 /* "Shift down" so 100% and 1 does not conflict. */
1814 *opt = (*opt - 1) / 100;
1815 if (*opt >= 1.0) {
1816 *opt = 0.99;
1817 config_msg = "Step value larger than 100%";
1818 return ERR;
1820 if (*opt < 0.0) {
1821 *opt = 1;
1822 config_msg = "Invalid step value";
1823 return ERR;
1825 return OK;
1828 static int
1829 parse_int(int *opt, const char *arg, int min, int max)
1831 int value = atoi(arg);
1833 if (min <= value && value <= max) {
1834 *opt = value;
1835 return OK;
1838 config_msg = "Integer value out of bound";
1839 return ERR;
1842 static bool
1843 set_color(int *color, const char *name)
1845 if (map_enum(color, color_map, name))
1846 return TRUE;
1847 if (!prefixcmp(name, "color"))
1848 return parse_int(color, name + 5, 0, 255) == OK;
1849 return FALSE;
1852 /* Wants: object fgcolor bgcolor [attribute] */
1853 static int
1854 option_color_command(int argc, const char *argv[])
1856 struct line_info *info;
1858 if (argc < 3) {
1859 config_msg = "Wrong number of arguments given to color command";
1860 return ERR;
1863 info = get_line_info(argv[0]);
1864 if (!info) {
1865 static const struct enum_map obsolete[] = {
1866 ENUM_MAP("main-delim", LINE_DELIMITER),
1867 ENUM_MAP("main-date", LINE_DATE),
1868 ENUM_MAP("main-author", LINE_AUTHOR),
1870 int index;
1872 if (!map_enum(&index, obsolete, argv[0])) {
1873 config_msg = "Unknown color name";
1874 return ERR;
1876 info = &line_info[index];
1879 if (!set_color(&info->fg, argv[1]) ||
1880 !set_color(&info->bg, argv[2])) {
1881 config_msg = "Unknown color";
1882 return ERR;
1885 info->attr = 0;
1886 while (argc-- > 3) {
1887 int attr;
1889 if (!set_attribute(&attr, argv[argc])) {
1890 config_msg = "Unknown attribute";
1891 return ERR;
1893 info->attr |= attr;
1896 return OK;
1899 static int parse_bool(bool *opt, const char *arg)
1901 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1902 ? TRUE : FALSE;
1903 return OK;
1906 static int parse_enum_do(unsigned int *opt, const char *arg,
1907 const struct enum_map *map, size_t map_size)
1909 bool is_true;
1911 assert(map_size > 1);
1913 if (map_enum_do(map, map_size, (int *) opt, arg))
1914 return OK;
1916 if (parse_bool(&is_true, arg) != OK)
1917 return ERR;
1919 *opt = is_true ? map[1].value : map[0].value;
1920 return OK;
1923 #define parse_enum(opt, arg, map) \
1924 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1926 static int
1927 parse_string(char *opt, const char *arg, size_t optsize)
1929 int arglen = strlen(arg);
1931 switch (arg[0]) {
1932 case '\"':
1933 case '\'':
1934 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1935 config_msg = "Unmatched quotation";
1936 return ERR;
1938 arg += 1; arglen -= 2;
1939 default:
1940 string_ncopy_do(opt, optsize, arg, arglen);
1941 return OK;
1945 /* Wants: name = value */
1946 static int
1947 option_set_command(int argc, const char *argv[])
1949 if (argc != 3) {
1950 config_msg = "Wrong number of arguments given to set command";
1951 return ERR;
1954 if (strcmp(argv[1], "=")) {
1955 config_msg = "No value assigned";
1956 return ERR;
1959 if (!strcmp(argv[0], "show-author"))
1960 return parse_enum(&opt_author, argv[2], author_map);
1962 if (!strcmp(argv[0], "show-date"))
1963 return parse_enum(&opt_date, argv[2], date_map);
1965 if (!strcmp(argv[0], "show-rev-graph"))
1966 return parse_bool(&opt_rev_graph, argv[2]);
1968 if (!strcmp(argv[0], "show-refs"))
1969 return parse_bool(&opt_show_refs, argv[2]);
1971 if (!strcmp(argv[0], "show-line-numbers"))
1972 return parse_bool(&opt_line_number, argv[2]);
1974 if (!strcmp(argv[0], "line-graphics"))
1975 return parse_bool(&opt_line_graphics, argv[2]);
1977 if (!strcmp(argv[0], "line-number-interval"))
1978 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1980 if (!strcmp(argv[0], "author-width"))
1981 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1983 if (!strcmp(argv[0], "horizontal-scroll"))
1984 return parse_step(&opt_hscroll, argv[2]);
1986 if (!strcmp(argv[0], "split-view-height"))
1987 return parse_step(&opt_scale_split_view, argv[2]);
1989 if (!strcmp(argv[0], "tab-size"))
1990 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1992 if (!strcmp(argv[0], "commit-encoding"))
1993 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1995 config_msg = "Unknown variable name";
1996 return ERR;
1999 /* Wants: mode request key */
2000 static int
2001 option_bind_command(int argc, const char *argv[])
2003 enum request request;
2004 int keymap = -1;
2005 int key;
2007 if (argc < 3) {
2008 config_msg = "Wrong number of arguments given to bind command";
2009 return ERR;
2012 if (!set_keymap(&keymap, argv[0])) {
2013 config_msg = "Unknown key map";
2014 return ERR;
2017 key = get_key_value(argv[1]);
2018 if (key == ERR) {
2019 config_msg = "Unknown key";
2020 return ERR;
2023 request = get_request(argv[2]);
2024 if (request == REQ_UNKNOWN) {
2025 static const struct enum_map obsolete[] = {
2026 ENUM_MAP("cherry-pick", REQ_NONE),
2027 ENUM_MAP("screen-resize", REQ_NONE),
2028 ENUM_MAP("tree-parent", REQ_PARENT),
2030 int alias;
2032 if (map_enum(&alias, obsolete, argv[2])) {
2033 if (alias != REQ_NONE)
2034 add_keybinding(keymap, alias, key);
2035 config_msg = "Obsolete request name";
2036 return ERR;
2039 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2040 request = add_run_request(keymap, key, argv + 2);
2041 if (request == REQ_UNKNOWN) {
2042 config_msg = "Unknown request name";
2043 return ERR;
2046 add_keybinding(keymap, request, key);
2048 return OK;
2051 static int
2052 set_option(const char *opt, char *value)
2054 const char *argv[SIZEOF_ARG];
2055 int argc = 0;
2057 if (!argv_from_string(argv, &argc, value)) {
2058 config_msg = "Too many option arguments";
2059 return ERR;
2062 if (!strcmp(opt, "color"))
2063 return option_color_command(argc, argv);
2065 if (!strcmp(opt, "set"))
2066 return option_set_command(argc, argv);
2068 if (!strcmp(opt, "bind"))
2069 return option_bind_command(argc, argv);
2071 config_msg = "Unknown option command";
2072 return ERR;
2075 static int
2076 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2078 int status = OK;
2080 config_lineno++;
2081 config_msg = "Internal error";
2083 /* Check for comment markers, since read_properties() will
2084 * only ensure opt and value are split at first " \t". */
2085 optlen = strcspn(opt, "#");
2086 if (optlen == 0)
2087 return OK;
2089 if (opt[optlen] != 0) {
2090 config_msg = "No option value";
2091 status = ERR;
2093 } else {
2094 /* Look for comment endings in the value. */
2095 size_t len = strcspn(value, "#");
2097 if (len < valuelen) {
2098 valuelen = len;
2099 value[valuelen] = 0;
2102 status = set_option(opt, value);
2105 if (status == ERR) {
2106 warn("Error on line %d, near '%.*s': %s",
2107 config_lineno, (int) optlen, opt, config_msg);
2108 config_errors = TRUE;
2111 /* Always keep going if errors are encountered. */
2112 return OK;
2115 static void
2116 load_option_file(const char *path)
2118 struct io io;
2120 /* It's OK that the file doesn't exist. */
2121 if (!io_open(&io, "%s", path))
2122 return;
2124 config_lineno = 0;
2125 config_errors = FALSE;
2127 if (io_load(&io, " \t", read_option) == ERR ||
2128 config_errors == TRUE)
2129 warn("Errors while loading %s.", path);
2132 static int
2133 load_options(void)
2135 const char *home = getenv("HOME");
2136 const char *tigrc_user = getenv("TIGRC_USER");
2137 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2138 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2139 char buf[SIZEOF_STR];
2141 if (!tigrc_system)
2142 tigrc_system = SYSCONFDIR "/tigrc";
2143 load_option_file(tigrc_system);
2145 if (!tigrc_user) {
2146 if (!home || !string_format(buf, "%s/.tigrc", home))
2147 return ERR;
2148 tigrc_user = buf;
2150 load_option_file(tigrc_user);
2152 /* Add _after_ loading config files to avoid adding run requests
2153 * that conflict with keybindings. */
2154 add_builtin_run_requests();
2156 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2157 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2158 int argc = 0;
2160 if (!string_format(buf, "%s", tig_diff_opts) ||
2161 !argv_from_string(diff_opts, &argc, buf))
2162 die("TIG_DIFF_OPTS contains too many arguments");
2163 else if (!argv_copy(&opt_diff_args, diff_opts))
2164 die("Failed to format TIG_DIFF_OPTS arguments");
2167 return OK;
2172 * The viewer
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views() (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF] = "";
2189 static char ref_commit[SIZEOF_REF] = "HEAD";
2190 static char ref_head[SIZEOF_REF] = "HEAD";
2191 static char ref_branch[SIZEOF_REF] = "";
2193 enum view_type {
2194 VIEW_MAIN,
2195 VIEW_DIFF,
2196 VIEW_LOG,
2197 VIEW_TREE,
2198 VIEW_BLOB,
2199 VIEW_BLAME,
2200 VIEW_BRANCH,
2201 VIEW_HELP,
2202 VIEW_PAGER,
2203 VIEW_STATUS,
2204 VIEW_STAGE,
2207 struct view {
2208 enum view_type type; /* View type */
2209 const char *name; /* View name */
2210 const char *cmd_env; /* Command line set via environment */
2211 const char *id; /* Points to either of ref_{head,commit,blob} */
2213 struct view_ops *ops; /* View operations */
2215 enum keymap keymap; /* What keymap does this view have */
2216 bool git_dir; /* Whether the view requires a git directory. */
2218 char ref[SIZEOF_REF]; /* Hovered commit reference */
2219 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2221 int height, width; /* The width and height of the main window */
2222 WINDOW *win; /* The main window */
2223 WINDOW *title; /* The title window living below the main window */
2225 /* Navigation */
2226 unsigned long offset; /* Offset of the window top */
2227 unsigned long yoffset; /* Offset from the window side. */
2228 unsigned long lineno; /* Current line number */
2229 unsigned long p_offset; /* Previous offset of the window top */
2230 unsigned long p_yoffset;/* Previous offset from the window side */
2231 unsigned long p_lineno; /* Previous current line number */
2232 bool p_restore; /* Should the previous position be restored. */
2234 /* Searching */
2235 char grep[SIZEOF_STR]; /* Search string */
2236 regex_t *regex; /* Pre-compiled regexp */
2238 /* If non-NULL, points to the view that opened this view. If this view
2239 * is closed tig will switch back to the parent view. */
2240 struct view *parent;
2241 struct view *prev;
2243 /* Buffering */
2244 size_t lines; /* Total number of lines */
2245 struct line *line; /* Line index */
2246 unsigned int digits; /* Number of digits in the lines member. */
2248 /* Drawing */
2249 struct line *curline; /* Line currently being drawn. */
2250 enum line_type curtype; /* Attribute currently used for drawing. */
2251 unsigned long col; /* Column when drawing. */
2252 bool has_scrolled; /* View was scrolled. */
2254 /* Loading */
2255 const char **argv; /* Shell command arguments. */
2256 const char *dir; /* Directory from which to execute. */
2257 struct io io;
2258 struct io *pipe;
2259 time_t start_time;
2260 time_t update_secs;
2263 struct view_ops {
2264 /* What type of content being displayed. Used in the title bar. */
2265 const char *type;
2266 /* Default command arguments. */
2267 const char **argv;
2268 /* Open and reads in all view content. */
2269 bool (*open)(struct view *view);
2270 /* Read one line; updates view->line. */
2271 bool (*read)(struct view *view, char *data);
2272 /* Draw one line; @lineno must be < view->height. */
2273 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2274 /* Depending on view handle a special requests. */
2275 enum request (*request)(struct view *view, enum request request, struct line *line);
2276 /* Search for regexp in a line. */
2277 bool (*grep)(struct view *view, struct line *line);
2278 /* Select line */
2279 void (*select)(struct view *view, struct line *line);
2280 /* Prepare view for loading */
2281 bool (*prepare)(struct view *view);
2284 static struct view_ops blame_ops;
2285 static struct view_ops blob_ops;
2286 static struct view_ops diff_ops;
2287 static struct view_ops help_ops;
2288 static struct view_ops log_ops;
2289 static struct view_ops main_ops;
2290 static struct view_ops pager_ops;
2291 static struct view_ops stage_ops;
2292 static struct view_ops status_ops;
2293 static struct view_ops tree_ops;
2294 static struct view_ops branch_ops;
2296 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2297 { type, name, #env, ref, ops, map, git }
2299 #define VIEW_(id, name, ops, git, ref) \
2300 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2302 static struct view views[] = {
2303 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2304 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2305 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2306 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2307 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2308 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2309 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2310 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2311 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2312 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2313 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2316 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2318 #define foreach_view(view, i) \
2319 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2321 #define view_is_displayed(view) \
2322 (view == display[0] || view == display[1])
2324 static enum request
2325 view_request(struct view *view, enum request request)
2327 if (!view || !view->lines)
2328 return request;
2329 return view->ops->request(view, request, &view->line[view->lineno]);
2334 * View drawing.
2337 static inline void
2338 set_view_attr(struct view *view, enum line_type type)
2340 if (!view->curline->selected && view->curtype != type) {
2341 (void) wattrset(view->win, get_line_attr(type));
2342 wchgat(view->win, -1, 0, type, NULL);
2343 view->curtype = type;
2347 static int
2348 draw_chars(struct view *view, enum line_type type, const char *string,
2349 int max_len, bool use_tilde)
2351 static char out_buffer[BUFSIZ * 2];
2352 int len = 0;
2353 int col = 0;
2354 int trimmed = FALSE;
2355 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2357 if (max_len <= 0)
2358 return 0;
2360 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2362 set_view_attr(view, type);
2363 if (len > 0) {
2364 if (opt_iconv_out != ICONV_NONE) {
2365 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2366 size_t inlen = len + 1;
2368 char *outbuf = out_buffer;
2369 size_t outlen = sizeof(out_buffer);
2371 size_t ret;
2373 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2374 if (ret != (size_t) -1) {
2375 string = out_buffer;
2376 len = sizeof(out_buffer) - outlen;
2380 waddnstr(view->win, string, len);
2382 if (trimmed && use_tilde) {
2383 set_view_attr(view, LINE_DELIMITER);
2384 waddch(view->win, '~');
2385 col++;
2388 return col;
2391 static int
2392 draw_space(struct view *view, enum line_type type, int max, int spaces)
2394 static char space[] = " ";
2395 int col = 0;
2397 spaces = MIN(max, spaces);
2399 while (spaces > 0) {
2400 int len = MIN(spaces, sizeof(space) - 1);
2402 col += draw_chars(view, type, space, len, FALSE);
2403 spaces -= len;
2406 return col;
2409 static bool
2410 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2412 char text[SIZEOF_STR];
2414 do {
2415 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2417 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2418 string += pos;
2419 } while (*string && view->width + view->yoffset > view->col);
2421 return view->width + view->yoffset <= view->col;
2424 static bool
2425 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2427 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2428 int max = view->width + view->yoffset - view->col;
2429 int i;
2431 if (max < size)
2432 size = max;
2434 set_view_attr(view, type);
2435 /* Using waddch() instead of waddnstr() ensures that
2436 * they'll be rendered correctly for the cursor line. */
2437 for (i = skip; i < size; i++)
2438 waddch(view->win, graphic[i]);
2440 view->col += size;
2441 if (size < max && skip <= size)
2442 waddch(view->win, ' ');
2443 view->col++;
2445 return view->width + view->yoffset <= view->col;
2448 static bool
2449 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2451 int max = MIN(view->width + view->yoffset - view->col, len);
2452 int col;
2454 if (text)
2455 col = draw_chars(view, type, text, max - 1, trim);
2456 else
2457 col = draw_space(view, type, max - 1, max - 1);
2459 view->col += col;
2460 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2461 return view->width + view->yoffset <= view->col;
2464 static bool
2465 draw_date(struct view *view, struct time *time)
2467 const char *date = mkdate(time, opt_date);
2468 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2470 return draw_field(view, LINE_DATE, date, cols, FALSE);
2473 static bool
2474 draw_author(struct view *view, const char *author)
2476 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2477 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2479 if (abbreviate && author)
2480 author = get_author_initials(author);
2482 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2485 static bool
2486 draw_mode(struct view *view, mode_t mode)
2488 const char *str;
2490 if (S_ISDIR(mode))
2491 str = "drwxr-xr-x";
2492 else if (S_ISLNK(mode))
2493 str = "lrwxrwxrwx";
2494 else if (S_ISGITLINK(mode))
2495 str = "m---------";
2496 else if (S_ISREG(mode) && mode & S_IXUSR)
2497 str = "-rwxr-xr-x";
2498 else if (S_ISREG(mode))
2499 str = "-rw-r--r--";
2500 else
2501 str = "----------";
2503 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2506 static bool
2507 draw_lineno(struct view *view, unsigned int lineno)
2509 char number[10];
2510 int digits3 = view->digits < 3 ? 3 : view->digits;
2511 int max = MIN(view->width + view->yoffset - view->col, digits3);
2512 char *text = NULL;
2513 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2515 lineno += view->offset + 1;
2516 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2517 static char fmt[] = "%1ld";
2519 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2520 if (string_format(number, fmt, lineno))
2521 text = number;
2523 if (text)
2524 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2525 else
2526 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2527 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2530 static bool
2531 draw_view_line(struct view *view, unsigned int lineno)
2533 struct line *line;
2534 bool selected = (view->offset + lineno == view->lineno);
2536 assert(view_is_displayed(view));
2538 if (view->offset + lineno >= view->lines)
2539 return FALSE;
2541 line = &view->line[view->offset + lineno];
2543 wmove(view->win, lineno, 0);
2544 if (line->cleareol)
2545 wclrtoeol(view->win);
2546 view->col = 0;
2547 view->curline = line;
2548 view->curtype = LINE_NONE;
2549 line->selected = FALSE;
2550 line->dirty = line->cleareol = 0;
2552 if (selected) {
2553 set_view_attr(view, LINE_CURSOR);
2554 line->selected = TRUE;
2555 view->ops->select(view, line);
2558 return view->ops->draw(view, line, lineno);
2561 static void
2562 redraw_view_dirty(struct view *view)
2564 bool dirty = FALSE;
2565 int lineno;
2567 for (lineno = 0; lineno < view->height; lineno++) {
2568 if (view->offset + lineno >= view->lines)
2569 break;
2570 if (!view->line[view->offset + lineno].dirty)
2571 continue;
2572 dirty = TRUE;
2573 if (!draw_view_line(view, lineno))
2574 break;
2577 if (!dirty)
2578 return;
2579 wnoutrefresh(view->win);
2582 static void
2583 redraw_view_from(struct view *view, int lineno)
2585 assert(0 <= lineno && lineno < view->height);
2587 for (; lineno < view->height; lineno++) {
2588 if (!draw_view_line(view, lineno))
2589 break;
2592 wnoutrefresh(view->win);
2595 static void
2596 redraw_view(struct view *view)
2598 werase(view->win);
2599 redraw_view_from(view, 0);
2603 static void
2604 update_view_title(struct view *view)
2606 char buf[SIZEOF_STR];
2607 char state[SIZEOF_STR];
2608 size_t bufpos = 0, statelen = 0;
2610 assert(view_is_displayed(view));
2612 if (view->type != VIEW_STATUS && view->lines) {
2613 unsigned int view_lines = view->offset + view->height;
2614 unsigned int lines = view->lines
2615 ? MIN(view_lines, view->lines) * 100 / view->lines
2616 : 0;
2618 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2619 view->ops->type,
2620 view->lineno + 1,
2621 view->lines,
2622 lines);
2626 if (view->pipe) {
2627 time_t secs = time(NULL) - view->start_time;
2629 /* Three git seconds are a long time ... */
2630 if (secs > 2)
2631 string_format_from(state, &statelen, " loading %lds", secs);
2634 string_format_from(buf, &bufpos, "[%s]", view->name);
2635 if (*view->ref && bufpos < view->width) {
2636 size_t refsize = strlen(view->ref);
2637 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2639 if (minsize < view->width)
2640 refsize = view->width - minsize + 7;
2641 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2644 if (statelen && bufpos < view->width) {
2645 string_format_from(buf, &bufpos, "%s", state);
2648 if (view == display[current_view])
2649 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2650 else
2651 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2653 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2654 wclrtoeol(view->title);
2655 wnoutrefresh(view->title);
2658 static int
2659 apply_step(double step, int value)
2661 if (step >= 1)
2662 return (int) step;
2663 value *= step + 0.01;
2664 return value ? value : 1;
2667 static void
2668 resize_display(void)
2670 int offset, i;
2671 struct view *base = display[0];
2672 struct view *view = display[1] ? display[1] : display[0];
2674 /* Setup window dimensions */
2676 getmaxyx(stdscr, base->height, base->width);
2678 /* Make room for the status window. */
2679 base->height -= 1;
2681 if (view != base) {
2682 /* Horizontal split. */
2683 view->width = base->width;
2684 view->height = apply_step(opt_scale_split_view, base->height);
2685 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2686 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2687 base->height -= view->height;
2689 /* Make room for the title bar. */
2690 view->height -= 1;
2693 /* Make room for the title bar. */
2694 base->height -= 1;
2696 offset = 0;
2698 foreach_displayed_view (view, i) {
2699 if (!view->win) {
2700 view->win = newwin(view->height, 0, offset, 0);
2701 if (!view->win)
2702 die("Failed to create %s view", view->name);
2704 scrollok(view->win, FALSE);
2706 view->title = newwin(1, 0, offset + view->height, 0);
2707 if (!view->title)
2708 die("Failed to create title window");
2710 } else {
2711 wresize(view->win, view->height, view->width);
2712 mvwin(view->win, offset, 0);
2713 mvwin(view->title, offset + view->height, 0);
2716 offset += view->height + 1;
2720 static void
2721 redraw_display(bool clear)
2723 struct view *view;
2724 int i;
2726 foreach_displayed_view (view, i) {
2727 if (clear)
2728 wclear(view->win);
2729 redraw_view(view);
2730 update_view_title(view);
2736 * Option management
2739 static void
2740 toggle_enum_option_do(unsigned int *opt, const char *help,
2741 const struct enum_map *map, size_t size)
2743 *opt = (*opt + 1) % size;
2744 redraw_display(FALSE);
2745 report("Displaying %s %s", enum_name(map[*opt]), help);
2748 #define toggle_enum_option(opt, help, map) \
2749 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2751 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2752 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2754 static void
2755 toggle_view_option(bool *option, const char *help)
2757 *option = !*option;
2758 redraw_display(FALSE);
2759 report("%sabling %s", *option ? "En" : "Dis", help);
2762 static void
2763 open_option_menu(void)
2765 const struct menu_item menu[] = {
2766 { '.', "line numbers", &opt_line_number },
2767 { 'D', "date display", &opt_date },
2768 { 'A', "author display", &opt_author },
2769 { 'g', "revision graph display", &opt_rev_graph },
2770 { 'F', "reference display", &opt_show_refs },
2771 { 0 }
2773 int selected = 0;
2775 if (prompt_menu("Toggle option", menu, &selected)) {
2776 if (menu[selected].data == &opt_date)
2777 toggle_date();
2778 else if (menu[selected].data == &opt_author)
2779 toggle_author();
2780 else
2781 toggle_view_option(menu[selected].data, menu[selected].text);
2785 static void
2786 maximize_view(struct view *view)
2788 memset(display, 0, sizeof(display));
2789 current_view = 0;
2790 display[current_view] = view;
2791 resize_display();
2792 redraw_display(FALSE);
2793 report("");
2798 * Navigation
2801 static bool
2802 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2804 if (lineno >= view->lines)
2805 lineno = view->lines > 0 ? view->lines - 1 : 0;
2807 if (offset > lineno || offset + view->height <= lineno) {
2808 unsigned long half = view->height / 2;
2810 if (lineno > half)
2811 offset = lineno - half;
2812 else
2813 offset = 0;
2816 if (offset != view->offset || lineno != view->lineno) {
2817 view->offset = offset;
2818 view->lineno = lineno;
2819 return TRUE;
2822 return FALSE;
2825 /* Scrolling backend */
2826 static void
2827 do_scroll_view(struct view *view, int lines)
2829 bool redraw_current_line = FALSE;
2831 /* The rendering expects the new offset. */
2832 view->offset += lines;
2834 assert(0 <= view->offset && view->offset < view->lines);
2835 assert(lines);
2837 /* Move current line into the view. */
2838 if (view->lineno < view->offset) {
2839 view->lineno = view->offset;
2840 redraw_current_line = TRUE;
2841 } else if (view->lineno >= view->offset + view->height) {
2842 view->lineno = view->offset + view->height - 1;
2843 redraw_current_line = TRUE;
2846 assert(view->offset <= view->lineno && view->lineno < view->lines);
2848 /* Redraw the whole screen if scrolling is pointless. */
2849 if (view->height < ABS(lines)) {
2850 redraw_view(view);
2852 } else {
2853 int line = lines > 0 ? view->height - lines : 0;
2854 int end = line + ABS(lines);
2856 scrollok(view->win, TRUE);
2857 wscrl(view->win, lines);
2858 scrollok(view->win, FALSE);
2860 while (line < end && draw_view_line(view, line))
2861 line++;
2863 if (redraw_current_line)
2864 draw_view_line(view, view->lineno - view->offset);
2865 wnoutrefresh(view->win);
2868 view->has_scrolled = TRUE;
2869 report("");
2872 /* Scroll frontend */
2873 static void
2874 scroll_view(struct view *view, enum request request)
2876 int lines = 1;
2878 assert(view_is_displayed(view));
2880 switch (request) {
2881 case REQ_SCROLL_LEFT:
2882 if (view->yoffset == 0) {
2883 report("Cannot scroll beyond the first column");
2884 return;
2886 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2887 view->yoffset = 0;
2888 else
2889 view->yoffset -= apply_step(opt_hscroll, view->width);
2890 redraw_view_from(view, 0);
2891 report("");
2892 return;
2893 case REQ_SCROLL_RIGHT:
2894 view->yoffset += apply_step(opt_hscroll, view->width);
2895 redraw_view(view);
2896 report("");
2897 return;
2898 case REQ_SCROLL_PAGE_DOWN:
2899 lines = view->height;
2900 case REQ_SCROLL_LINE_DOWN:
2901 if (view->offset + lines > view->lines)
2902 lines = view->lines - view->offset;
2904 if (lines == 0 || view->offset + view->height >= view->lines) {
2905 report("Cannot scroll beyond the last line");
2906 return;
2908 break;
2910 case REQ_SCROLL_PAGE_UP:
2911 lines = view->height;
2912 case REQ_SCROLL_LINE_UP:
2913 if (lines > view->offset)
2914 lines = view->offset;
2916 if (lines == 0) {
2917 report("Cannot scroll beyond the first line");
2918 return;
2921 lines = -lines;
2922 break;
2924 default:
2925 die("request %d not handled in switch", request);
2928 do_scroll_view(view, lines);
2931 /* Cursor moving */
2932 static void
2933 move_view(struct view *view, enum request request)
2935 int scroll_steps = 0;
2936 int steps;
2938 switch (request) {
2939 case REQ_MOVE_FIRST_LINE:
2940 steps = -view->lineno;
2941 break;
2943 case REQ_MOVE_LAST_LINE:
2944 steps = view->lines - view->lineno - 1;
2945 break;
2947 case REQ_MOVE_PAGE_UP:
2948 steps = view->height > view->lineno
2949 ? -view->lineno : -view->height;
2950 break;
2952 case REQ_MOVE_PAGE_DOWN:
2953 steps = view->lineno + view->height >= view->lines
2954 ? view->lines - view->lineno - 1 : view->height;
2955 break;
2957 case REQ_MOVE_UP:
2958 steps = -1;
2959 break;
2961 case REQ_MOVE_DOWN:
2962 steps = 1;
2963 break;
2965 default:
2966 die("request %d not handled in switch", request);
2969 if (steps <= 0 && view->lineno == 0) {
2970 report("Cannot move beyond the first line");
2971 return;
2973 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2974 report("Cannot move beyond the last line");
2975 return;
2978 /* Move the current line */
2979 view->lineno += steps;
2980 assert(0 <= view->lineno && view->lineno < view->lines);
2982 /* Check whether the view needs to be scrolled */
2983 if (view->lineno < view->offset ||
2984 view->lineno >= view->offset + view->height) {
2985 scroll_steps = steps;
2986 if (steps < 0 && -steps > view->offset) {
2987 scroll_steps = -view->offset;
2989 } else if (steps > 0) {
2990 if (view->lineno == view->lines - 1 &&
2991 view->lines > view->height) {
2992 scroll_steps = view->lines - view->offset - 1;
2993 if (scroll_steps >= view->height)
2994 scroll_steps -= view->height - 1;
2999 if (!view_is_displayed(view)) {
3000 view->offset += scroll_steps;
3001 assert(0 <= view->offset && view->offset < view->lines);
3002 view->ops->select(view, &view->line[view->lineno]);
3003 return;
3006 /* Repaint the old "current" line if we be scrolling */
3007 if (ABS(steps) < view->height)
3008 draw_view_line(view, view->lineno - steps - view->offset);
3010 if (scroll_steps) {
3011 do_scroll_view(view, scroll_steps);
3012 return;
3015 /* Draw the current line */
3016 draw_view_line(view, view->lineno - view->offset);
3018 wnoutrefresh(view->win);
3019 report("");
3024 * Searching
3027 static void search_view(struct view *view, enum request request);
3029 static bool
3030 grep_text(struct view *view, const char *text[])
3032 regmatch_t pmatch;
3033 size_t i;
3035 for (i = 0; text[i]; i++)
3036 if (*text[i] &&
3037 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3038 return TRUE;
3039 return FALSE;
3042 static void
3043 select_view_line(struct view *view, unsigned long lineno)
3045 unsigned long old_lineno = view->lineno;
3046 unsigned long old_offset = view->offset;
3048 if (goto_view_line(view, view->offset, lineno)) {
3049 if (view_is_displayed(view)) {
3050 if (old_offset != view->offset) {
3051 redraw_view(view);
3052 } else {
3053 draw_view_line(view, old_lineno - view->offset);
3054 draw_view_line(view, view->lineno - view->offset);
3055 wnoutrefresh(view->win);
3057 } else {
3058 view->ops->select(view, &view->line[view->lineno]);
3063 static void
3064 find_next(struct view *view, enum request request)
3066 unsigned long lineno = view->lineno;
3067 int direction;
3069 if (!*view->grep) {
3070 if (!*opt_search)
3071 report("No previous search");
3072 else
3073 search_view(view, request);
3074 return;
3077 switch (request) {
3078 case REQ_SEARCH:
3079 case REQ_FIND_NEXT:
3080 direction = 1;
3081 break;
3083 case REQ_SEARCH_BACK:
3084 case REQ_FIND_PREV:
3085 direction = -1;
3086 break;
3088 default:
3089 return;
3092 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3093 lineno += direction;
3095 /* Note, lineno is unsigned long so will wrap around in which case it
3096 * will become bigger than view->lines. */
3097 for (; lineno < view->lines; lineno += direction) {
3098 if (view->ops->grep(view, &view->line[lineno])) {
3099 select_view_line(view, lineno);
3100 report("Line %ld matches '%s'", lineno + 1, view->grep);
3101 return;
3105 report("No match found for '%s'", view->grep);
3108 static void
3109 search_view(struct view *view, enum request request)
3111 int regex_err;
3113 if (view->regex) {
3114 regfree(view->regex);
3115 *view->grep = 0;
3116 } else {
3117 view->regex = calloc(1, sizeof(*view->regex));
3118 if (!view->regex)
3119 return;
3122 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3123 if (regex_err != 0) {
3124 char buf[SIZEOF_STR] = "unknown error";
3126 regerror(regex_err, view->regex, buf, sizeof(buf));
3127 report("Search failed: %s", buf);
3128 return;
3131 string_copy(view->grep, opt_search);
3133 find_next(view, request);
3137 * Incremental updating
3140 static void
3141 reset_view(struct view *view)
3143 int i;
3145 for (i = 0; i < view->lines; i++)
3146 free(view->line[i].data);
3147 free(view->line);
3149 view->p_offset = view->offset;
3150 view->p_yoffset = view->yoffset;
3151 view->p_lineno = view->lineno;
3153 view->line = NULL;
3154 view->offset = 0;
3155 view->yoffset = 0;
3156 view->lines = 0;
3157 view->lineno = 0;
3158 view->vid[0] = 0;
3159 view->update_secs = 0;
3162 static const char *
3163 format_arg(const char *name)
3165 static struct {
3166 const char *name;
3167 size_t namelen;
3168 const char *value;
3169 const char *value_if_empty;
3170 } vars[] = {
3171 #define FORMAT_VAR(name, value, value_if_empty) \
3172 { name, STRING_SIZE(name), value, value_if_empty }
3173 FORMAT_VAR("%(directory)", opt_path, ""),
3174 FORMAT_VAR("%(file)", opt_file, ""),
3175 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3176 FORMAT_VAR("%(head)", ref_head, ""),
3177 FORMAT_VAR("%(commit)", ref_commit, ""),
3178 FORMAT_VAR("%(blob)", ref_blob, ""),
3179 FORMAT_VAR("%(branch)", ref_branch, ""),
3181 int i;
3183 for (i = 0; i < ARRAY_SIZE(vars); i++)
3184 if (!strncmp(name, vars[i].name, vars[i].namelen))
3185 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3187 report("Unknown replacement: `%s`", name);
3188 return NULL;
3191 static bool
3192 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3194 char buf[SIZEOF_STR];
3195 int argc;
3197 argv_free(*dst_argv);
3199 for (argc = 0; src_argv[argc]; argc++) {
3200 const char *arg = src_argv[argc];
3201 size_t bufpos = 0;
3203 if (!strcmp(arg, "%(file-args)")) {
3204 if (!argv_append_array(dst_argv, opt_file_args))
3205 break;
3206 continue;
3208 } else if (!strcmp(arg, "%(diff-args)")) {
3209 if (!argv_append_array(dst_argv, opt_diff_args))
3210 break;
3211 continue;
3213 } else if (!strcmp(arg, "%(rev-args)")) {
3214 if (!argv_append_array(dst_argv, opt_rev_args))
3215 break;
3216 continue;
3219 while (arg) {
3220 char *next = strstr(arg, "%(");
3221 int len = next - arg;
3222 const char *value;
3224 if (!next || !replace) {
3225 len = strlen(arg);
3226 value = "";
3228 } else {
3229 value = format_arg(next);
3231 if (!value) {
3232 return FALSE;
3236 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3237 return FALSE;
3239 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3242 if (!argv_append(dst_argv, buf))
3243 break;
3246 return src_argv[argc] == NULL;
3249 static bool
3250 restore_view_position(struct view *view)
3252 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3253 return FALSE;
3255 /* Changing the view position cancels the restoring. */
3256 /* FIXME: Changing back to the first line is not detected. */
3257 if (view->offset != 0 || view->lineno != 0) {
3258 view->p_restore = FALSE;
3259 return FALSE;
3262 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3263 view_is_displayed(view))
3264 werase(view->win);
3266 view->yoffset = view->p_yoffset;
3267 view->p_restore = FALSE;
3269 return TRUE;
3272 static void
3273 end_update(struct view *view, bool force)
3275 if (!view->pipe)
3276 return;
3277 while (!view->ops->read(view, NULL))
3278 if (!force)
3279 return;
3280 if (force)
3281 io_kill(view->pipe);
3282 io_done(view->pipe);
3283 view->pipe = NULL;
3286 static void
3287 setup_update(struct view *view, const char *vid)
3289 reset_view(view);
3290 string_copy_rev(view->vid, vid);
3291 view->pipe = &view->io;
3292 view->start_time = time(NULL);
3295 static bool
3296 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3298 view->dir = dir;
3299 return format_argv(&view->argv, argv, replace);
3302 static bool
3303 prepare_update(struct view *view, const char *argv[], const char *dir)
3305 if (view->pipe)
3306 end_update(view, TRUE);
3307 return prepare_io(view, dir, argv, FALSE);
3310 static bool
3311 start_update(struct view *view, const char **argv, const char *dir)
3313 if (view->pipe)
3314 io_done(view->pipe);
3315 return prepare_io(view, dir, argv, FALSE) &&
3316 io_run(&view->io, IO_RD, dir, view->argv);
3319 static bool
3320 prepare_update_file(struct view *view, const char *name)
3322 if (view->pipe)
3323 end_update(view, TRUE);
3324 argv_free(view->argv);
3325 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3328 static bool
3329 begin_update(struct view *view, bool refresh)
3331 if (view->pipe)
3332 end_update(view, TRUE);
3334 if (!refresh) {
3335 if (view->ops->prepare) {
3336 if (!view->ops->prepare(view))
3337 return FALSE;
3338 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3339 return FALSE;
3342 /* Put the current ref_* value to the view title ref
3343 * member. This is needed by the blob view. Most other
3344 * views sets it automatically after loading because the
3345 * first line is a commit line. */
3346 string_copy_rev(view->ref, view->id);
3349 if (view->argv && view->argv[0] &&
3350 !io_run(&view->io, IO_RD, view->dir, view->argv))
3351 return FALSE;
3353 setup_update(view, view->id);
3355 return TRUE;
3358 static bool
3359 update_view(struct view *view)
3361 char out_buffer[BUFSIZ * 2];
3362 char *line;
3363 /* Clear the view and redraw everything since the tree sorting
3364 * might have rearranged things. */
3365 bool redraw = view->lines == 0;
3366 bool can_read = TRUE;
3368 if (!view->pipe)
3369 return TRUE;
3371 if (!io_can_read(view->pipe)) {
3372 if (view->lines == 0 && view_is_displayed(view)) {
3373 time_t secs = time(NULL) - view->start_time;
3375 if (secs > 1 && secs > view->update_secs) {
3376 if (view->update_secs == 0)
3377 redraw_view(view);
3378 update_view_title(view);
3379 view->update_secs = secs;
3382 return TRUE;
3385 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3386 if (opt_iconv_in != ICONV_NONE) {
3387 ICONV_CONST char *inbuf = line;
3388 size_t inlen = strlen(line) + 1;
3390 char *outbuf = out_buffer;
3391 size_t outlen = sizeof(out_buffer);
3393 size_t ret;
3395 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3396 if (ret != (size_t) -1)
3397 line = out_buffer;
3400 if (!view->ops->read(view, line)) {
3401 report("Allocation failure");
3402 end_update(view, TRUE);
3403 return FALSE;
3408 unsigned long lines = view->lines;
3409 int digits;
3411 for (digits = 0; lines; digits++)
3412 lines /= 10;
3414 /* Keep the displayed view in sync with line number scaling. */
3415 if (digits != view->digits) {
3416 view->digits = digits;
3417 if (opt_line_number || view->type == VIEW_BLAME)
3418 redraw = TRUE;
3422 if (io_error(view->pipe)) {
3423 report("Failed to read: %s", io_strerror(view->pipe));
3424 end_update(view, TRUE);
3426 } else if (io_eof(view->pipe)) {
3427 if (view_is_displayed(view))
3428 report("");
3429 end_update(view, FALSE);
3432 if (restore_view_position(view))
3433 redraw = TRUE;
3435 if (!view_is_displayed(view))
3436 return TRUE;
3438 if (redraw)
3439 redraw_view_from(view, 0);
3440 else
3441 redraw_view_dirty(view);
3443 /* Update the title _after_ the redraw so that if the redraw picks up a
3444 * commit reference in view->ref it'll be available here. */
3445 update_view_title(view);
3446 return TRUE;
3449 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3451 static struct line *
3452 add_line_data(struct view *view, void *data, enum line_type type)
3454 struct line *line;
3456 if (!realloc_lines(&view->line, view->lines, 1))
3457 return NULL;
3459 line = &view->line[view->lines++];
3460 memset(line, 0, sizeof(*line));
3461 line->type = type;
3462 line->data = data;
3463 line->dirty = 1;
3465 return line;
3468 static struct line *
3469 add_line_text(struct view *view, const char *text, enum line_type type)
3471 char *data = text ? strdup(text) : NULL;
3473 return data ? add_line_data(view, data, type) : NULL;
3476 static struct line *
3477 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3479 char buf[SIZEOF_STR];
3480 va_list args;
3482 va_start(args, fmt);
3483 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3484 buf[0] = 0;
3485 va_end(args);
3487 return buf[0] ? add_line_text(view, buf, type) : NULL;
3491 * View opening
3494 enum open_flags {
3495 OPEN_DEFAULT = 0, /* Use default view switching. */
3496 OPEN_SPLIT = 1, /* Split current view. */
3497 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3498 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3499 OPEN_PREPARED = 32, /* Open already prepared command. */
3502 static void
3503 open_view(struct view *prev, enum request request, enum open_flags flags)
3505 bool split = !!(flags & OPEN_SPLIT);
3506 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3507 bool nomaximize = !!(flags & OPEN_REFRESH);
3508 struct view *view = VIEW(request);
3509 int nviews = displayed_views();
3510 struct view *base_view = display[0];
3512 if (view == prev && nviews == 1 && !reload) {
3513 report("Already in %s view", view->name);
3514 return;
3517 if (view->git_dir && !opt_git_dir[0]) {
3518 report("The %s view is disabled in pager view", view->name);
3519 return;
3522 if (split) {
3523 display[1] = view;
3524 current_view = 1;
3525 view->parent = prev;
3526 } else if (!nomaximize) {
3527 /* Maximize the current view. */
3528 memset(display, 0, sizeof(display));
3529 current_view = 0;
3530 display[current_view] = view;
3533 /* No prev signals that this is the first loaded view. */
3534 if (prev && view != prev) {
3535 view->prev = prev;
3538 /* Resize the view when switching between split- and full-screen,
3539 * or when switching between two different full-screen views. */
3540 if (nviews != displayed_views() ||
3541 (nviews == 1 && base_view != display[0]))
3542 resize_display();
3544 if (view->ops->open) {
3545 if (view->pipe)
3546 end_update(view, TRUE);
3547 if (!view->ops->open(view)) {
3548 report("Failed to load %s view", view->name);
3549 return;
3551 restore_view_position(view);
3553 } else if ((reload || strcmp(view->vid, view->id)) &&
3554 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3555 report("Failed to load %s view", view->name);
3556 return;
3559 if (split && prev->lineno - prev->offset >= prev->height) {
3560 /* Take the title line into account. */
3561 int lines = prev->lineno - prev->offset - prev->height + 1;
3563 /* Scroll the view that was split if the current line is
3564 * outside the new limited view. */
3565 do_scroll_view(prev, lines);
3568 if (prev && view != prev && split && view_is_displayed(prev)) {
3569 /* "Blur" the previous view. */
3570 update_view_title(prev);
3573 if (view->pipe && view->lines == 0) {
3574 /* Clear the old view and let the incremental updating refill
3575 * the screen. */
3576 werase(view->win);
3577 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3578 report("");
3579 } else if (view_is_displayed(view)) {
3580 redraw_view(view);
3581 report("");
3585 static void
3586 open_external_viewer(const char *argv[], const char *dir)
3588 def_prog_mode(); /* save current tty modes */
3589 endwin(); /* restore original tty modes */
3590 io_run_fg(argv, dir);
3591 fprintf(stderr, "Press Enter to continue");
3592 getc(opt_tty);
3593 reset_prog_mode();
3594 redraw_display(TRUE);
3597 static void
3598 open_mergetool(const char *file)
3600 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3602 open_external_viewer(mergetool_argv, opt_cdup);
3605 static void
3606 open_editor(const char *file)
3608 const char *editor_argv[] = { "vi", file, NULL };
3609 const char *editor;
3611 editor = getenv("GIT_EDITOR");
3612 if (!editor && *opt_editor)
3613 editor = opt_editor;
3614 if (!editor)
3615 editor = getenv("VISUAL");
3616 if (!editor)
3617 editor = getenv("EDITOR");
3618 if (!editor)
3619 editor = "vi";
3621 editor_argv[0] = editor;
3622 open_external_viewer(editor_argv, opt_cdup);
3625 static void
3626 open_run_request(enum request request)
3628 struct run_request *req = get_run_request(request);
3629 const char **argv = NULL;
3631 if (!req) {
3632 report("Unknown run request");
3633 return;
3636 if (format_argv(&argv, req->argv, TRUE))
3637 open_external_viewer(argv, NULL);
3638 if (argv)
3639 argv_free(argv);
3640 free(argv);
3644 * User request switch noodle
3647 static int
3648 view_driver(struct view *view, enum request request)
3650 int i;
3652 if (request == REQ_NONE)
3653 return TRUE;
3655 if (request > REQ_NONE) {
3656 open_run_request(request);
3657 view_request(view, REQ_REFRESH);
3658 return TRUE;
3661 request = view_request(view, request);
3662 if (request == REQ_NONE)
3663 return TRUE;
3665 switch (request) {
3666 case REQ_MOVE_UP:
3667 case REQ_MOVE_DOWN:
3668 case REQ_MOVE_PAGE_UP:
3669 case REQ_MOVE_PAGE_DOWN:
3670 case REQ_MOVE_FIRST_LINE:
3671 case REQ_MOVE_LAST_LINE:
3672 move_view(view, request);
3673 break;
3675 case REQ_SCROLL_LEFT:
3676 case REQ_SCROLL_RIGHT:
3677 case REQ_SCROLL_LINE_DOWN:
3678 case REQ_SCROLL_LINE_UP:
3679 case REQ_SCROLL_PAGE_DOWN:
3680 case REQ_SCROLL_PAGE_UP:
3681 scroll_view(view, request);
3682 break;
3684 case REQ_VIEW_BLAME:
3685 if (!opt_file[0]) {
3686 report("No file chosen, press %s to open tree view",
3687 get_key(view->keymap, REQ_VIEW_TREE));
3688 break;
3690 open_view(view, request, OPEN_DEFAULT);
3691 break;
3693 case REQ_VIEW_BLOB:
3694 if (!ref_blob[0]) {
3695 report("No file chosen, press %s to open tree view",
3696 get_key(view->keymap, REQ_VIEW_TREE));
3697 break;
3699 open_view(view, request, OPEN_DEFAULT);
3700 break;
3702 case REQ_VIEW_PAGER:
3703 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3704 report("No pager content, press %s to run command from prompt",
3705 get_key(view->keymap, REQ_PROMPT));
3706 break;
3708 open_view(view, request, OPEN_DEFAULT);
3709 break;
3711 case REQ_VIEW_STAGE:
3712 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3713 report("No stage content, press %s to open the status view and choose file",
3714 get_key(view->keymap, REQ_VIEW_STATUS));
3715 break;
3717 open_view(view, request, OPEN_DEFAULT);
3718 break;
3720 case REQ_VIEW_STATUS:
3721 if (opt_is_inside_work_tree == FALSE) {
3722 report("The status view requires a working tree");
3723 break;
3725 open_view(view, request, OPEN_DEFAULT);
3726 break;
3728 case REQ_VIEW_MAIN:
3729 case REQ_VIEW_DIFF:
3730 case REQ_VIEW_LOG:
3731 case REQ_VIEW_TREE:
3732 case REQ_VIEW_HELP:
3733 case REQ_VIEW_BRANCH:
3734 open_view(view, request, OPEN_DEFAULT);
3735 break;
3737 case REQ_NEXT:
3738 case REQ_PREVIOUS:
3739 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3741 if (view->parent) {
3742 int line;
3744 view = view->parent;
3745 line = view->lineno;
3746 move_view(view, request);
3747 if (view_is_displayed(view))
3748 update_view_title(view);
3749 if (line != view->lineno)
3750 view_request(view, REQ_ENTER);
3751 } else {
3752 move_view(view, request);
3754 break;
3756 case REQ_VIEW_NEXT:
3758 int nviews = displayed_views();
3759 int next_view = (current_view + 1) % nviews;
3761 if (next_view == current_view) {
3762 report("Only one view is displayed");
3763 break;
3766 current_view = next_view;
3767 /* Blur out the title of the previous view. */
3768 update_view_title(view);
3769 report("");
3770 break;
3772 case REQ_REFRESH:
3773 report("Refreshing is not yet supported for the %s view", view->name);
3774 break;
3776 case REQ_MAXIMIZE:
3777 if (displayed_views() == 2)
3778 maximize_view(view);
3779 break;
3781 case REQ_OPTIONS:
3782 open_option_menu();
3783 break;
3785 case REQ_TOGGLE_LINENO:
3786 toggle_view_option(&opt_line_number, "line numbers");
3787 break;
3789 case REQ_TOGGLE_DATE:
3790 toggle_date();
3791 break;
3793 case REQ_TOGGLE_AUTHOR:
3794 toggle_author();
3795 break;
3797 case REQ_TOGGLE_REV_GRAPH:
3798 toggle_view_option(&opt_rev_graph, "revision graph display");
3799 break;
3801 case REQ_TOGGLE_REFS:
3802 toggle_view_option(&opt_show_refs, "reference display");
3803 break;
3805 case REQ_TOGGLE_SORT_FIELD:
3806 case REQ_TOGGLE_SORT_ORDER:
3807 report("Sorting is not yet supported for the %s view", view->name);
3808 break;
3810 case REQ_SEARCH:
3811 case REQ_SEARCH_BACK:
3812 search_view(view, request);
3813 break;
3815 case REQ_FIND_NEXT:
3816 case REQ_FIND_PREV:
3817 find_next(view, request);
3818 break;
3820 case REQ_STOP_LOADING:
3821 foreach_view(view, i) {
3822 if (view->pipe)
3823 report("Stopped loading the %s view", view->name),
3824 end_update(view, TRUE);
3826 break;
3828 case REQ_SHOW_VERSION:
3829 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3830 return TRUE;
3832 case REQ_SCREEN_REDRAW:
3833 redraw_display(TRUE);
3834 break;
3836 case REQ_EDIT:
3837 report("Nothing to edit");
3838 break;
3840 case REQ_ENTER:
3841 report("Nothing to enter");
3842 break;
3844 case REQ_VIEW_CLOSE:
3845 /* XXX: Mark closed views by letting view->prev point to the
3846 * view itself. Parents to closed view should never be
3847 * followed. */
3848 if (view->prev && view->prev != view) {
3849 maximize_view(view->prev);
3850 view->prev = view;
3851 break;
3853 /* Fall-through */
3854 case REQ_QUIT:
3855 return FALSE;
3857 default:
3858 report("Unknown key, press %s for help",
3859 get_key(view->keymap, REQ_VIEW_HELP));
3860 return TRUE;
3863 return TRUE;
3868 * View backend utilities
3871 enum sort_field {
3872 ORDERBY_NAME,
3873 ORDERBY_DATE,
3874 ORDERBY_AUTHOR,
3877 struct sort_state {
3878 const enum sort_field *fields;
3879 size_t size, current;
3880 bool reverse;
3883 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3884 #define get_sort_field(state) ((state).fields[(state).current])
3885 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3887 static void
3888 sort_view(struct view *view, enum request request, struct sort_state *state,
3889 int (*compare)(const void *, const void *))
3891 switch (request) {
3892 case REQ_TOGGLE_SORT_FIELD:
3893 state->current = (state->current + 1) % state->size;
3894 break;
3896 case REQ_TOGGLE_SORT_ORDER:
3897 state->reverse = !state->reverse;
3898 break;
3899 default:
3900 die("Not a sort request");
3903 qsort(view->line, view->lines, sizeof(*view->line), compare);
3904 redraw_view(view);
3907 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3909 /* Small author cache to reduce memory consumption. It uses binary
3910 * search to lookup or find place to position new entries. No entries
3911 * are ever freed. */
3912 static const char *
3913 get_author(const char *name)
3915 static const char **authors;
3916 static size_t authors_size;
3917 int from = 0, to = authors_size - 1;
3919 while (from <= to) {
3920 size_t pos = (to + from) / 2;
3921 int cmp = strcmp(name, authors[pos]);
3923 if (!cmp)
3924 return authors[pos];
3926 if (cmp < 0)
3927 to = pos - 1;
3928 else
3929 from = pos + 1;
3932 if (!realloc_authors(&authors, authors_size, 1))
3933 return NULL;
3934 name = strdup(name);
3935 if (!name)
3936 return NULL;
3938 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3939 authors[from] = name;
3940 authors_size++;
3942 return name;
3945 static void
3946 parse_timesec(struct time *time, const char *sec)
3948 time->sec = (time_t) atol(sec);
3951 static void
3952 parse_timezone(struct time *time, const char *zone)
3954 long tz;
3956 tz = ('0' - zone[1]) * 60 * 60 * 10;
3957 tz += ('0' - zone[2]) * 60 * 60;
3958 tz += ('0' - zone[3]) * 60 * 10;
3959 tz += ('0' - zone[4]) * 60;
3961 if (zone[0] == '-')
3962 tz = -tz;
3964 time->tz = tz;
3965 time->sec -= tz;
3968 /* Parse author lines where the name may be empty:
3969 * author <email@address.tld> 1138474660 +0100
3971 static void
3972 parse_author_line(char *ident, const char **author, struct time *time)
3974 char *nameend = strchr(ident, '<');
3975 char *emailend = strchr(ident, '>');
3977 if (nameend && emailend)
3978 *nameend = *emailend = 0;
3979 ident = chomp_string(ident);
3980 if (!*ident) {
3981 if (nameend)
3982 ident = chomp_string(nameend + 1);
3983 if (!*ident)
3984 ident = "Unknown";
3987 *author = get_author(ident);
3989 /* Parse epoch and timezone */
3990 if (emailend && emailend[1] == ' ') {
3991 char *secs = emailend + 2;
3992 char *zone = strchr(secs, ' ');
3994 parse_timesec(time, secs);
3996 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3997 parse_timezone(time, zone + 1);
4002 * Pager backend
4005 static bool
4006 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4008 if (opt_line_number && draw_lineno(view, lineno))
4009 return TRUE;
4011 draw_text(view, line->type, line->data, TRUE);
4012 return TRUE;
4015 static bool
4016 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4018 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4019 char ref[SIZEOF_STR];
4021 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4022 return TRUE;
4024 /* This is the only fatal call, since it can "corrupt" the buffer. */
4025 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4026 return FALSE;
4028 return TRUE;
4031 static void
4032 add_pager_refs(struct view *view, struct line *line)
4034 char buf[SIZEOF_STR];
4035 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4036 struct ref_list *list;
4037 size_t bufpos = 0, i;
4038 const char *sep = "Refs: ";
4039 bool is_tag = FALSE;
4041 assert(line->type == LINE_COMMIT);
4043 list = get_ref_list(commit_id);
4044 if (!list) {
4045 if (view->type == VIEW_DIFF)
4046 goto try_add_describe_ref;
4047 return;
4050 for (i = 0; i < list->size; i++) {
4051 struct ref *ref = list->refs[i];
4052 const char *fmt = ref->tag ? "%s[%s]" :
4053 ref->remote ? "%s<%s>" : "%s%s";
4055 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4056 return;
4057 sep = ", ";
4058 if (ref->tag)
4059 is_tag = TRUE;
4062 if (!is_tag && view->type == VIEW_DIFF) {
4063 try_add_describe_ref:
4064 /* Add <tag>-g<commit_id> "fake" reference. */
4065 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4066 return;
4069 if (bufpos == 0)
4070 return;
4072 add_line_text(view, buf, LINE_PP_REFS);
4075 static bool
4076 pager_read(struct view *view, char *data)
4078 struct line *line;
4080 if (!data)
4081 return TRUE;
4083 line = add_line_text(view, data, get_line_type(data));
4084 if (!line)
4085 return FALSE;
4087 if (line->type == LINE_COMMIT &&
4088 (view->type == VIEW_DIFF ||
4089 view->type == VIEW_LOG))
4090 add_pager_refs(view, line);
4092 return TRUE;
4095 static enum request
4096 pager_request(struct view *view, enum request request, struct line *line)
4098 int split = 0;
4100 if (request != REQ_ENTER)
4101 return request;
4103 if (line->type == LINE_COMMIT &&
4104 (view->type == VIEW_LOG ||
4105 view->type == VIEW_PAGER)) {
4106 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4107 split = 1;
4110 /* Always scroll the view even if it was split. That way
4111 * you can use Enter to scroll through the log view and
4112 * split open each commit diff. */
4113 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4115 /* FIXME: A minor workaround. Scrolling the view will call report("")
4116 * but if we are scrolling a non-current view this won't properly
4117 * update the view title. */
4118 if (split)
4119 update_view_title(view);
4121 return REQ_NONE;
4124 static bool
4125 pager_grep(struct view *view, struct line *line)
4127 const char *text[] = { line->data, NULL };
4129 return grep_text(view, text);
4132 static void
4133 pager_select(struct view *view, struct line *line)
4135 if (line->type == LINE_COMMIT) {
4136 char *text = (char *)line->data + STRING_SIZE("commit ");
4138 if (view->type != VIEW_PAGER)
4139 string_copy_rev(view->ref, text);
4140 string_copy_rev(ref_commit, text);
4144 static struct view_ops pager_ops = {
4145 "line",
4146 NULL,
4147 NULL,
4148 pager_read,
4149 pager_draw,
4150 pager_request,
4151 pager_grep,
4152 pager_select,
4155 static const char *log_argv[SIZEOF_ARG] = {
4156 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4159 static enum request
4160 log_request(struct view *view, enum request request, struct line *line)
4162 switch (request) {
4163 case REQ_REFRESH:
4164 load_refs();
4165 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4166 return REQ_NONE;
4167 default:
4168 return pager_request(view, request, line);
4172 static struct view_ops log_ops = {
4173 "line",
4174 log_argv,
4175 NULL,
4176 pager_read,
4177 pager_draw,
4178 log_request,
4179 pager_grep,
4180 pager_select,
4183 static const char *diff_argv[SIZEOF_ARG] = {
4184 "git", "show", "--pretty=fuller", "--no-color", "--root",
4185 "--patch-with-stat", "--find-copies-harder", "-C",
4186 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4189 static struct view_ops diff_ops = {
4190 "line",
4191 diff_argv,
4192 NULL,
4193 pager_read,
4194 pager_draw,
4195 pager_request,
4196 pager_grep,
4197 pager_select,
4201 * Help backend
4204 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4206 static bool
4207 help_open_keymap_title(struct view *view, enum keymap keymap)
4209 struct line *line;
4211 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4212 help_keymap_hidden[keymap] ? '+' : '-',
4213 enum_name(keymap_table[keymap]));
4214 if (line)
4215 line->other = keymap;
4217 return help_keymap_hidden[keymap];
4220 static void
4221 help_open_keymap(struct view *view, enum keymap keymap)
4223 const char *group = NULL;
4224 char buf[SIZEOF_STR];
4225 size_t bufpos;
4226 bool add_title = TRUE;
4227 int i;
4229 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4230 const char *key = NULL;
4232 if (req_info[i].request == REQ_NONE)
4233 continue;
4235 if (!req_info[i].request) {
4236 group = req_info[i].help;
4237 continue;
4240 key = get_keys(keymap, req_info[i].request, TRUE);
4241 if (!key || !*key)
4242 continue;
4244 if (add_title && help_open_keymap_title(view, keymap))
4245 return;
4246 add_title = FALSE;
4248 if (group) {
4249 add_line_text(view, group, LINE_HELP_GROUP);
4250 group = NULL;
4253 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4254 enum_name(req_info[i]), req_info[i].help);
4257 group = "External commands:";
4259 for (i = 0; i < run_requests; i++) {
4260 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4261 const char *key;
4262 int argc;
4264 if (!req || req->keymap != keymap)
4265 continue;
4267 key = get_key_name(req->key);
4268 if (!*key)
4269 key = "(no key defined)";
4271 if (add_title && help_open_keymap_title(view, keymap))
4272 return;
4273 if (group) {
4274 add_line_text(view, group, LINE_HELP_GROUP);
4275 group = NULL;
4278 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4279 if (!string_format_from(buf, &bufpos, "%s%s",
4280 argc ? " " : "", req->argv[argc]))
4281 return;
4283 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4287 static bool
4288 help_open(struct view *view)
4290 enum keymap keymap;
4292 reset_view(view);
4293 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4294 add_line_text(view, "", LINE_DEFAULT);
4296 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4297 help_open_keymap(view, keymap);
4299 return TRUE;
4302 static enum request
4303 help_request(struct view *view, enum request request, struct line *line)
4305 switch (request) {
4306 case REQ_ENTER:
4307 if (line->type == LINE_HELP_KEYMAP) {
4308 help_keymap_hidden[line->other] =
4309 !help_keymap_hidden[line->other];
4310 view->p_restore = TRUE;
4311 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4314 return REQ_NONE;
4315 default:
4316 return pager_request(view, request, line);
4320 static struct view_ops help_ops = {
4321 "line",
4322 NULL,
4323 help_open,
4324 NULL,
4325 pager_draw,
4326 help_request,
4327 pager_grep,
4328 pager_select,
4333 * Tree backend
4336 struct tree_stack_entry {
4337 struct tree_stack_entry *prev; /* Entry below this in the stack */
4338 unsigned long lineno; /* Line number to restore */
4339 char *name; /* Position of name in opt_path */
4342 /* The top of the path stack. */
4343 static struct tree_stack_entry *tree_stack = NULL;
4344 unsigned long tree_lineno = 0;
4346 static void
4347 pop_tree_stack_entry(void)
4349 struct tree_stack_entry *entry = tree_stack;
4351 tree_lineno = entry->lineno;
4352 entry->name[0] = 0;
4353 tree_stack = entry->prev;
4354 free(entry);
4357 static void
4358 push_tree_stack_entry(const char *name, unsigned long lineno)
4360 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4361 size_t pathlen = strlen(opt_path);
4363 if (!entry)
4364 return;
4366 entry->prev = tree_stack;
4367 entry->name = opt_path + pathlen;
4368 tree_stack = entry;
4370 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4371 pop_tree_stack_entry();
4372 return;
4375 /* Move the current line to the first tree entry. */
4376 tree_lineno = 1;
4377 entry->lineno = lineno;
4380 /* Parse output from git-ls-tree(1):
4382 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4385 #define SIZEOF_TREE_ATTR \
4386 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4388 #define SIZEOF_TREE_MODE \
4389 STRING_SIZE("100644 ")
4391 #define TREE_ID_OFFSET \
4392 STRING_SIZE("100644 blob ")
4394 struct tree_entry {
4395 char id[SIZEOF_REV];
4396 mode_t mode;
4397 struct time time; /* Date from the author ident. */
4398 const char *author; /* Author of the commit. */
4399 char name[1];
4402 static const char *
4403 tree_path(const struct line *line)
4405 return ((struct tree_entry *) line->data)->name;
4408 static int
4409 tree_compare_entry(const struct line *line1, const struct line *line2)
4411 if (line1->type != line2->type)
4412 return line1->type == LINE_TREE_DIR ? -1 : 1;
4413 return strcmp(tree_path(line1), tree_path(line2));
4416 static const enum sort_field tree_sort_fields[] = {
4417 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4419 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4421 static int
4422 tree_compare(const void *l1, const void *l2)
4424 const struct line *line1 = (const struct line *) l1;
4425 const struct line *line2 = (const struct line *) l2;
4426 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4427 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4429 if (line1->type == LINE_TREE_HEAD)
4430 return -1;
4431 if (line2->type == LINE_TREE_HEAD)
4432 return 1;
4434 switch (get_sort_field(tree_sort_state)) {
4435 case ORDERBY_DATE:
4436 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4438 case ORDERBY_AUTHOR:
4439 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4441 case ORDERBY_NAME:
4442 default:
4443 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4448 static struct line *
4449 tree_entry(struct view *view, enum line_type type, const char *path,
4450 const char *mode, const char *id)
4452 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4453 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4455 if (!entry || !line) {
4456 free(entry);
4457 return NULL;
4460 strncpy(entry->name, path, strlen(path));
4461 if (mode)
4462 entry->mode = strtoul(mode, NULL, 8);
4463 if (id)
4464 string_copy_rev(entry->id, id);
4466 return line;
4469 static bool
4470 tree_read_date(struct view *view, char *text, bool *read_date)
4472 static const char *author_name;
4473 static struct time author_time;
4475 if (!text && *read_date) {
4476 *read_date = FALSE;
4477 return TRUE;
4479 } else if (!text) {
4480 char *path = *opt_path ? opt_path : ".";
4481 /* Find next entry to process */
4482 const char *log_file[] = {
4483 "git", "log", "--no-color", "--pretty=raw",
4484 "--cc", "--raw", view->id, "--", path, NULL
4487 if (!view->lines) {
4488 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4489 report("Tree is empty");
4490 return TRUE;
4493 if (!start_update(view, log_file, opt_cdup)) {
4494 report("Failed to load tree data");
4495 return TRUE;
4498 *read_date = TRUE;
4499 return FALSE;
4501 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4502 parse_author_line(text + STRING_SIZE("author "),
4503 &author_name, &author_time);
4505 } else if (*text == ':') {
4506 char *pos;
4507 size_t annotated = 1;
4508 size_t i;
4510 pos = strchr(text, '\t');
4511 if (!pos)
4512 return TRUE;
4513 text = pos + 1;
4514 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4515 text += strlen(opt_path);
4516 pos = strchr(text, '/');
4517 if (pos)
4518 *pos = 0;
4520 for (i = 1; i < view->lines; i++) {
4521 struct line *line = &view->line[i];
4522 struct tree_entry *entry = line->data;
4524 annotated += !!entry->author;
4525 if (entry->author || strcmp(entry->name, text))
4526 continue;
4528 entry->author = author_name;
4529 entry->time = author_time;
4530 line->dirty = 1;
4531 break;
4534 if (annotated == view->lines)
4535 io_kill(view->pipe);
4537 return TRUE;
4540 static bool
4541 tree_read(struct view *view, char *text)
4543 static bool read_date = FALSE;
4544 struct tree_entry *data;
4545 struct line *entry, *line;
4546 enum line_type type;
4547 size_t textlen = text ? strlen(text) : 0;
4548 char *path = text + SIZEOF_TREE_ATTR;
4550 if (read_date || !text)
4551 return tree_read_date(view, text, &read_date);
4553 if (textlen <= SIZEOF_TREE_ATTR)
4554 return FALSE;
4555 if (view->lines == 0 &&
4556 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4557 return FALSE;
4559 /* Strip the path part ... */
4560 if (*opt_path) {
4561 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4562 size_t striplen = strlen(opt_path);
4564 if (pathlen > striplen)
4565 memmove(path, path + striplen,
4566 pathlen - striplen + 1);
4568 /* Insert "link" to parent directory. */
4569 if (view->lines == 1 &&
4570 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4571 return FALSE;
4574 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4575 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4576 if (!entry)
4577 return FALSE;
4578 data = entry->data;
4580 /* Skip "Directory ..." and ".." line. */
4581 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4582 if (tree_compare_entry(line, entry) <= 0)
4583 continue;
4585 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4587 line->data = data;
4588 line->type = type;
4589 for (; line <= entry; line++)
4590 line->dirty = line->cleareol = 1;
4591 return TRUE;
4594 if (tree_lineno > view->lineno) {
4595 view->lineno = tree_lineno;
4596 tree_lineno = 0;
4599 return TRUE;
4602 static bool
4603 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4605 struct tree_entry *entry = line->data;
4607 if (line->type == LINE_TREE_HEAD) {
4608 if (draw_text(view, line->type, "Directory path /", TRUE))
4609 return TRUE;
4610 } else {
4611 if (draw_mode(view, entry->mode))
4612 return TRUE;
4614 if (opt_author && draw_author(view, entry->author))
4615 return TRUE;
4617 if (opt_date && draw_date(view, &entry->time))
4618 return TRUE;
4620 if (draw_text(view, line->type, entry->name, TRUE))
4621 return TRUE;
4622 return TRUE;
4625 static void
4626 open_blob_editor(const char *id)
4628 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4629 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4630 int fd = mkstemp(file);
4632 if (fd == -1)
4633 report("Failed to create temporary file");
4634 else if (!io_run_append(blob_argv, fd))
4635 report("Failed to save blob data to file");
4636 else
4637 open_editor(file);
4638 if (fd != -1)
4639 unlink(file);
4642 static enum request
4643 tree_request(struct view *view, enum request request, struct line *line)
4645 enum open_flags flags;
4646 struct tree_entry *entry = line->data;
4648 switch (request) {
4649 case REQ_VIEW_BLAME:
4650 if (line->type != LINE_TREE_FILE) {
4651 report("Blame only supported for files");
4652 return REQ_NONE;
4655 string_copy(opt_ref, view->vid);
4656 return request;
4658 case REQ_EDIT:
4659 if (line->type != LINE_TREE_FILE) {
4660 report("Edit only supported for files");
4661 } else if (!is_head_commit(view->vid)) {
4662 open_blob_editor(entry->id);
4663 } else {
4664 open_editor(opt_file);
4666 return REQ_NONE;
4668 case REQ_TOGGLE_SORT_FIELD:
4669 case REQ_TOGGLE_SORT_ORDER:
4670 sort_view(view, request, &tree_sort_state, tree_compare);
4671 return REQ_NONE;
4673 case REQ_PARENT:
4674 if (!*opt_path) {
4675 /* quit view if at top of tree */
4676 return REQ_VIEW_CLOSE;
4678 /* fake 'cd ..' */
4679 line = &view->line[1];
4680 break;
4682 case REQ_ENTER:
4683 break;
4685 default:
4686 return request;
4689 /* Cleanup the stack if the tree view is at a different tree. */
4690 while (!*opt_path && tree_stack)
4691 pop_tree_stack_entry();
4693 switch (line->type) {
4694 case LINE_TREE_DIR:
4695 /* Depending on whether it is a subdirectory or parent link
4696 * mangle the path buffer. */
4697 if (line == &view->line[1] && *opt_path) {
4698 pop_tree_stack_entry();
4700 } else {
4701 const char *basename = tree_path(line);
4703 push_tree_stack_entry(basename, view->lineno);
4706 /* Trees and subtrees share the same ID, so they are not not
4707 * unique like blobs. */
4708 flags = OPEN_RELOAD;
4709 request = REQ_VIEW_TREE;
4710 break;
4712 case LINE_TREE_FILE:
4713 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4714 request = REQ_VIEW_BLOB;
4715 break;
4717 default:
4718 return REQ_NONE;
4721 open_view(view, request, flags);
4722 if (request == REQ_VIEW_TREE)
4723 view->lineno = tree_lineno;
4725 return REQ_NONE;
4728 static bool
4729 tree_grep(struct view *view, struct line *line)
4731 struct tree_entry *entry = line->data;
4732 const char *text[] = {
4733 entry->name,
4734 opt_author ? entry->author : "",
4735 mkdate(&entry->time, opt_date),
4736 NULL
4739 return grep_text(view, text);
4742 static void
4743 tree_select(struct view *view, struct line *line)
4745 struct tree_entry *entry = line->data;
4747 if (line->type == LINE_TREE_FILE) {
4748 string_copy_rev(ref_blob, entry->id);
4749 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4751 } else if (line->type != LINE_TREE_DIR) {
4752 return;
4755 string_copy_rev(view->ref, entry->id);
4758 static bool
4759 tree_prepare(struct view *view)
4761 if (view->lines == 0 && opt_prefix[0]) {
4762 char *pos = opt_prefix;
4764 while (pos && *pos) {
4765 char *end = strchr(pos, '/');
4767 if (end)
4768 *end = 0;
4769 push_tree_stack_entry(pos, 0);
4770 pos = end;
4771 if (end) {
4772 *end = '/';
4773 pos++;
4777 } else if (strcmp(view->vid, view->id)) {
4778 opt_path[0] = 0;
4781 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4784 static const char *tree_argv[SIZEOF_ARG] = {
4785 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4788 static struct view_ops tree_ops = {
4789 "file",
4790 tree_argv,
4791 NULL,
4792 tree_read,
4793 tree_draw,
4794 tree_request,
4795 tree_grep,
4796 tree_select,
4797 tree_prepare,
4800 static bool
4801 blob_read(struct view *view, char *line)
4803 if (!line)
4804 return TRUE;
4805 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4808 static enum request
4809 blob_request(struct view *view, enum request request, struct line *line)
4811 switch (request) {
4812 case REQ_EDIT:
4813 open_blob_editor(view->vid);
4814 return REQ_NONE;
4815 default:
4816 return pager_request(view, request, line);
4820 static const char *blob_argv[SIZEOF_ARG] = {
4821 "git", "cat-file", "blob", "%(blob)", NULL
4824 static struct view_ops blob_ops = {
4825 "line",
4826 blob_argv,
4827 NULL,
4828 blob_read,
4829 pager_draw,
4830 blob_request,
4831 pager_grep,
4832 pager_select,
4836 * Blame backend
4838 * Loading the blame view is a two phase job:
4840 * 1. File content is read either using opt_file from the
4841 * filesystem or using git-cat-file.
4842 * 2. Then blame information is incrementally added by
4843 * reading output from git-blame.
4846 struct blame_commit {
4847 char id[SIZEOF_REV]; /* SHA1 ID. */
4848 char title[128]; /* First line of the commit message. */
4849 const char *author; /* Author of the commit. */
4850 struct time time; /* Date from the author ident. */
4851 char filename[128]; /* Name of file. */
4852 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4853 char parent_filename[128]; /* Parent/previous name of file. */
4856 struct blame {
4857 struct blame_commit *commit;
4858 unsigned long lineno;
4859 char text[1];
4862 static bool
4863 blame_open(struct view *view)
4865 char path[SIZEOF_STR];
4866 size_t i;
4868 if (!view->prev && *opt_prefix) {
4869 string_copy(path, opt_file);
4870 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4871 return FALSE;
4874 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4875 const char *blame_cat_file_argv[] = {
4876 "git", "cat-file", "blob", path, NULL
4879 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4880 !start_update(view, blame_cat_file_argv, opt_cdup))
4881 return FALSE;
4884 /* First pass: remove multiple references to the same commit. */
4885 for (i = 0; i < view->lines; i++) {
4886 struct blame *blame = view->line[i].data;
4888 if (blame->commit && blame->commit->id[0])
4889 blame->commit->id[0] = 0;
4890 else
4891 blame->commit = NULL;
4894 /* Second pass: free existing references. */
4895 for (i = 0; i < view->lines; i++) {
4896 struct blame *blame = view->line[i].data;
4898 if (blame->commit)
4899 free(blame->commit);
4902 setup_update(view, opt_file);
4903 string_format(view->ref, "%s ...", opt_file);
4905 return TRUE;
4908 static struct blame_commit *
4909 get_blame_commit(struct view *view, const char *id)
4911 size_t i;
4913 for (i = 0; i < view->lines; i++) {
4914 struct blame *blame = view->line[i].data;
4916 if (!blame->commit)
4917 continue;
4919 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4920 return blame->commit;
4924 struct blame_commit *commit = calloc(1, sizeof(*commit));
4926 if (commit)
4927 string_ncopy(commit->id, id, SIZEOF_REV);
4928 return commit;
4932 static bool
4933 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4935 const char *pos = *posref;
4937 *posref = NULL;
4938 pos = strchr(pos + 1, ' ');
4939 if (!pos || !isdigit(pos[1]))
4940 return FALSE;
4941 *number = atoi(pos + 1);
4942 if (*number < min || *number > max)
4943 return FALSE;
4945 *posref = pos;
4946 return TRUE;
4949 static struct blame_commit *
4950 parse_blame_commit(struct view *view, const char *text, int *blamed)
4952 struct blame_commit *commit;
4953 struct blame *blame;
4954 const char *pos = text + SIZEOF_REV - 2;
4955 size_t orig_lineno = 0;
4956 size_t lineno;
4957 size_t group;
4959 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4960 return NULL;
4962 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4963 !parse_number(&pos, &lineno, 1, view->lines) ||
4964 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4965 return NULL;
4967 commit = get_blame_commit(view, text);
4968 if (!commit)
4969 return NULL;
4971 *blamed += group;
4972 while (group--) {
4973 struct line *line = &view->line[lineno + group - 1];
4975 blame = line->data;
4976 blame->commit = commit;
4977 blame->lineno = orig_lineno + group - 1;
4978 line->dirty = 1;
4981 return commit;
4984 static bool
4985 blame_read_file(struct view *view, const char *line, bool *read_file)
4987 if (!line) {
4988 const char *blame_argv[] = {
4989 "git", "blame", "--incremental",
4990 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4993 if (view->lines == 0 && !view->prev)
4994 die("No blame exist for %s", view->vid);
4996 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4997 report("Failed to load blame data");
4998 return TRUE;
5001 *read_file = FALSE;
5002 return FALSE;
5004 } else {
5005 size_t linelen = strlen(line);
5006 struct blame *blame = malloc(sizeof(*blame) + linelen);
5008 if (!blame)
5009 return FALSE;
5011 blame->commit = NULL;
5012 strncpy(blame->text, line, linelen);
5013 blame->text[linelen] = 0;
5014 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5018 static bool
5019 match_blame_header(const char *name, char **line)
5021 size_t namelen = strlen(name);
5022 bool matched = !strncmp(name, *line, namelen);
5024 if (matched)
5025 *line += namelen;
5027 return matched;
5030 static bool
5031 blame_read(struct view *view, char *line)
5033 static struct blame_commit *commit = NULL;
5034 static int blamed = 0;
5035 static bool read_file = TRUE;
5037 if (read_file)
5038 return blame_read_file(view, line, &read_file);
5040 if (!line) {
5041 /* Reset all! */
5042 commit = NULL;
5043 blamed = 0;
5044 read_file = TRUE;
5045 string_format(view->ref, "%s", view->vid);
5046 if (view_is_displayed(view)) {
5047 update_view_title(view);
5048 redraw_view_from(view, 0);
5050 return TRUE;
5053 if (!commit) {
5054 commit = parse_blame_commit(view, line, &blamed);
5055 string_format(view->ref, "%s %2d%%", view->vid,
5056 view->lines ? blamed * 100 / view->lines : 0);
5058 } else if (match_blame_header("author ", &line)) {
5059 commit->author = get_author(line);
5061 } else if (match_blame_header("author-time ", &line)) {
5062 parse_timesec(&commit->time, line);
5064 } else if (match_blame_header("author-tz ", &line)) {
5065 parse_timezone(&commit->time, line);
5067 } else if (match_blame_header("summary ", &line)) {
5068 string_ncopy(commit->title, line, strlen(line));
5070 } else if (match_blame_header("previous ", &line)) {
5071 if (strlen(line) <= SIZEOF_REV)
5072 return FALSE;
5073 string_copy_rev(commit->parent_id, line);
5074 line += SIZEOF_REV;
5075 string_ncopy(commit->parent_filename, line, strlen(line));
5077 } else if (match_blame_header("filename ", &line)) {
5078 string_ncopy(commit->filename, line, strlen(line));
5079 commit = NULL;
5082 return TRUE;
5085 static bool
5086 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5088 struct blame *blame = line->data;
5089 struct time *time = NULL;
5090 const char *id = NULL, *author = NULL;
5092 if (blame->commit && *blame->commit->filename) {
5093 id = blame->commit->id;
5094 author = blame->commit->author;
5095 time = &blame->commit->time;
5098 if (opt_date && draw_date(view, time))
5099 return TRUE;
5101 if (opt_author && draw_author(view, author))
5102 return TRUE;
5104 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5105 return TRUE;
5107 if (draw_lineno(view, lineno))
5108 return TRUE;
5110 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5111 return TRUE;
5114 static bool
5115 check_blame_commit(struct blame *blame, bool check_null_id)
5117 if (!blame->commit)
5118 report("Commit data not loaded yet");
5119 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5120 report("No commit exist for the selected line");
5121 else
5122 return TRUE;
5123 return FALSE;
5126 static void
5127 setup_blame_parent_line(struct view *view, struct blame *blame)
5129 char from[SIZEOF_REF + SIZEOF_STR];
5130 char to[SIZEOF_REF + SIZEOF_STR];
5131 const char *diff_tree_argv[] = {
5132 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5133 "-U0", from, to, "--", NULL
5135 struct io io;
5136 int parent_lineno = -1;
5137 int blamed_lineno = -1;
5138 char *line;
5140 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5141 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5142 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5143 return;
5145 while ((line = io_get(&io, '\n', TRUE))) {
5146 if (*line == '@') {
5147 char *pos = strchr(line, '+');
5149 parent_lineno = atoi(line + 4);
5150 if (pos)
5151 blamed_lineno = atoi(pos + 1);
5153 } else if (*line == '+' && parent_lineno != -1) {
5154 if (blame->lineno == blamed_lineno - 1 &&
5155 !strcmp(blame->text, line + 1)) {
5156 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5157 break;
5159 blamed_lineno++;
5163 io_done(&io);
5166 static enum request
5167 blame_request(struct view *view, enum request request, struct line *line)
5169 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5170 struct blame *blame = line->data;
5172 switch (request) {
5173 case REQ_VIEW_BLAME:
5174 if (check_blame_commit(blame, TRUE)) {
5175 string_copy(opt_ref, blame->commit->id);
5176 string_copy(opt_file, blame->commit->filename);
5177 if (blame->lineno)
5178 view->lineno = blame->lineno;
5179 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5181 break;
5183 case REQ_PARENT:
5184 if (!check_blame_commit(blame, TRUE))
5185 break;
5186 if (!*blame->commit->parent_id) {
5187 report("The selected commit has no parents");
5188 } else {
5189 string_copy_rev(opt_ref, blame->commit->parent_id);
5190 string_copy(opt_file, blame->commit->parent_filename);
5191 setup_blame_parent_line(view, blame);
5192 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5194 break;
5196 case REQ_ENTER:
5197 if (!check_blame_commit(blame, FALSE))
5198 break;
5200 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5201 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5202 break;
5204 if (!strcmp(blame->commit->id, NULL_ID)) {
5205 struct view *diff = VIEW(REQ_VIEW_DIFF);
5206 const char *diff_index_argv[] = {
5207 "git", "diff-index", "--root", "--patch-with-stat",
5208 "-C", "-M", "HEAD", "--", view->vid, NULL
5211 if (!*blame->commit->parent_id) {
5212 diff_index_argv[1] = "diff";
5213 diff_index_argv[2] = "--no-color";
5214 diff_index_argv[6] = "--";
5215 diff_index_argv[7] = "/dev/null";
5218 if (!prepare_update(diff, diff_index_argv, NULL)) {
5219 report("Failed to allocate diff command");
5220 break;
5222 flags |= OPEN_PREPARED;
5225 open_view(view, REQ_VIEW_DIFF, flags);
5226 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5227 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5228 break;
5230 default:
5231 return request;
5234 return REQ_NONE;
5237 static bool
5238 blame_grep(struct view *view, struct line *line)
5240 struct blame *blame = line->data;
5241 struct blame_commit *commit = blame->commit;
5242 const char *text[] = {
5243 blame->text,
5244 commit ? commit->title : "",
5245 commit ? commit->id : "",
5246 commit && opt_author ? commit->author : "",
5247 commit ? mkdate(&commit->time, opt_date) : "",
5248 NULL
5251 return grep_text(view, text);
5254 static void
5255 blame_select(struct view *view, struct line *line)
5257 struct blame *blame = line->data;
5258 struct blame_commit *commit = blame->commit;
5260 if (!commit)
5261 return;
5263 if (!strcmp(commit->id, NULL_ID))
5264 string_ncopy(ref_commit, "HEAD", 4);
5265 else
5266 string_copy_rev(ref_commit, commit->id);
5269 static struct view_ops blame_ops = {
5270 "line",
5271 NULL,
5272 blame_open,
5273 blame_read,
5274 blame_draw,
5275 blame_request,
5276 blame_grep,
5277 blame_select,
5281 * Branch backend
5284 struct branch {
5285 const char *author; /* Author of the last commit. */
5286 struct time time; /* Date of the last activity. */
5287 const struct ref *ref; /* Name and commit ID information. */
5290 static const struct ref branch_all;
5292 static const enum sort_field branch_sort_fields[] = {
5293 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5295 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5297 static int
5298 branch_compare(const void *l1, const void *l2)
5300 const struct branch *branch1 = ((const struct line *) l1)->data;
5301 const struct branch *branch2 = ((const struct line *) l2)->data;
5303 switch (get_sort_field(branch_sort_state)) {
5304 case ORDERBY_DATE:
5305 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5307 case ORDERBY_AUTHOR:
5308 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5310 case ORDERBY_NAME:
5311 default:
5312 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5316 static bool
5317 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5319 struct branch *branch = line->data;
5320 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5322 if (opt_date && draw_date(view, &branch->time))
5323 return TRUE;
5325 if (opt_author && draw_author(view, branch->author))
5326 return TRUE;
5328 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5329 return TRUE;
5332 static enum request
5333 branch_request(struct view *view, enum request request, struct line *line)
5335 struct branch *branch = line->data;
5337 switch (request) {
5338 case REQ_REFRESH:
5339 load_refs();
5340 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5341 return REQ_NONE;
5343 case REQ_TOGGLE_SORT_FIELD:
5344 case REQ_TOGGLE_SORT_ORDER:
5345 sort_view(view, request, &branch_sort_state, branch_compare);
5346 return REQ_NONE;
5348 case REQ_ENTER:
5350 const struct ref *ref = branch->ref;
5351 const char *all_branches_argv[] = {
5352 "git", "log", "--no-color", "--pretty=raw", "--parents",
5353 "--topo-order",
5354 ref == &branch_all ? "--all" : ref->name, NULL
5356 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5358 if (!prepare_update(main_view, all_branches_argv, NULL))
5359 report("Failed to load view of all branches");
5360 else
5361 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5362 return REQ_NONE;
5364 default:
5365 return request;
5369 static bool
5370 branch_read(struct view *view, char *line)
5372 static char id[SIZEOF_REV];
5373 struct branch *reference;
5374 size_t i;
5376 if (!line)
5377 return TRUE;
5379 switch (get_line_type(line)) {
5380 case LINE_COMMIT:
5381 string_copy_rev(id, line + STRING_SIZE("commit "));
5382 return TRUE;
5384 case LINE_AUTHOR:
5385 for (i = 0, reference = NULL; i < view->lines; i++) {
5386 struct branch *branch = view->line[i].data;
5388 if (strcmp(branch->ref->id, id))
5389 continue;
5391 view->line[i].dirty = TRUE;
5392 if (reference) {
5393 branch->author = reference->author;
5394 branch->time = reference->time;
5395 continue;
5398 parse_author_line(line + STRING_SIZE("author "),
5399 &branch->author, &branch->time);
5400 reference = branch;
5402 return TRUE;
5404 default:
5405 return TRUE;
5410 static bool
5411 branch_open_visitor(void *data, const struct ref *ref)
5413 struct view *view = data;
5414 struct branch *branch;
5416 if (ref->tag || ref->ltag || ref->remote)
5417 return TRUE;
5419 branch = calloc(1, sizeof(*branch));
5420 if (!branch)
5421 return FALSE;
5423 branch->ref = ref;
5424 return !!add_line_data(view, branch, LINE_DEFAULT);
5427 static bool
5428 branch_open(struct view *view)
5430 const char *branch_log[] = {
5431 "git", "log", "--no-color", "--pretty=raw",
5432 "--simplify-by-decoration", "--all", NULL
5435 if (!start_update(view, branch_log, NULL)) {
5436 report("Failed to load branch data");
5437 return TRUE;
5440 setup_update(view, view->id);
5441 branch_open_visitor(view, &branch_all);
5442 foreach_ref(branch_open_visitor, view);
5443 view->p_restore = TRUE;
5445 return TRUE;
5448 static bool
5449 branch_grep(struct view *view, struct line *line)
5451 struct branch *branch = line->data;
5452 const char *text[] = {
5453 branch->ref->name,
5454 branch->author,
5455 NULL
5458 return grep_text(view, text);
5461 static void
5462 branch_select(struct view *view, struct line *line)
5464 struct branch *branch = line->data;
5466 string_copy_rev(view->ref, branch->ref->id);
5467 string_copy_rev(ref_commit, branch->ref->id);
5468 string_copy_rev(ref_head, branch->ref->id);
5469 string_copy_rev(ref_branch, branch->ref->name);
5472 static struct view_ops branch_ops = {
5473 "branch",
5474 NULL,
5475 branch_open,
5476 branch_read,
5477 branch_draw,
5478 branch_request,
5479 branch_grep,
5480 branch_select,
5484 * Status backend
5487 struct status {
5488 char status;
5489 struct {
5490 mode_t mode;
5491 char rev[SIZEOF_REV];
5492 char name[SIZEOF_STR];
5493 } old;
5494 struct {
5495 mode_t mode;
5496 char rev[SIZEOF_REV];
5497 char name[SIZEOF_STR];
5498 } new;
5501 static char status_onbranch[SIZEOF_STR];
5502 static struct status stage_status;
5503 static enum line_type stage_line_type;
5504 static size_t stage_chunks;
5505 static int *stage_chunk;
5507 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5509 /* This should work even for the "On branch" line. */
5510 static inline bool
5511 status_has_none(struct view *view, struct line *line)
5513 return line < view->line + view->lines && !line[1].data;
5516 /* Get fields from the diff line:
5517 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5519 static inline bool
5520 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5522 const char *old_mode = buf + 1;
5523 const char *new_mode = buf + 8;
5524 const char *old_rev = buf + 15;
5525 const char *new_rev = buf + 56;
5526 const char *status = buf + 97;
5528 if (bufsize < 98 ||
5529 old_mode[-1] != ':' ||
5530 new_mode[-1] != ' ' ||
5531 old_rev[-1] != ' ' ||
5532 new_rev[-1] != ' ' ||
5533 status[-1] != ' ')
5534 return FALSE;
5536 file->status = *status;
5538 string_copy_rev(file->old.rev, old_rev);
5539 string_copy_rev(file->new.rev, new_rev);
5541 file->old.mode = strtoul(old_mode, NULL, 8);
5542 file->new.mode = strtoul(new_mode, NULL, 8);
5544 file->old.name[0] = file->new.name[0] = 0;
5546 return TRUE;
5549 static bool
5550 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5552 struct status *unmerged = NULL;
5553 char *buf;
5554 struct io io;
5556 if (!io_run(&io, IO_RD, opt_cdup, argv))
5557 return FALSE;
5559 add_line_data(view, NULL, type);
5561 while ((buf = io_get(&io, 0, TRUE))) {
5562 struct status *file = unmerged;
5564 if (!file) {
5565 file = calloc(1, sizeof(*file));
5566 if (!file || !add_line_data(view, file, type))
5567 goto error_out;
5570 /* Parse diff info part. */
5571 if (status) {
5572 file->status = status;
5573 if (status == 'A')
5574 string_copy(file->old.rev, NULL_ID);
5576 } else if (!file->status || file == unmerged) {
5577 if (!status_get_diff(file, buf, strlen(buf)))
5578 goto error_out;
5580 buf = io_get(&io, 0, TRUE);
5581 if (!buf)
5582 break;
5584 /* Collapse all modified entries that follow an
5585 * associated unmerged entry. */
5586 if (unmerged == file) {
5587 unmerged->status = 'U';
5588 unmerged = NULL;
5589 } else if (file->status == 'U') {
5590 unmerged = file;
5594 /* Grab the old name for rename/copy. */
5595 if (!*file->old.name &&
5596 (file->status == 'R' || file->status == 'C')) {
5597 string_ncopy(file->old.name, buf, strlen(buf));
5599 buf = io_get(&io, 0, TRUE);
5600 if (!buf)
5601 break;
5604 /* git-ls-files just delivers a NUL separated list of
5605 * file names similar to the second half of the
5606 * git-diff-* output. */
5607 string_ncopy(file->new.name, buf, strlen(buf));
5608 if (!*file->old.name)
5609 string_copy(file->old.name, file->new.name);
5610 file = NULL;
5613 if (io_error(&io)) {
5614 error_out:
5615 io_done(&io);
5616 return FALSE;
5619 if (!view->line[view->lines - 1].data)
5620 add_line_data(view, NULL, LINE_STAT_NONE);
5622 io_done(&io);
5623 return TRUE;
5626 /* Don't show unmerged entries in the staged section. */
5627 static const char *status_diff_index_argv[] = {
5628 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5629 "--cached", "-M", "HEAD", NULL
5632 static const char *status_diff_files_argv[] = {
5633 "git", "diff-files", "-z", NULL
5636 static const char *status_list_other_argv[] = {
5637 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5640 static const char *status_list_no_head_argv[] = {
5641 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5644 static const char *update_index_argv[] = {
5645 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5648 /* Restore the previous line number to stay in the context or select a
5649 * line with something that can be updated. */
5650 static void
5651 status_restore(struct view *view)
5653 if (view->p_lineno >= view->lines)
5654 view->p_lineno = view->lines - 1;
5655 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5656 view->p_lineno++;
5657 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5658 view->p_lineno--;
5660 /* If the above fails, always skip the "On branch" line. */
5661 if (view->p_lineno < view->lines)
5662 view->lineno = view->p_lineno;
5663 else
5664 view->lineno = 1;
5666 if (view->lineno < view->offset)
5667 view->offset = view->lineno;
5668 else if (view->offset + view->height <= view->lineno)
5669 view->offset = view->lineno - view->height + 1;
5671 view->p_restore = FALSE;
5674 static void
5675 status_update_onbranch(void)
5677 static const char *paths[][2] = {
5678 { "rebase-apply/rebasing", "Rebasing" },
5679 { "rebase-apply/applying", "Applying mailbox" },
5680 { "rebase-apply/", "Rebasing mailbox" },
5681 { "rebase-merge/interactive", "Interactive rebase" },
5682 { "rebase-merge/", "Rebase merge" },
5683 { "MERGE_HEAD", "Merging" },
5684 { "BISECT_LOG", "Bisecting" },
5685 { "HEAD", "On branch" },
5687 char buf[SIZEOF_STR];
5688 struct stat stat;
5689 int i;
5691 if (is_initial_commit()) {
5692 string_copy(status_onbranch, "Initial commit");
5693 return;
5696 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5697 char *head = opt_head;
5699 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5700 lstat(buf, &stat) < 0)
5701 continue;
5703 if (!*opt_head) {
5704 struct io io;
5706 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5707 io_read_buf(&io, buf, sizeof(buf))) {
5708 head = buf;
5709 if (!prefixcmp(head, "refs/heads/"))
5710 head += STRING_SIZE("refs/heads/");
5714 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5715 string_copy(status_onbranch, opt_head);
5716 return;
5719 string_copy(status_onbranch, "Not currently on any branch");
5722 /* First parse staged info using git-diff-index(1), then parse unstaged
5723 * info using git-diff-files(1), and finally untracked files using
5724 * git-ls-files(1). */
5725 static bool
5726 status_open(struct view *view)
5728 reset_view(view);
5730 add_line_data(view, NULL, LINE_STAT_HEAD);
5731 status_update_onbranch();
5733 io_run_bg(update_index_argv);
5735 if (is_initial_commit()) {
5736 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5737 return FALSE;
5738 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5739 return FALSE;
5742 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5743 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5744 return FALSE;
5746 /* Restore the exact position or use the specialized restore
5747 * mode? */
5748 if (!view->p_restore)
5749 status_restore(view);
5750 return TRUE;
5753 static bool
5754 status_draw(struct view *view, struct line *line, unsigned int lineno)
5756 struct status *status = line->data;
5757 enum line_type type;
5758 const char *text;
5760 if (!status) {
5761 switch (line->type) {
5762 case LINE_STAT_STAGED:
5763 type = LINE_STAT_SECTION;
5764 text = "Changes to be committed:";
5765 break;
5767 case LINE_STAT_UNSTAGED:
5768 type = LINE_STAT_SECTION;
5769 text = "Changed but not updated:";
5770 break;
5772 case LINE_STAT_UNTRACKED:
5773 type = LINE_STAT_SECTION;
5774 text = "Untracked files:";
5775 break;
5777 case LINE_STAT_NONE:
5778 type = LINE_DEFAULT;
5779 text = " (no files)";
5780 break;
5782 case LINE_STAT_HEAD:
5783 type = LINE_STAT_HEAD;
5784 text = status_onbranch;
5785 break;
5787 default:
5788 return FALSE;
5790 } else {
5791 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5793 buf[0] = status->status;
5794 if (draw_text(view, line->type, buf, TRUE))
5795 return TRUE;
5796 type = LINE_DEFAULT;
5797 text = status->new.name;
5800 draw_text(view, type, text, TRUE);
5801 return TRUE;
5804 static enum request
5805 status_load_error(struct view *view, struct view *stage, const char *path)
5807 if (displayed_views() == 2 || display[current_view] != view)
5808 maximize_view(view);
5809 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5810 return REQ_NONE;
5813 static enum request
5814 status_enter(struct view *view, struct line *line)
5816 struct status *status = line->data;
5817 const char *oldpath = status ? status->old.name : NULL;
5818 /* Diffs for unmerged entries are empty when passing the new
5819 * path, so leave it empty. */
5820 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5821 const char *info;
5822 enum open_flags split;
5823 struct view *stage = VIEW(REQ_VIEW_STAGE);
5825 if (line->type == LINE_STAT_NONE ||
5826 (!status && line[1].type == LINE_STAT_NONE)) {
5827 report("No file to diff");
5828 return REQ_NONE;
5831 switch (line->type) {
5832 case LINE_STAT_STAGED:
5833 if (is_initial_commit()) {
5834 const char *no_head_diff_argv[] = {
5835 "git", "diff", "--no-color", "--patch-with-stat",
5836 "--", "/dev/null", newpath, NULL
5839 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5840 return status_load_error(view, stage, newpath);
5841 } else {
5842 const char *index_show_argv[] = {
5843 "git", "diff-index", "--root", "--patch-with-stat",
5844 "-C", "-M", "--cached", "HEAD", "--",
5845 oldpath, newpath, NULL
5848 if (!prepare_update(stage, index_show_argv, opt_cdup))
5849 return status_load_error(view, stage, newpath);
5852 if (status)
5853 info = "Staged changes to %s";
5854 else
5855 info = "Staged changes";
5856 break;
5858 case LINE_STAT_UNSTAGED:
5860 const char *files_show_argv[] = {
5861 "git", "diff-files", "--root", "--patch-with-stat",
5862 "-C", "-M", "--", oldpath, newpath, NULL
5865 if (!prepare_update(stage, files_show_argv, opt_cdup))
5866 return status_load_error(view, stage, newpath);
5867 if (status)
5868 info = "Unstaged changes to %s";
5869 else
5870 info = "Unstaged changes";
5871 break;
5873 case LINE_STAT_UNTRACKED:
5874 if (!newpath) {
5875 report("No file to show");
5876 return REQ_NONE;
5879 if (!suffixcmp(status->new.name, -1, "/")) {
5880 report("Cannot display a directory");
5881 return REQ_NONE;
5884 if (!prepare_update_file(stage, newpath))
5885 return status_load_error(view, stage, newpath);
5886 info = "Untracked file %s";
5887 break;
5889 case LINE_STAT_HEAD:
5890 return REQ_NONE;
5892 default:
5893 die("line type %d not handled in switch", line->type);
5896 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5897 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5898 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5899 if (status) {
5900 stage_status = *status;
5901 } else {
5902 memset(&stage_status, 0, sizeof(stage_status));
5905 stage_line_type = line->type;
5906 stage_chunks = 0;
5907 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5910 return REQ_NONE;
5913 static bool
5914 status_exists(struct status *status, enum line_type type)
5916 struct view *view = VIEW(REQ_VIEW_STATUS);
5917 unsigned long lineno;
5919 for (lineno = 0; lineno < view->lines; lineno++) {
5920 struct line *line = &view->line[lineno];
5921 struct status *pos = line->data;
5923 if (line->type != type)
5924 continue;
5925 if (!pos && (!status || !status->status) && line[1].data) {
5926 select_view_line(view, lineno);
5927 return TRUE;
5929 if (pos && !strcmp(status->new.name, pos->new.name)) {
5930 select_view_line(view, lineno);
5931 return TRUE;
5935 return FALSE;
5939 static bool
5940 status_update_prepare(struct io *io, enum line_type type)
5942 const char *staged_argv[] = {
5943 "git", "update-index", "-z", "--index-info", NULL
5945 const char *others_argv[] = {
5946 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5949 switch (type) {
5950 case LINE_STAT_STAGED:
5951 return io_run(io, IO_WR, opt_cdup, staged_argv);
5953 case LINE_STAT_UNSTAGED:
5954 case LINE_STAT_UNTRACKED:
5955 return io_run(io, IO_WR, opt_cdup, others_argv);
5957 default:
5958 die("line type %d not handled in switch", type);
5959 return FALSE;
5963 static bool
5964 status_update_write(struct io *io, struct status *status, enum line_type type)
5966 char buf[SIZEOF_STR];
5967 size_t bufsize = 0;
5969 switch (type) {
5970 case LINE_STAT_STAGED:
5971 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5972 status->old.mode,
5973 status->old.rev,
5974 status->old.name, 0))
5975 return FALSE;
5976 break;
5978 case LINE_STAT_UNSTAGED:
5979 case LINE_STAT_UNTRACKED:
5980 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5981 return FALSE;
5982 break;
5984 default:
5985 die("line type %d not handled in switch", type);
5988 return io_write(io, buf, bufsize);
5991 static bool
5992 status_update_file(struct status *status, enum line_type type)
5994 struct io io;
5995 bool result;
5997 if (!status_update_prepare(&io, type))
5998 return FALSE;
6000 result = status_update_write(&io, status, type);
6001 return io_done(&io) && result;
6004 static bool
6005 status_update_files(struct view *view, struct line *line)
6007 char buf[sizeof(view->ref)];
6008 struct io io;
6009 bool result = TRUE;
6010 struct line *pos = view->line + view->lines;
6011 int files = 0;
6012 int file, done;
6013 int cursor_y = -1, cursor_x = -1;
6015 if (!status_update_prepare(&io, line->type))
6016 return FALSE;
6018 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6019 files++;
6021 string_copy(buf, view->ref);
6022 getsyx(cursor_y, cursor_x);
6023 for (file = 0, done = 5; result && file < files; line++, file++) {
6024 int almost_done = file * 100 / files;
6026 if (almost_done > done) {
6027 done = almost_done;
6028 string_format(view->ref, "updating file %u of %u (%d%% done)",
6029 file, files, done);
6030 update_view_title(view);
6031 setsyx(cursor_y, cursor_x);
6032 doupdate();
6034 result = status_update_write(&io, line->data, line->type);
6036 string_copy(view->ref, buf);
6038 return io_done(&io) && result;
6041 static bool
6042 status_update(struct view *view)
6044 struct line *line = &view->line[view->lineno];
6046 assert(view->lines);
6048 if (!line->data) {
6049 /* This should work even for the "On branch" line. */
6050 if (line < view->line + view->lines && !line[1].data) {
6051 report("Nothing to update");
6052 return FALSE;
6055 if (!status_update_files(view, line + 1)) {
6056 report("Failed to update file status");
6057 return FALSE;
6060 } else if (!status_update_file(line->data, line->type)) {
6061 report("Failed to update file status");
6062 return FALSE;
6065 return TRUE;
6068 static bool
6069 status_revert(struct status *status, enum line_type type, bool has_none)
6071 if (!status || type != LINE_STAT_UNSTAGED) {
6072 if (type == LINE_STAT_STAGED) {
6073 report("Cannot revert changes to staged files");
6074 } else if (type == LINE_STAT_UNTRACKED) {
6075 report("Cannot revert changes to untracked files");
6076 } else if (has_none) {
6077 report("Nothing to revert");
6078 } else {
6079 report("Cannot revert changes to multiple files");
6082 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6083 char mode[10] = "100644";
6084 const char *reset_argv[] = {
6085 "git", "update-index", "--cacheinfo", mode,
6086 status->old.rev, status->old.name, NULL
6088 const char *checkout_argv[] = {
6089 "git", "checkout", "--", status->old.name, NULL
6092 if (status->status == 'U') {
6093 string_format(mode, "%5o", status->old.mode);
6095 if (status->old.mode == 0 && status->new.mode == 0) {
6096 reset_argv[2] = "--force-remove";
6097 reset_argv[3] = status->old.name;
6098 reset_argv[4] = NULL;
6101 if (!io_run_fg(reset_argv, opt_cdup))
6102 return FALSE;
6103 if (status->old.mode == 0 && status->new.mode == 0)
6104 return TRUE;
6107 return io_run_fg(checkout_argv, opt_cdup);
6110 return FALSE;
6113 static enum request
6114 status_request(struct view *view, enum request request, struct line *line)
6116 struct status *status = line->data;
6118 switch (request) {
6119 case REQ_STATUS_UPDATE:
6120 if (!status_update(view))
6121 return REQ_NONE;
6122 break;
6124 case REQ_STATUS_REVERT:
6125 if (!status_revert(status, line->type, status_has_none(view, line)))
6126 return REQ_NONE;
6127 break;
6129 case REQ_STATUS_MERGE:
6130 if (!status || status->status != 'U') {
6131 report("Merging only possible for files with unmerged status ('U').");
6132 return REQ_NONE;
6134 open_mergetool(status->new.name);
6135 break;
6137 case REQ_EDIT:
6138 if (!status)
6139 return request;
6140 if (status->status == 'D') {
6141 report("File has been deleted.");
6142 return REQ_NONE;
6145 open_editor(status->new.name);
6146 break;
6148 case REQ_VIEW_BLAME:
6149 if (status)
6150 opt_ref[0] = 0;
6151 return request;
6153 case REQ_ENTER:
6154 /* After returning the status view has been split to
6155 * show the stage view. No further reloading is
6156 * necessary. */
6157 return status_enter(view, line);
6159 case REQ_REFRESH:
6160 /* Simply reload the view. */
6161 break;
6163 default:
6164 return request;
6167 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6169 return REQ_NONE;
6172 static void
6173 status_select(struct view *view, struct line *line)
6175 struct status *status = line->data;
6176 char file[SIZEOF_STR] = "all files";
6177 const char *text;
6178 const char *key;
6180 if (status && !string_format(file, "'%s'", status->new.name))
6181 return;
6183 if (!status && line[1].type == LINE_STAT_NONE)
6184 line++;
6186 switch (line->type) {
6187 case LINE_STAT_STAGED:
6188 text = "Press %s to unstage %s for commit";
6189 break;
6191 case LINE_STAT_UNSTAGED:
6192 text = "Press %s to stage %s for commit";
6193 break;
6195 case LINE_STAT_UNTRACKED:
6196 text = "Press %s to stage %s for addition";
6197 break;
6199 case LINE_STAT_HEAD:
6200 case LINE_STAT_NONE:
6201 text = "Nothing to update";
6202 break;
6204 default:
6205 die("line type %d not handled in switch", line->type);
6208 if (status && status->status == 'U') {
6209 text = "Press %s to resolve conflict in %s";
6210 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6212 } else {
6213 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6216 string_format(view->ref, text, key, file);
6217 if (status)
6218 string_copy(opt_file, status->new.name);
6221 static bool
6222 status_grep(struct view *view, struct line *line)
6224 struct status *status = line->data;
6226 if (status) {
6227 const char buf[2] = { status->status, 0 };
6228 const char *text[] = { status->new.name, buf, NULL };
6230 return grep_text(view, text);
6233 return FALSE;
6236 static struct view_ops status_ops = {
6237 "file",
6238 NULL,
6239 status_open,
6240 NULL,
6241 status_draw,
6242 status_request,
6243 status_grep,
6244 status_select,
6248 static bool
6249 stage_diff_write(struct io *io, struct line *line, struct line *end)
6251 while (line < end) {
6252 if (!io_write(io, line->data, strlen(line->data)) ||
6253 !io_write(io, "\n", 1))
6254 return FALSE;
6255 line++;
6256 if (line->type == LINE_DIFF_CHUNK ||
6257 line->type == LINE_DIFF_HEADER)
6258 break;
6261 return TRUE;
6264 static struct line *
6265 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6267 for (; view->line < line; line--)
6268 if (line->type == type)
6269 return line;
6271 return NULL;
6274 static bool
6275 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6277 const char *apply_argv[SIZEOF_ARG] = {
6278 "git", "apply", "--whitespace=nowarn", NULL
6280 struct line *diff_hdr;
6281 struct io io;
6282 int argc = 3;
6284 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6285 if (!diff_hdr)
6286 return FALSE;
6288 if (!revert)
6289 apply_argv[argc++] = "--cached";
6290 if (revert || stage_line_type == LINE_STAT_STAGED)
6291 apply_argv[argc++] = "-R";
6292 apply_argv[argc++] = "-";
6293 apply_argv[argc++] = NULL;
6294 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6295 return FALSE;
6297 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6298 !stage_diff_write(&io, chunk, view->line + view->lines))
6299 chunk = NULL;
6301 io_done(&io);
6302 io_run_bg(update_index_argv);
6304 return chunk ? TRUE : FALSE;
6307 static bool
6308 stage_update(struct view *view, struct line *line)
6310 struct line *chunk = NULL;
6312 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6313 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6315 if (chunk) {
6316 if (!stage_apply_chunk(view, chunk, FALSE)) {
6317 report("Failed to apply chunk");
6318 return FALSE;
6321 } else if (!stage_status.status) {
6322 view = VIEW(REQ_VIEW_STATUS);
6324 for (line = view->line; line < view->line + view->lines; line++)
6325 if (line->type == stage_line_type)
6326 break;
6328 if (!status_update_files(view, line + 1)) {
6329 report("Failed to update files");
6330 return FALSE;
6333 } else if (!status_update_file(&stage_status, stage_line_type)) {
6334 report("Failed to update file");
6335 return FALSE;
6338 return TRUE;
6341 static bool
6342 stage_revert(struct view *view, struct line *line)
6344 struct line *chunk = NULL;
6346 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6347 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6349 if (chunk) {
6350 if (!prompt_yesno("Are you sure you want to revert changes?"))
6351 return FALSE;
6353 if (!stage_apply_chunk(view, chunk, TRUE)) {
6354 report("Failed to revert chunk");
6355 return FALSE;
6357 return TRUE;
6359 } else {
6360 return status_revert(stage_status.status ? &stage_status : NULL,
6361 stage_line_type, FALSE);
6366 static void
6367 stage_next(struct view *view, struct line *line)
6369 int i;
6371 if (!stage_chunks) {
6372 for (line = view->line; line < view->line + view->lines; line++) {
6373 if (line->type != LINE_DIFF_CHUNK)
6374 continue;
6376 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6377 report("Allocation failure");
6378 return;
6381 stage_chunk[stage_chunks++] = line - view->line;
6385 for (i = 0; i < stage_chunks; i++) {
6386 if (stage_chunk[i] > view->lineno) {
6387 do_scroll_view(view, stage_chunk[i] - view->lineno);
6388 report("Chunk %d of %d", i + 1, stage_chunks);
6389 return;
6393 report("No next chunk found");
6396 static enum request
6397 stage_request(struct view *view, enum request request, struct line *line)
6399 switch (request) {
6400 case REQ_STATUS_UPDATE:
6401 if (!stage_update(view, line))
6402 return REQ_NONE;
6403 break;
6405 case REQ_STATUS_REVERT:
6406 if (!stage_revert(view, line))
6407 return REQ_NONE;
6408 break;
6410 case REQ_STAGE_NEXT:
6411 if (stage_line_type == LINE_STAT_UNTRACKED) {
6412 report("File is untracked; press %s to add",
6413 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6414 return REQ_NONE;
6416 stage_next(view, line);
6417 return REQ_NONE;
6419 case REQ_EDIT:
6420 if (!stage_status.new.name[0])
6421 return request;
6422 if (stage_status.status == 'D') {
6423 report("File has been deleted.");
6424 return REQ_NONE;
6427 open_editor(stage_status.new.name);
6428 break;
6430 case REQ_REFRESH:
6431 /* Reload everything ... */
6432 break;
6434 case REQ_VIEW_BLAME:
6435 if (stage_status.new.name[0]) {
6436 string_copy(opt_file, stage_status.new.name);
6437 opt_ref[0] = 0;
6439 return request;
6441 case REQ_ENTER:
6442 return pager_request(view, request, line);
6444 default:
6445 return request;
6448 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6449 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6451 /* Check whether the staged entry still exists, and close the
6452 * stage view if it doesn't. */
6453 if (!status_exists(&stage_status, stage_line_type)) {
6454 status_restore(VIEW(REQ_VIEW_STATUS));
6455 return REQ_VIEW_CLOSE;
6458 if (stage_line_type == LINE_STAT_UNTRACKED) {
6459 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6460 report("Cannot display a directory");
6461 return REQ_NONE;
6464 if (!prepare_update_file(view, stage_status.new.name)) {
6465 report("Failed to open file: %s", strerror(errno));
6466 return REQ_NONE;
6469 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6471 return REQ_NONE;
6474 static struct view_ops stage_ops = {
6475 "line",
6476 NULL,
6477 NULL,
6478 pager_read,
6479 pager_draw,
6480 stage_request,
6481 pager_grep,
6482 pager_select,
6487 * Revision graph
6490 struct commit {
6491 char id[SIZEOF_REV]; /* SHA1 ID. */
6492 char title[128]; /* First line of the commit message. */
6493 const char *author; /* Author of the commit. */
6494 struct time time; /* Date from the author ident. */
6495 struct ref_list *refs; /* Repository references. */
6496 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6497 size_t graph_size; /* The width of the graph array. */
6498 bool has_parents; /* Rewritten --parents seen. */
6501 /* Size of rev graph with no "padding" columns */
6502 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6504 struct rev_graph {
6505 struct rev_graph *prev, *next, *parents;
6506 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6507 size_t size;
6508 struct commit *commit;
6509 size_t pos;
6510 unsigned int boundary:1;
6513 /* Parents of the commit being visualized. */
6514 static struct rev_graph graph_parents[4];
6516 /* The current stack of revisions on the graph. */
6517 static struct rev_graph graph_stacks[4] = {
6518 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6519 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6520 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6521 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6524 static inline bool
6525 graph_parent_is_merge(struct rev_graph *graph)
6527 return graph->parents->size > 1;
6530 static inline void
6531 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6533 struct commit *commit = graph->commit;
6535 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6536 commit->graph[commit->graph_size++] = symbol;
6539 static void
6540 clear_rev_graph(struct rev_graph *graph)
6542 graph->boundary = 0;
6543 graph->size = graph->pos = 0;
6544 graph->commit = NULL;
6545 memset(graph->parents, 0, sizeof(*graph->parents));
6548 static void
6549 done_rev_graph(struct rev_graph *graph)
6551 if (graph_parent_is_merge(graph) &&
6552 graph->pos < graph->size - 1 &&
6553 graph->next->size == graph->size + graph->parents->size - 1) {
6554 size_t i = graph->pos + graph->parents->size - 1;
6556 graph->commit->graph_size = i * 2;
6557 while (i < graph->next->size - 1) {
6558 append_to_rev_graph(graph, ' ');
6559 append_to_rev_graph(graph, '\\');
6560 i++;
6564 clear_rev_graph(graph);
6567 static void
6568 push_rev_graph(struct rev_graph *graph, const char *parent)
6570 int i;
6572 /* "Collapse" duplicate parents lines.
6574 * FIXME: This needs to also update update the drawn graph but
6575 * for now it just serves as a method for pruning graph lines. */
6576 for (i = 0; i < graph->size; i++)
6577 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6578 return;
6580 if (graph->size < SIZEOF_REVITEMS) {
6581 string_copy_rev(graph->rev[graph->size++], parent);
6585 static chtype
6586 get_rev_graph_symbol(struct rev_graph *graph)
6588 chtype symbol;
6590 if (graph->boundary)
6591 symbol = REVGRAPH_BOUND;
6592 else if (graph->parents->size == 0)
6593 symbol = REVGRAPH_INIT;
6594 else if (graph_parent_is_merge(graph))
6595 symbol = REVGRAPH_MERGE;
6596 else if (graph->pos >= graph->size)
6597 symbol = REVGRAPH_BRANCH;
6598 else
6599 symbol = REVGRAPH_COMMIT;
6601 return symbol;
6604 static void
6605 draw_rev_graph(struct rev_graph *graph)
6607 struct rev_filler {
6608 chtype separator, line;
6610 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6611 static struct rev_filler fillers[] = {
6612 { ' ', '|' },
6613 { '`', '.' },
6614 { '\'', ' ' },
6615 { '/', ' ' },
6617 chtype symbol = get_rev_graph_symbol(graph);
6618 struct rev_filler *filler;
6619 size_t i;
6621 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6622 filler = &fillers[DEFAULT];
6624 for (i = 0; i < graph->pos; i++) {
6625 append_to_rev_graph(graph, filler->line);
6626 if (graph_parent_is_merge(graph->prev) &&
6627 graph->prev->pos == i)
6628 filler = &fillers[RSHARP];
6630 append_to_rev_graph(graph, filler->separator);
6633 /* Place the symbol for this revision. */
6634 append_to_rev_graph(graph, symbol);
6636 if (graph->prev->size > graph->size)
6637 filler = &fillers[RDIAG];
6638 else
6639 filler = &fillers[DEFAULT];
6641 i++;
6643 for (; i < graph->size; i++) {
6644 append_to_rev_graph(graph, filler->separator);
6645 append_to_rev_graph(graph, filler->line);
6646 if (graph_parent_is_merge(graph->prev) &&
6647 i < graph->prev->pos + graph->parents->size)
6648 filler = &fillers[RSHARP];
6649 if (graph->prev->size > graph->size)
6650 filler = &fillers[LDIAG];
6653 if (graph->prev->size > graph->size) {
6654 append_to_rev_graph(graph, filler->separator);
6655 if (filler->line != ' ')
6656 append_to_rev_graph(graph, filler->line);
6660 /* Prepare the next rev graph */
6661 static void
6662 prepare_rev_graph(struct rev_graph *graph)
6664 size_t i;
6666 /* First, traverse all lines of revisions up to the active one. */
6667 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6668 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6669 break;
6671 push_rev_graph(graph->next, graph->rev[graph->pos]);
6674 /* Interleave the new revision parent(s). */
6675 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6676 push_rev_graph(graph->next, graph->parents->rev[i]);
6678 /* Lastly, put any remaining revisions. */
6679 for (i = graph->pos + 1; i < graph->size; i++)
6680 push_rev_graph(graph->next, graph->rev[i]);
6683 static void
6684 update_rev_graph(struct view *view, struct rev_graph *graph)
6686 /* If this is the finalizing update ... */
6687 if (graph->commit)
6688 prepare_rev_graph(graph);
6690 /* Graph visualization needs a one rev look-ahead,
6691 * so the first update doesn't visualize anything. */
6692 if (!graph->prev->commit)
6693 return;
6695 if (view->lines > 2)
6696 view->line[view->lines - 3].dirty = 1;
6697 if (view->lines > 1)
6698 view->line[view->lines - 2].dirty = 1;
6699 draw_rev_graph(graph->prev);
6700 done_rev_graph(graph->prev->prev);
6705 * Main view backend
6708 static const char *main_argv[SIZEOF_ARG] = {
6709 "git", "log", "--no-color", "--pretty=raw", "--parents",
6710 "--topo-order", "%(diff-args)", "%(rev-args)",
6711 "--", "%(file-args)", NULL
6714 static bool
6715 main_draw(struct view *view, struct line *line, unsigned int lineno)
6717 struct commit *commit = line->data;
6719 if (!commit->author)
6720 return FALSE;
6722 if (opt_date && draw_date(view, &commit->time))
6723 return TRUE;
6725 if (opt_author && draw_author(view, commit->author))
6726 return TRUE;
6728 if (opt_rev_graph && commit->graph_size &&
6729 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6730 return TRUE;
6732 if (opt_show_refs && commit->refs) {
6733 size_t i;
6735 for (i = 0; i < commit->refs->size; i++) {
6736 struct ref *ref = commit->refs->refs[i];
6737 enum line_type type;
6739 if (ref->head)
6740 type = LINE_MAIN_HEAD;
6741 else if (ref->ltag)
6742 type = LINE_MAIN_LOCAL_TAG;
6743 else if (ref->tag)
6744 type = LINE_MAIN_TAG;
6745 else if (ref->tracked)
6746 type = LINE_MAIN_TRACKED;
6747 else if (ref->remote)
6748 type = LINE_MAIN_REMOTE;
6749 else
6750 type = LINE_MAIN_REF;
6752 if (draw_text(view, type, "[", TRUE) ||
6753 draw_text(view, type, ref->name, TRUE) ||
6754 draw_text(view, type, "]", TRUE))
6755 return TRUE;
6757 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6758 return TRUE;
6762 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6763 return TRUE;
6766 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6767 static bool
6768 main_read(struct view *view, char *line)
6770 static struct rev_graph *graph = graph_stacks;
6771 enum line_type type;
6772 struct commit *commit;
6774 if (!line) {
6775 int i;
6777 if (!view->lines && !view->prev)
6778 die("No revisions match the given arguments.");
6779 if (view->lines > 0) {
6780 commit = view->line[view->lines - 1].data;
6781 view->line[view->lines - 1].dirty = 1;
6782 if (!commit->author) {
6783 view->lines--;
6784 free(commit);
6785 graph->commit = NULL;
6788 update_rev_graph(view, graph);
6790 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6791 clear_rev_graph(&graph_stacks[i]);
6792 return TRUE;
6795 type = get_line_type(line);
6796 if (type == LINE_COMMIT) {
6797 commit = calloc(1, sizeof(struct commit));
6798 if (!commit)
6799 return FALSE;
6801 line += STRING_SIZE("commit ");
6802 if (*line == '-') {
6803 graph->boundary = 1;
6804 line++;
6807 string_copy_rev(commit->id, line);
6808 commit->refs = get_ref_list(commit->id);
6809 graph->commit = commit;
6810 add_line_data(view, commit, LINE_MAIN_COMMIT);
6812 while ((line = strchr(line, ' '))) {
6813 line++;
6814 push_rev_graph(graph->parents, line);
6815 commit->has_parents = TRUE;
6817 return TRUE;
6820 if (!view->lines)
6821 return TRUE;
6822 commit = view->line[view->lines - 1].data;
6824 switch (type) {
6825 case LINE_PARENT:
6826 if (commit->has_parents)
6827 break;
6828 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6829 break;
6831 case LINE_AUTHOR:
6832 parse_author_line(line + STRING_SIZE("author "),
6833 &commit->author, &commit->time);
6834 update_rev_graph(view, graph);
6835 graph = graph->next;
6836 break;
6838 default:
6839 /* Fill in the commit title if it has not already been set. */
6840 if (commit->title[0])
6841 break;
6843 /* Require titles to start with a non-space character at the
6844 * offset used by git log. */
6845 if (strncmp(line, " ", 4))
6846 break;
6847 line += 4;
6848 /* Well, if the title starts with a whitespace character,
6849 * try to be forgiving. Otherwise we end up with no title. */
6850 while (isspace(*line))
6851 line++;
6852 if (*line == '\0')
6853 break;
6854 /* FIXME: More graceful handling of titles; append "..." to
6855 * shortened titles, etc. */
6857 string_expand(commit->title, sizeof(commit->title), line, 1);
6858 view->line[view->lines - 1].dirty = 1;
6861 return TRUE;
6864 static enum request
6865 main_request(struct view *view, enum request request, struct line *line)
6867 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6869 switch (request) {
6870 case REQ_ENTER:
6871 open_view(view, REQ_VIEW_DIFF, flags);
6872 break;
6873 case REQ_REFRESH:
6874 load_refs();
6875 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6876 break;
6877 default:
6878 return request;
6881 return REQ_NONE;
6884 static bool
6885 grep_refs(struct ref_list *list, regex_t *regex)
6887 regmatch_t pmatch;
6888 size_t i;
6890 if (!opt_show_refs || !list)
6891 return FALSE;
6893 for (i = 0; i < list->size; i++) {
6894 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6895 return TRUE;
6898 return FALSE;
6901 static bool
6902 main_grep(struct view *view, struct line *line)
6904 struct commit *commit = line->data;
6905 const char *text[] = {
6906 commit->title,
6907 opt_author ? commit->author : "",
6908 mkdate(&commit->time, opt_date),
6909 NULL
6912 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6915 static void
6916 main_select(struct view *view, struct line *line)
6918 struct commit *commit = line->data;
6920 string_copy_rev(view->ref, commit->id);
6921 string_copy_rev(ref_commit, view->ref);
6924 static struct view_ops main_ops = {
6925 "commit",
6926 main_argv,
6927 NULL,
6928 main_read,
6929 main_draw,
6930 main_request,
6931 main_grep,
6932 main_select,
6937 * Status management
6940 /* Whether or not the curses interface has been initialized. */
6941 static bool cursed = FALSE;
6943 /* Terminal hacks and workarounds. */
6944 static bool use_scroll_redrawwin;
6945 static bool use_scroll_status_wclear;
6947 /* The status window is used for polling keystrokes. */
6948 static WINDOW *status_win;
6950 /* Reading from the prompt? */
6951 static bool input_mode = FALSE;
6953 static bool status_empty = FALSE;
6955 /* Update status and title window. */
6956 static void
6957 report(const char *msg, ...)
6959 struct view *view = display[current_view];
6961 if (input_mode)
6962 return;
6964 if (!view) {
6965 char buf[SIZEOF_STR];
6966 va_list args;
6968 va_start(args, msg);
6969 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6970 buf[sizeof(buf) - 1] = 0;
6971 buf[sizeof(buf) - 2] = '.';
6972 buf[sizeof(buf) - 3] = '.';
6973 buf[sizeof(buf) - 4] = '.';
6975 va_end(args);
6976 die("%s", buf);
6979 if (!status_empty || *msg) {
6980 va_list args;
6982 va_start(args, msg);
6984 wmove(status_win, 0, 0);
6985 if (view->has_scrolled && use_scroll_status_wclear)
6986 wclear(status_win);
6987 if (*msg) {
6988 vwprintw(status_win, msg, args);
6989 status_empty = FALSE;
6990 } else {
6991 status_empty = TRUE;
6993 wclrtoeol(status_win);
6994 wnoutrefresh(status_win);
6996 va_end(args);
6999 update_view_title(view);
7002 static void
7003 init_display(void)
7005 const char *term;
7006 int x, y;
7008 /* Initialize the curses library */
7009 if (isatty(STDIN_FILENO)) {
7010 cursed = !!initscr();
7011 opt_tty = stdin;
7012 } else {
7013 /* Leave stdin and stdout alone when acting as a pager. */
7014 opt_tty = fopen("/dev/tty", "r+");
7015 if (!opt_tty)
7016 die("Failed to open /dev/tty");
7017 cursed = !!newterm(NULL, opt_tty, opt_tty);
7020 if (!cursed)
7021 die("Failed to initialize curses");
7023 nonl(); /* Disable conversion and detect newlines from input. */
7024 cbreak(); /* Take input chars one at a time, no wait for \n */
7025 noecho(); /* Don't echo input */
7026 leaveok(stdscr, FALSE);
7028 if (has_colors())
7029 init_colors();
7031 getmaxyx(stdscr, y, x);
7032 status_win = newwin(1, 0, y - 1, 0);
7033 if (!status_win)
7034 die("Failed to create status window");
7036 /* Enable keyboard mapping */
7037 keypad(status_win, TRUE);
7038 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7040 TABSIZE = opt_tab_size;
7042 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7043 if (term && !strcmp(term, "gnome-terminal")) {
7044 /* In the gnome-terminal-emulator, the message from
7045 * scrolling up one line when impossible followed by
7046 * scrolling down one line causes corruption of the
7047 * status line. This is fixed by calling wclear. */
7048 use_scroll_status_wclear = TRUE;
7049 use_scroll_redrawwin = FALSE;
7051 } else if (term && !strcmp(term, "xrvt-xpm")) {
7052 /* No problems with full optimizations in xrvt-(unicode)
7053 * and aterm. */
7054 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7056 } else {
7057 /* When scrolling in (u)xterm the last line in the
7058 * scrolling direction will update slowly. */
7059 use_scroll_redrawwin = TRUE;
7060 use_scroll_status_wclear = FALSE;
7064 static int
7065 get_input(int prompt_position)
7067 struct view *view;
7068 int i, key, cursor_y, cursor_x;
7070 if (prompt_position)
7071 input_mode = TRUE;
7073 while (TRUE) {
7074 bool loading = FALSE;
7076 foreach_view (view, i) {
7077 update_view(view);
7078 if (view_is_displayed(view) && view->has_scrolled &&
7079 use_scroll_redrawwin)
7080 redrawwin(view->win);
7081 view->has_scrolled = FALSE;
7082 if (view->pipe)
7083 loading = TRUE;
7086 /* Update the cursor position. */
7087 if (prompt_position) {
7088 getbegyx(status_win, cursor_y, cursor_x);
7089 cursor_x = prompt_position;
7090 } else {
7091 view = display[current_view];
7092 getbegyx(view->win, cursor_y, cursor_x);
7093 cursor_x = view->width - 1;
7094 cursor_y += view->lineno - view->offset;
7096 setsyx(cursor_y, cursor_x);
7098 /* Refresh, accept single keystroke of input */
7099 doupdate();
7100 nodelay(status_win, loading);
7101 key = wgetch(status_win);
7103 /* wgetch() with nodelay() enabled returns ERR when
7104 * there's no input. */
7105 if (key == ERR) {
7107 } else if (key == KEY_RESIZE) {
7108 int height, width;
7110 getmaxyx(stdscr, height, width);
7112 wresize(status_win, 1, width);
7113 mvwin(status_win, height - 1, 0);
7114 wnoutrefresh(status_win);
7115 resize_display();
7116 redraw_display(TRUE);
7118 } else {
7119 input_mode = FALSE;
7120 return key;
7125 static char *
7126 prompt_input(const char *prompt, input_handler handler, void *data)
7128 enum input_status status = INPUT_OK;
7129 static char buf[SIZEOF_STR];
7130 size_t pos = 0;
7132 buf[pos] = 0;
7134 while (status == INPUT_OK || status == INPUT_SKIP) {
7135 int key;
7137 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7138 wclrtoeol(status_win);
7140 key = get_input(pos + 1);
7141 switch (key) {
7142 case KEY_RETURN:
7143 case KEY_ENTER:
7144 case '\n':
7145 status = pos ? INPUT_STOP : INPUT_CANCEL;
7146 break;
7148 case KEY_BACKSPACE:
7149 if (pos > 0)
7150 buf[--pos] = 0;
7151 else
7152 status = INPUT_CANCEL;
7153 break;
7155 case KEY_ESC:
7156 status = INPUT_CANCEL;
7157 break;
7159 default:
7160 if (pos >= sizeof(buf)) {
7161 report("Input string too long");
7162 return NULL;
7165 status = handler(data, buf, key);
7166 if (status == INPUT_OK)
7167 buf[pos++] = (char) key;
7171 /* Clear the status window */
7172 status_empty = FALSE;
7173 report("");
7175 if (status == INPUT_CANCEL)
7176 return NULL;
7178 buf[pos++] = 0;
7180 return buf;
7183 static enum input_status
7184 prompt_yesno_handler(void *data, char *buf, int c)
7186 if (c == 'y' || c == 'Y')
7187 return INPUT_STOP;
7188 if (c == 'n' || c == 'N')
7189 return INPUT_CANCEL;
7190 return INPUT_SKIP;
7193 static bool
7194 prompt_yesno(const char *prompt)
7196 char prompt2[SIZEOF_STR];
7198 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7199 return FALSE;
7201 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7204 static enum input_status
7205 read_prompt_handler(void *data, char *buf, int c)
7207 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7210 static char *
7211 read_prompt(const char *prompt)
7213 return prompt_input(prompt, read_prompt_handler, NULL);
7216 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7218 enum input_status status = INPUT_OK;
7219 int size = 0;
7221 while (items[size].text)
7222 size++;
7224 while (status == INPUT_OK) {
7225 const struct menu_item *item = &items[*selected];
7226 int key;
7227 int i;
7229 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7230 prompt, *selected + 1, size);
7231 if (item->hotkey)
7232 wprintw(status_win, "[%c] ", (char) item->hotkey);
7233 wprintw(status_win, "%s", item->text);
7234 wclrtoeol(status_win);
7236 key = get_input(COLS - 1);
7237 switch (key) {
7238 case KEY_RETURN:
7239 case KEY_ENTER:
7240 case '\n':
7241 status = INPUT_STOP;
7242 break;
7244 case KEY_LEFT:
7245 case KEY_UP:
7246 *selected = *selected - 1;
7247 if (*selected < 0)
7248 *selected = size - 1;
7249 break;
7251 case KEY_RIGHT:
7252 case KEY_DOWN:
7253 *selected = (*selected + 1) % size;
7254 break;
7256 case KEY_ESC:
7257 status = INPUT_CANCEL;
7258 break;
7260 default:
7261 for (i = 0; items[i].text; i++)
7262 if (items[i].hotkey == key) {
7263 *selected = i;
7264 status = INPUT_STOP;
7265 break;
7270 /* Clear the status window */
7271 status_empty = FALSE;
7272 report("");
7274 return status != INPUT_CANCEL;
7278 * Repository properties
7281 static struct ref **refs = NULL;
7282 static size_t refs_size = 0;
7283 static struct ref *refs_head = NULL;
7285 static struct ref_list **ref_lists = NULL;
7286 static size_t ref_lists_size = 0;
7288 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7289 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7290 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7292 static int
7293 compare_refs(const void *ref1_, const void *ref2_)
7295 const struct ref *ref1 = *(const struct ref **)ref1_;
7296 const struct ref *ref2 = *(const struct ref **)ref2_;
7298 if (ref1->tag != ref2->tag)
7299 return ref2->tag - ref1->tag;
7300 if (ref1->ltag != ref2->ltag)
7301 return ref2->ltag - ref2->ltag;
7302 if (ref1->head != ref2->head)
7303 return ref2->head - ref1->head;
7304 if (ref1->tracked != ref2->tracked)
7305 return ref2->tracked - ref1->tracked;
7306 if (ref1->remote != ref2->remote)
7307 return ref2->remote - ref1->remote;
7308 return strcmp(ref1->name, ref2->name);
7311 static void
7312 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7314 size_t i;
7316 for (i = 0; i < refs_size; i++)
7317 if (!visitor(data, refs[i]))
7318 break;
7321 static struct ref *
7322 get_ref_head()
7324 return refs_head;
7327 static struct ref_list *
7328 get_ref_list(const char *id)
7330 struct ref_list *list;
7331 size_t i;
7333 for (i = 0; i < ref_lists_size; i++)
7334 if (!strcmp(id, ref_lists[i]->id))
7335 return ref_lists[i];
7337 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7338 return NULL;
7339 list = calloc(1, sizeof(*list));
7340 if (!list)
7341 return NULL;
7343 for (i = 0; i < refs_size; i++) {
7344 if (!strcmp(id, refs[i]->id) &&
7345 realloc_refs_list(&list->refs, list->size, 1))
7346 list->refs[list->size++] = refs[i];
7349 if (!list->refs) {
7350 free(list);
7351 return NULL;
7354 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7355 ref_lists[ref_lists_size++] = list;
7356 return list;
7359 static int
7360 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7362 struct ref *ref = NULL;
7363 bool tag = FALSE;
7364 bool ltag = FALSE;
7365 bool remote = FALSE;
7366 bool tracked = FALSE;
7367 bool head = FALSE;
7368 int from = 0, to = refs_size - 1;
7370 if (!prefixcmp(name, "refs/tags/")) {
7371 if (!suffixcmp(name, namelen, "^{}")) {
7372 namelen -= 3;
7373 name[namelen] = 0;
7374 } else {
7375 ltag = TRUE;
7378 tag = TRUE;
7379 namelen -= STRING_SIZE("refs/tags/");
7380 name += STRING_SIZE("refs/tags/");
7382 } else if (!prefixcmp(name, "refs/remotes/")) {
7383 remote = TRUE;
7384 namelen -= STRING_SIZE("refs/remotes/");
7385 name += STRING_SIZE("refs/remotes/");
7386 tracked = !strcmp(opt_remote, name);
7388 } else if (!prefixcmp(name, "refs/heads/")) {
7389 namelen -= STRING_SIZE("refs/heads/");
7390 name += STRING_SIZE("refs/heads/");
7391 if (!strncmp(opt_head, name, namelen))
7392 return OK;
7394 } else if (!strcmp(name, "HEAD")) {
7395 head = TRUE;
7396 if (*opt_head) {
7397 namelen = strlen(opt_head);
7398 name = opt_head;
7402 /* If we are reloading or it's an annotated tag, replace the
7403 * previous SHA1 with the resolved commit id; relies on the fact
7404 * git-ls-remote lists the commit id of an annotated tag right
7405 * before the commit id it points to. */
7406 while (from <= to) {
7407 size_t pos = (to + from) / 2;
7408 int cmp = strcmp(name, refs[pos]->name);
7410 if (!cmp) {
7411 ref = refs[pos];
7412 break;
7415 if (cmp < 0)
7416 to = pos - 1;
7417 else
7418 from = pos + 1;
7421 if (!ref) {
7422 if (!realloc_refs(&refs, refs_size, 1))
7423 return ERR;
7424 ref = calloc(1, sizeof(*ref) + namelen);
7425 if (!ref)
7426 return ERR;
7427 memmove(refs + from + 1, refs + from,
7428 (refs_size - from) * sizeof(*refs));
7429 refs[from] = ref;
7430 strncpy(ref->name, name, namelen);
7431 refs_size++;
7434 ref->head = head;
7435 ref->tag = tag;
7436 ref->ltag = ltag;
7437 ref->remote = remote;
7438 ref->tracked = tracked;
7439 string_copy_rev(ref->id, id);
7441 if (head)
7442 refs_head = ref;
7443 return OK;
7446 static int
7447 load_refs(void)
7449 const char *head_argv[] = {
7450 "git", "symbolic-ref", "HEAD", NULL
7452 static const char *ls_remote_argv[SIZEOF_ARG] = {
7453 "git", "ls-remote", opt_git_dir, NULL
7455 static bool init = FALSE;
7456 size_t i;
7458 if (!init) {
7459 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7460 die("TIG_LS_REMOTE contains too many arguments");
7461 init = TRUE;
7464 if (!*opt_git_dir)
7465 return OK;
7467 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7468 !prefixcmp(opt_head, "refs/heads/")) {
7469 char *offset = opt_head + STRING_SIZE("refs/heads/");
7471 memmove(opt_head, offset, strlen(offset) + 1);
7474 refs_head = NULL;
7475 for (i = 0; i < refs_size; i++)
7476 refs[i]->id[0] = 0;
7478 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7479 return ERR;
7481 /* Update the ref lists to reflect changes. */
7482 for (i = 0; i < ref_lists_size; i++) {
7483 struct ref_list *list = ref_lists[i];
7484 size_t old, new;
7486 for (old = new = 0; old < list->size; old++)
7487 if (!strcmp(list->id, list->refs[old]->id))
7488 list->refs[new++] = list->refs[old];
7489 list->size = new;
7492 return OK;
7495 static void
7496 set_remote_branch(const char *name, const char *value, size_t valuelen)
7498 if (!strcmp(name, ".remote")) {
7499 string_ncopy(opt_remote, value, valuelen);
7501 } else if (*opt_remote && !strcmp(name, ".merge")) {
7502 size_t from = strlen(opt_remote);
7504 if (!prefixcmp(value, "refs/heads/"))
7505 value += STRING_SIZE("refs/heads/");
7507 if (!string_format_from(opt_remote, &from, "/%s", value))
7508 opt_remote[0] = 0;
7512 static void
7513 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7515 const char *argv[SIZEOF_ARG] = { name, "=" };
7516 int argc = 1 + (cmd == option_set_command);
7517 int error = ERR;
7519 if (!argv_from_string(argv, &argc, value))
7520 config_msg = "Too many option arguments";
7521 else
7522 error = cmd(argc, argv);
7524 if (error == ERR)
7525 warn("Option 'tig.%s': %s", name, config_msg);
7528 static bool
7529 set_environment_variable(const char *name, const char *value)
7531 size_t len = strlen(name) + 1 + strlen(value) + 1;
7532 char *env = malloc(len);
7534 if (env &&
7535 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7536 putenv(env) == 0)
7537 return TRUE;
7538 free(env);
7539 return FALSE;
7542 static void
7543 set_work_tree(const char *value)
7545 char cwd[SIZEOF_STR];
7547 if (!getcwd(cwd, sizeof(cwd)))
7548 die("Failed to get cwd path: %s", strerror(errno));
7549 if (chdir(opt_git_dir) < 0)
7550 die("Failed to chdir(%s): %s", strerror(errno));
7551 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7552 die("Failed to get git path: %s", strerror(errno));
7553 if (chdir(cwd) < 0)
7554 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7555 if (chdir(value) < 0)
7556 die("Failed to chdir(%s): %s", value, strerror(errno));
7557 if (!getcwd(cwd, sizeof(cwd)))
7558 die("Failed to get cwd path: %s", strerror(errno));
7559 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7560 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7561 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7562 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7563 opt_is_inside_work_tree = TRUE;
7566 static int
7567 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7569 if (!strcmp(name, "i18n.commitencoding"))
7570 string_ncopy(opt_encoding, value, valuelen);
7572 else if (!strcmp(name, "core.editor"))
7573 string_ncopy(opt_editor, value, valuelen);
7575 else if (!strcmp(name, "core.worktree"))
7576 set_work_tree(value);
7578 else if (!prefixcmp(name, "tig.color."))
7579 set_repo_config_option(name + 10, value, option_color_command);
7581 else if (!prefixcmp(name, "tig.bind."))
7582 set_repo_config_option(name + 9, value, option_bind_command);
7584 else if (!prefixcmp(name, "tig."))
7585 set_repo_config_option(name + 4, value, option_set_command);
7587 else if (*opt_head && !prefixcmp(name, "branch.") &&
7588 !strncmp(name + 7, opt_head, strlen(opt_head)))
7589 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7591 return OK;
7594 static int
7595 load_git_config(void)
7597 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7599 return io_run_load(config_list_argv, "=", read_repo_config_option);
7602 static int
7603 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7605 if (!opt_git_dir[0]) {
7606 string_ncopy(opt_git_dir, name, namelen);
7608 } else if (opt_is_inside_work_tree == -1) {
7609 /* This can be 3 different values depending on the
7610 * version of git being used. If git-rev-parse does not
7611 * understand --is-inside-work-tree it will simply echo
7612 * the option else either "true" or "false" is printed.
7613 * Default to true for the unknown case. */
7614 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7616 } else if (*name == '.') {
7617 string_ncopy(opt_cdup, name, namelen);
7619 } else {
7620 string_ncopy(opt_prefix, name, namelen);
7623 return OK;
7626 static int
7627 load_repo_info(void)
7629 const char *rev_parse_argv[] = {
7630 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7631 "--show-cdup", "--show-prefix", NULL
7634 return io_run_load(rev_parse_argv, "=", read_repo_info);
7639 * Main
7642 static const char usage[] =
7643 "tig " TIG_VERSION " (" __DATE__ ")\n"
7644 "\n"
7645 "Usage: tig [options] [revs] [--] [paths]\n"
7646 " or: tig show [options] [revs] [--] [paths]\n"
7647 " or: tig blame [rev] path\n"
7648 " or: tig status\n"
7649 " or: tig < [git command output]\n"
7650 "\n"
7651 "Options:\n"
7652 " -v, --version Show version and exit\n"
7653 " -h, --help Show help message and exit";
7655 static void __NORETURN
7656 quit(int sig)
7658 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7659 if (cursed)
7660 endwin();
7661 exit(0);
7664 static void __NORETURN
7665 die(const char *err, ...)
7667 va_list args;
7669 endwin();
7671 va_start(args, err);
7672 fputs("tig: ", stderr);
7673 vfprintf(stderr, err, args);
7674 fputs("\n", stderr);
7675 va_end(args);
7677 exit(1);
7680 static void
7681 warn(const char *msg, ...)
7683 va_list args;
7685 va_start(args, msg);
7686 fputs("tig warning: ", stderr);
7687 vfprintf(stderr, msg, args);
7688 fputs("\n", stderr);
7689 va_end(args);
7692 static const char ***filter_args;
7694 static int
7695 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7697 return argv_append(filter_args, name) ? OK : ERR;
7700 static void
7701 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7703 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7704 const char **all_argv = NULL;
7706 filter_args = args;
7707 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7708 !argv_append_array(&all_argv, argv) ||
7709 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7710 die("Failed to split arguments");
7711 argv_free(all_argv);
7712 free(all_argv);
7715 static void
7716 filter_options(const char *argv[])
7718 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7719 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7720 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7723 static enum request
7724 parse_options(int argc, const char *argv[])
7726 enum request request = REQ_VIEW_MAIN;
7727 const char *subcommand;
7728 bool seen_dashdash = FALSE;
7729 const char **filter_argv = NULL;
7730 int i;
7732 if (!isatty(STDIN_FILENO)) {
7733 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7734 return REQ_VIEW_PAGER;
7737 if (argc <= 1)
7738 return REQ_VIEW_MAIN;
7740 subcommand = argv[1];
7741 if (!strcmp(subcommand, "status")) {
7742 if (argc > 2)
7743 warn("ignoring arguments after `%s'", subcommand);
7744 return REQ_VIEW_STATUS;
7746 } else if (!strcmp(subcommand, "blame")) {
7747 if (argc <= 2 || argc > 4)
7748 die("invalid number of options to blame\n\n%s", usage);
7750 i = 2;
7751 if (argc == 4) {
7752 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7753 i++;
7756 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7757 return REQ_VIEW_BLAME;
7759 } else if (!strcmp(subcommand, "show")) {
7760 request = REQ_VIEW_DIFF;
7762 } else {
7763 subcommand = NULL;
7766 for (i = 1 + !!subcommand; i < argc; i++) {
7767 const char *opt = argv[i];
7769 if (seen_dashdash) {
7770 argv_append(&opt_file_args, opt);
7771 continue;
7773 } else if (!strcmp(opt, "--")) {
7774 seen_dashdash = TRUE;
7775 continue;
7777 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7778 printf("tig version %s\n", TIG_VERSION);
7779 quit(0);
7781 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7782 printf("%s\n", usage);
7783 quit(0);
7785 } else if (!strcmp(opt, "--all")) {
7786 argv_append(&opt_rev_args, opt);
7787 continue;
7790 if (!argv_append(&filter_argv, opt))
7791 die("command too long");
7794 if (filter_argv)
7795 filter_options(filter_argv);
7797 return request;
7801 main(int argc, const char *argv[])
7803 const char *codeset = "UTF-8";
7804 enum request request = parse_options(argc, argv);
7805 struct view *view;
7806 size_t i;
7808 signal(SIGINT, quit);
7809 signal(SIGPIPE, SIG_IGN);
7811 if (setlocale(LC_ALL, "")) {
7812 codeset = nl_langinfo(CODESET);
7815 if (load_repo_info() == ERR)
7816 die("Failed to load repo info.");
7818 if (load_options() == ERR)
7819 die("Failed to load user config.");
7821 if (load_git_config() == ERR)
7822 die("Failed to load repo config.");
7824 /* Require a git repository unless when running in pager mode. */
7825 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7826 die("Not a git repository");
7828 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7829 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7830 if (opt_iconv_in == ICONV_NONE)
7831 die("Failed to initialize character set conversion");
7834 if (codeset && strcmp(codeset, "UTF-8")) {
7835 opt_iconv_out = iconv_open(codeset, "UTF-8");
7836 if (opt_iconv_out == ICONV_NONE)
7837 die("Failed to initialize character set conversion");
7840 if (load_refs() == ERR)
7841 die("Failed to load refs.");
7843 foreach_view (view, i) {
7844 if (getenv(view->cmd_env))
7845 warn("Use of the %s environment variable is deprecated,"
7846 " use options or TIG_DIFF_ARGS instead",
7847 view->cmd_env);
7848 if (!argv_from_env(view->ops->argv, view->cmd_env))
7849 die("Too many arguments in the `%s` environment variable",
7850 view->cmd_env);
7853 init_display();
7855 while (view_driver(display[current_view], request)) {
7856 int key = get_input(0);
7858 view = display[current_view];
7859 request = get_keybinding(view->keymap, key);
7861 /* Some low-level request handling. This keeps access to
7862 * status_win restricted. */
7863 switch (request) {
7864 case REQ_NONE:
7865 report("Unknown key, press %s for help",
7866 get_key(view->keymap, REQ_VIEW_HELP));
7867 break;
7868 case REQ_PROMPT:
7870 char *cmd = read_prompt(":");
7872 if (cmd && isdigit(*cmd)) {
7873 int lineno = view->lineno + 1;
7875 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7876 select_view_line(view, lineno - 1);
7877 report("");
7878 } else {
7879 report("Unable to parse '%s' as a line number", cmd);
7882 } else if (cmd) {
7883 struct view *next = VIEW(REQ_VIEW_PAGER);
7884 const char *argv[SIZEOF_ARG] = { "git" };
7885 int argc = 1;
7887 /* When running random commands, initially show the
7888 * command in the title. However, it maybe later be
7889 * overwritten if a commit line is selected. */
7890 string_ncopy(next->ref, cmd, strlen(cmd));
7892 if (!argv_from_string(argv, &argc, cmd)) {
7893 report("Too many arguments");
7894 } else if (!prepare_update(next, argv, NULL)) {
7895 report("Failed to format command");
7896 } else {
7897 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7901 request = REQ_NONE;
7902 break;
7904 case REQ_SEARCH:
7905 case REQ_SEARCH_BACK:
7907 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7908 char *search = read_prompt(prompt);
7910 if (search)
7911 string_ncopy(opt_search, search, strlen(search));
7912 else if (*opt_search)
7913 request = request == REQ_SEARCH ?
7914 REQ_FIND_NEXT :
7915 REQ_FIND_PREV;
7916 else
7917 request = REQ_NONE;
7918 break;
7920 default:
7921 break;
7925 quit(0);
7927 return 0;