Move application of string_expand to draw_text
[tig.git] / tig.c
blobe5612c197c7320b51ea0303284ab52718ca6d9eb
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 char buf[SIZEOF_STR];
2140 if (!tigrc_system)
2141 tigrc_system = SYSCONFDIR "/tigrc";
2142 load_option_file(tigrc_system);
2144 if (!tigrc_user) {
2145 if (!home || !string_format(buf, "%s/.tigrc", home))
2146 return ERR;
2147 tigrc_user = buf;
2149 load_option_file(tigrc_user);
2151 /* Add _after_ loading config files to avoid adding run requests
2152 * that conflict with keybindings. */
2153 add_builtin_run_requests();
2155 return OK;
2160 * The viewer
2163 struct view;
2164 struct view_ops;
2166 /* The display array of active views and the index of the current view. */
2167 static struct view *display[2];
2168 static unsigned int current_view;
2170 #define foreach_displayed_view(view, i) \
2171 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2173 #define displayed_views() (display[1] != NULL ? 2 : 1)
2175 /* Current head and commit ID */
2176 static char ref_blob[SIZEOF_REF] = "";
2177 static char ref_commit[SIZEOF_REF] = "HEAD";
2178 static char ref_head[SIZEOF_REF] = "HEAD";
2179 static char ref_branch[SIZEOF_REF] = "";
2181 enum view_type {
2182 VIEW_MAIN,
2183 VIEW_DIFF,
2184 VIEW_LOG,
2185 VIEW_TREE,
2186 VIEW_BLOB,
2187 VIEW_BLAME,
2188 VIEW_BRANCH,
2189 VIEW_HELP,
2190 VIEW_PAGER,
2191 VIEW_STATUS,
2192 VIEW_STAGE,
2195 struct view {
2196 enum view_type type; /* View type */
2197 const char *name; /* View name */
2198 const char *cmd_env; /* Command line set via environment */
2199 const char *id; /* Points to either of ref_{head,commit,blob} */
2201 struct view_ops *ops; /* View operations */
2203 enum keymap keymap; /* What keymap does this view have */
2204 bool git_dir; /* Whether the view requires a git directory. */
2206 char ref[SIZEOF_REF]; /* Hovered commit reference */
2207 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2209 int height, width; /* The width and height of the main window */
2210 WINDOW *win; /* The main window */
2211 WINDOW *title; /* The title window living below the main window */
2213 /* Navigation */
2214 unsigned long offset; /* Offset of the window top */
2215 unsigned long yoffset; /* Offset from the window side. */
2216 unsigned long lineno; /* Current line number */
2217 unsigned long p_offset; /* Previous offset of the window top */
2218 unsigned long p_yoffset;/* Previous offset from the window side */
2219 unsigned long p_lineno; /* Previous current line number */
2220 bool p_restore; /* Should the previous position be restored. */
2222 /* Searching */
2223 char grep[SIZEOF_STR]; /* Search string */
2224 regex_t *regex; /* Pre-compiled regexp */
2226 /* If non-NULL, points to the view that opened this view. If this view
2227 * is closed tig will switch back to the parent view. */
2228 struct view *parent;
2229 struct view *prev;
2231 /* Buffering */
2232 size_t lines; /* Total number of lines */
2233 struct line *line; /* Line index */
2234 unsigned int digits; /* Number of digits in the lines member. */
2236 /* Drawing */
2237 struct line *curline; /* Line currently being drawn. */
2238 enum line_type curtype; /* Attribute currently used for drawing. */
2239 unsigned long col; /* Column when drawing. */
2240 bool has_scrolled; /* View was scrolled. */
2242 /* Loading */
2243 const char **argv; /* Shell command arguments. */
2244 const char *dir; /* Directory from which to execute. */
2245 struct io io;
2246 struct io *pipe;
2247 time_t start_time;
2248 time_t update_secs;
2251 struct view_ops {
2252 /* What type of content being displayed. Used in the title bar. */
2253 const char *type;
2254 /* Default command arguments. */
2255 const char **argv;
2256 /* Open and reads in all view content. */
2257 bool (*open)(struct view *view);
2258 /* Read one line; updates view->line. */
2259 bool (*read)(struct view *view, char *data);
2260 /* Draw one line; @lineno must be < view->height. */
2261 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2262 /* Depending on view handle a special requests. */
2263 enum request (*request)(struct view *view, enum request request, struct line *line);
2264 /* Search for regexp in a line. */
2265 bool (*grep)(struct view *view, struct line *line);
2266 /* Select line */
2267 void (*select)(struct view *view, struct line *line);
2268 /* Prepare view for loading */
2269 bool (*prepare)(struct view *view);
2272 static struct view_ops blame_ops;
2273 static struct view_ops blob_ops;
2274 static struct view_ops diff_ops;
2275 static struct view_ops help_ops;
2276 static struct view_ops log_ops;
2277 static struct view_ops main_ops;
2278 static struct view_ops pager_ops;
2279 static struct view_ops stage_ops;
2280 static struct view_ops status_ops;
2281 static struct view_ops tree_ops;
2282 static struct view_ops branch_ops;
2284 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2285 { type, name, #env, ref, ops, map, git }
2287 #define VIEW_(id, name, ops, git, ref) \
2288 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2290 static struct view views[] = {
2291 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2292 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2293 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2294 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2295 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2296 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2297 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2298 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2299 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2300 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2301 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2304 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2306 #define foreach_view(view, i) \
2307 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2309 #define view_is_displayed(view) \
2310 (view == display[0] || view == display[1])
2312 static enum request
2313 view_request(struct view *view, enum request request)
2315 if (!view || !view->lines)
2316 return request;
2317 return view->ops->request(view, request, &view->line[view->lineno]);
2322 * View drawing.
2325 static inline void
2326 set_view_attr(struct view *view, enum line_type type)
2328 if (!view->curline->selected && view->curtype != type) {
2329 (void) wattrset(view->win, get_line_attr(type));
2330 wchgat(view->win, -1, 0, type, NULL);
2331 view->curtype = type;
2335 static int
2336 draw_chars(struct view *view, enum line_type type, const char *string,
2337 int max_len, bool use_tilde)
2339 static char out_buffer[BUFSIZ * 2];
2340 int len = 0;
2341 int col = 0;
2342 int trimmed = FALSE;
2343 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2345 if (max_len <= 0)
2346 return 0;
2348 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2350 set_view_attr(view, type);
2351 if (len > 0) {
2352 if (opt_iconv_out != ICONV_NONE) {
2353 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2354 size_t inlen = len + 1;
2356 char *outbuf = out_buffer;
2357 size_t outlen = sizeof(out_buffer);
2359 size_t ret;
2361 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2362 if (ret != (size_t) -1) {
2363 string = out_buffer;
2364 len = sizeof(out_buffer) - outlen;
2368 waddnstr(view->win, string, len);
2370 if (trimmed && use_tilde) {
2371 set_view_attr(view, LINE_DELIMITER);
2372 waddch(view->win, '~');
2373 col++;
2376 return col;
2379 static int
2380 draw_space(struct view *view, enum line_type type, int max, int spaces)
2382 static char space[] = " ";
2383 int col = 0;
2385 spaces = MIN(max, spaces);
2387 while (spaces > 0) {
2388 int len = MIN(spaces, sizeof(space) - 1);
2390 col += draw_chars(view, type, space, len, FALSE);
2391 spaces -= len;
2394 return col;
2397 static bool
2398 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2400 char text[SIZEOF_STR];
2402 do {
2403 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2405 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2406 string += pos;
2407 } while (*string && view->width + view->yoffset > view->col);
2409 return view->width + view->yoffset <= view->col;
2412 static bool
2413 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2415 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2416 int max = view->width + view->yoffset - view->col;
2417 int i;
2419 if (max < size)
2420 size = max;
2422 set_view_attr(view, type);
2423 /* Using waddch() instead of waddnstr() ensures that
2424 * they'll be rendered correctly for the cursor line. */
2425 for (i = skip; i < size; i++)
2426 waddch(view->win, graphic[i]);
2428 view->col += size;
2429 if (size < max && skip <= size)
2430 waddch(view->win, ' ');
2431 view->col++;
2433 return view->width + view->yoffset <= view->col;
2436 static bool
2437 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2439 int max = MIN(view->width + view->yoffset - view->col, len);
2440 int col;
2442 if (text)
2443 col = draw_chars(view, type, text, max - 1, trim);
2444 else
2445 col = draw_space(view, type, max - 1, max - 1);
2447 view->col += col;
2448 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2449 return view->width + view->yoffset <= view->col;
2452 static bool
2453 draw_date(struct view *view, struct time *time)
2455 const char *date = mkdate(time, opt_date);
2456 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2458 return draw_field(view, LINE_DATE, date, cols, FALSE);
2461 static bool
2462 draw_author(struct view *view, const char *author)
2464 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2465 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2467 if (abbreviate && author)
2468 author = get_author_initials(author);
2470 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2473 static bool
2474 draw_mode(struct view *view, mode_t mode)
2476 const char *str;
2478 if (S_ISDIR(mode))
2479 str = "drwxr-xr-x";
2480 else if (S_ISLNK(mode))
2481 str = "lrwxrwxrwx";
2482 else if (S_ISGITLINK(mode))
2483 str = "m---------";
2484 else if (S_ISREG(mode) && mode & S_IXUSR)
2485 str = "-rwxr-xr-x";
2486 else if (S_ISREG(mode))
2487 str = "-rw-r--r--";
2488 else
2489 str = "----------";
2491 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2494 static bool
2495 draw_lineno(struct view *view, unsigned int lineno)
2497 char number[10];
2498 int digits3 = view->digits < 3 ? 3 : view->digits;
2499 int max = MIN(view->width + view->yoffset - view->col, digits3);
2500 char *text = NULL;
2501 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2503 lineno += view->offset + 1;
2504 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2505 static char fmt[] = "%1ld";
2507 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2508 if (string_format(number, fmt, lineno))
2509 text = number;
2511 if (text)
2512 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2513 else
2514 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2515 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2518 static bool
2519 draw_view_line(struct view *view, unsigned int lineno)
2521 struct line *line;
2522 bool selected = (view->offset + lineno == view->lineno);
2524 assert(view_is_displayed(view));
2526 if (view->offset + lineno >= view->lines)
2527 return FALSE;
2529 line = &view->line[view->offset + lineno];
2531 wmove(view->win, lineno, 0);
2532 if (line->cleareol)
2533 wclrtoeol(view->win);
2534 view->col = 0;
2535 view->curline = line;
2536 view->curtype = LINE_NONE;
2537 line->selected = FALSE;
2538 line->dirty = line->cleareol = 0;
2540 if (selected) {
2541 set_view_attr(view, LINE_CURSOR);
2542 line->selected = TRUE;
2543 view->ops->select(view, line);
2546 return view->ops->draw(view, line, lineno);
2549 static void
2550 redraw_view_dirty(struct view *view)
2552 bool dirty = FALSE;
2553 int lineno;
2555 for (lineno = 0; lineno < view->height; lineno++) {
2556 if (view->offset + lineno >= view->lines)
2557 break;
2558 if (!view->line[view->offset + lineno].dirty)
2559 continue;
2560 dirty = TRUE;
2561 if (!draw_view_line(view, lineno))
2562 break;
2565 if (!dirty)
2566 return;
2567 wnoutrefresh(view->win);
2570 static void
2571 redraw_view_from(struct view *view, int lineno)
2573 assert(0 <= lineno && lineno < view->height);
2575 for (; lineno < view->height; lineno++) {
2576 if (!draw_view_line(view, lineno))
2577 break;
2580 wnoutrefresh(view->win);
2583 static void
2584 redraw_view(struct view *view)
2586 werase(view->win);
2587 redraw_view_from(view, 0);
2591 static void
2592 update_view_title(struct view *view)
2594 char buf[SIZEOF_STR];
2595 char state[SIZEOF_STR];
2596 size_t bufpos = 0, statelen = 0;
2598 assert(view_is_displayed(view));
2600 if (view->type != VIEW_STATUS && view->lines) {
2601 unsigned int view_lines = view->offset + view->height;
2602 unsigned int lines = view->lines
2603 ? MIN(view_lines, view->lines) * 100 / view->lines
2604 : 0;
2606 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2607 view->ops->type,
2608 view->lineno + 1,
2609 view->lines,
2610 lines);
2614 if (view->pipe) {
2615 time_t secs = time(NULL) - view->start_time;
2617 /* Three git seconds are a long time ... */
2618 if (secs > 2)
2619 string_format_from(state, &statelen, " loading %lds", secs);
2622 string_format_from(buf, &bufpos, "[%s]", view->name);
2623 if (*view->ref && bufpos < view->width) {
2624 size_t refsize = strlen(view->ref);
2625 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2627 if (minsize < view->width)
2628 refsize = view->width - minsize + 7;
2629 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2632 if (statelen && bufpos < view->width) {
2633 string_format_from(buf, &bufpos, "%s", state);
2636 if (view == display[current_view])
2637 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2638 else
2639 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2641 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2642 wclrtoeol(view->title);
2643 wnoutrefresh(view->title);
2646 static int
2647 apply_step(double step, int value)
2649 if (step >= 1)
2650 return (int) step;
2651 value *= step + 0.01;
2652 return value ? value : 1;
2655 static void
2656 resize_display(void)
2658 int offset, i;
2659 struct view *base = display[0];
2660 struct view *view = display[1] ? display[1] : display[0];
2662 /* Setup window dimensions */
2664 getmaxyx(stdscr, base->height, base->width);
2666 /* Make room for the status window. */
2667 base->height -= 1;
2669 if (view != base) {
2670 /* Horizontal split. */
2671 view->width = base->width;
2672 view->height = apply_step(opt_scale_split_view, base->height);
2673 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2674 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2675 base->height -= view->height;
2677 /* Make room for the title bar. */
2678 view->height -= 1;
2681 /* Make room for the title bar. */
2682 base->height -= 1;
2684 offset = 0;
2686 foreach_displayed_view (view, i) {
2687 if (!view->win) {
2688 view->win = newwin(view->height, 0, offset, 0);
2689 if (!view->win)
2690 die("Failed to create %s view", view->name);
2692 scrollok(view->win, FALSE);
2694 view->title = newwin(1, 0, offset + view->height, 0);
2695 if (!view->title)
2696 die("Failed to create title window");
2698 } else {
2699 wresize(view->win, view->height, view->width);
2700 mvwin(view->win, offset, 0);
2701 mvwin(view->title, offset + view->height, 0);
2704 offset += view->height + 1;
2708 static void
2709 redraw_display(bool clear)
2711 struct view *view;
2712 int i;
2714 foreach_displayed_view (view, i) {
2715 if (clear)
2716 wclear(view->win);
2717 redraw_view(view);
2718 update_view_title(view);
2724 * Option management
2727 static void
2728 toggle_enum_option_do(unsigned int *opt, const char *help,
2729 const struct enum_map *map, size_t size)
2731 *opt = (*opt + 1) % size;
2732 redraw_display(FALSE);
2733 report("Displaying %s %s", enum_name(map[*opt]), help);
2736 #define toggle_enum_option(opt, help, map) \
2737 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2739 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2740 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2742 static void
2743 toggle_view_option(bool *option, const char *help)
2745 *option = !*option;
2746 redraw_display(FALSE);
2747 report("%sabling %s", *option ? "En" : "Dis", help);
2750 static void
2751 open_option_menu(void)
2753 const struct menu_item menu[] = {
2754 { '.', "line numbers", &opt_line_number },
2755 { 'D', "date display", &opt_date },
2756 { 'A', "author display", &opt_author },
2757 { 'g', "revision graph display", &opt_rev_graph },
2758 { 'F', "reference display", &opt_show_refs },
2759 { 0 }
2761 int selected = 0;
2763 if (prompt_menu("Toggle option", menu, &selected)) {
2764 if (menu[selected].data == &opt_date)
2765 toggle_date();
2766 else if (menu[selected].data == &opt_author)
2767 toggle_author();
2768 else
2769 toggle_view_option(menu[selected].data, menu[selected].text);
2773 static void
2774 maximize_view(struct view *view)
2776 memset(display, 0, sizeof(display));
2777 current_view = 0;
2778 display[current_view] = view;
2779 resize_display();
2780 redraw_display(FALSE);
2781 report("");
2786 * Navigation
2789 static bool
2790 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2792 if (lineno >= view->lines)
2793 lineno = view->lines > 0 ? view->lines - 1 : 0;
2795 if (offset > lineno || offset + view->height <= lineno) {
2796 unsigned long half = view->height / 2;
2798 if (lineno > half)
2799 offset = lineno - half;
2800 else
2801 offset = 0;
2804 if (offset != view->offset || lineno != view->lineno) {
2805 view->offset = offset;
2806 view->lineno = lineno;
2807 return TRUE;
2810 return FALSE;
2813 /* Scrolling backend */
2814 static void
2815 do_scroll_view(struct view *view, int lines)
2817 bool redraw_current_line = FALSE;
2819 /* The rendering expects the new offset. */
2820 view->offset += lines;
2822 assert(0 <= view->offset && view->offset < view->lines);
2823 assert(lines);
2825 /* Move current line into the view. */
2826 if (view->lineno < view->offset) {
2827 view->lineno = view->offset;
2828 redraw_current_line = TRUE;
2829 } else if (view->lineno >= view->offset + view->height) {
2830 view->lineno = view->offset + view->height - 1;
2831 redraw_current_line = TRUE;
2834 assert(view->offset <= view->lineno && view->lineno < view->lines);
2836 /* Redraw the whole screen if scrolling is pointless. */
2837 if (view->height < ABS(lines)) {
2838 redraw_view(view);
2840 } else {
2841 int line = lines > 0 ? view->height - lines : 0;
2842 int end = line + ABS(lines);
2844 scrollok(view->win, TRUE);
2845 wscrl(view->win, lines);
2846 scrollok(view->win, FALSE);
2848 while (line < end && draw_view_line(view, line))
2849 line++;
2851 if (redraw_current_line)
2852 draw_view_line(view, view->lineno - view->offset);
2853 wnoutrefresh(view->win);
2856 view->has_scrolled = TRUE;
2857 report("");
2860 /* Scroll frontend */
2861 static void
2862 scroll_view(struct view *view, enum request request)
2864 int lines = 1;
2866 assert(view_is_displayed(view));
2868 switch (request) {
2869 case REQ_SCROLL_LEFT:
2870 if (view->yoffset == 0) {
2871 report("Cannot scroll beyond the first column");
2872 return;
2874 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2875 view->yoffset = 0;
2876 else
2877 view->yoffset -= apply_step(opt_hscroll, view->width);
2878 redraw_view_from(view, 0);
2879 report("");
2880 return;
2881 case REQ_SCROLL_RIGHT:
2882 view->yoffset += apply_step(opt_hscroll, view->width);
2883 redraw_view(view);
2884 report("");
2885 return;
2886 case REQ_SCROLL_PAGE_DOWN:
2887 lines = view->height;
2888 case REQ_SCROLL_LINE_DOWN:
2889 if (view->offset + lines > view->lines)
2890 lines = view->lines - view->offset;
2892 if (lines == 0 || view->offset + view->height >= view->lines) {
2893 report("Cannot scroll beyond the last line");
2894 return;
2896 break;
2898 case REQ_SCROLL_PAGE_UP:
2899 lines = view->height;
2900 case REQ_SCROLL_LINE_UP:
2901 if (lines > view->offset)
2902 lines = view->offset;
2904 if (lines == 0) {
2905 report("Cannot scroll beyond the first line");
2906 return;
2909 lines = -lines;
2910 break;
2912 default:
2913 die("request %d not handled in switch", request);
2916 do_scroll_view(view, lines);
2919 /* Cursor moving */
2920 static void
2921 move_view(struct view *view, enum request request)
2923 int scroll_steps = 0;
2924 int steps;
2926 switch (request) {
2927 case REQ_MOVE_FIRST_LINE:
2928 steps = -view->lineno;
2929 break;
2931 case REQ_MOVE_LAST_LINE:
2932 steps = view->lines - view->lineno - 1;
2933 break;
2935 case REQ_MOVE_PAGE_UP:
2936 steps = view->height > view->lineno
2937 ? -view->lineno : -view->height;
2938 break;
2940 case REQ_MOVE_PAGE_DOWN:
2941 steps = view->lineno + view->height >= view->lines
2942 ? view->lines - view->lineno - 1 : view->height;
2943 break;
2945 case REQ_MOVE_UP:
2946 steps = -1;
2947 break;
2949 case REQ_MOVE_DOWN:
2950 steps = 1;
2951 break;
2953 default:
2954 die("request %d not handled in switch", request);
2957 if (steps <= 0 && view->lineno == 0) {
2958 report("Cannot move beyond the first line");
2959 return;
2961 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2962 report("Cannot move beyond the last line");
2963 return;
2966 /* Move the current line */
2967 view->lineno += steps;
2968 assert(0 <= view->lineno && view->lineno < view->lines);
2970 /* Check whether the view needs to be scrolled */
2971 if (view->lineno < view->offset ||
2972 view->lineno >= view->offset + view->height) {
2973 scroll_steps = steps;
2974 if (steps < 0 && -steps > view->offset) {
2975 scroll_steps = -view->offset;
2977 } else if (steps > 0) {
2978 if (view->lineno == view->lines - 1 &&
2979 view->lines > view->height) {
2980 scroll_steps = view->lines - view->offset - 1;
2981 if (scroll_steps >= view->height)
2982 scroll_steps -= view->height - 1;
2987 if (!view_is_displayed(view)) {
2988 view->offset += scroll_steps;
2989 assert(0 <= view->offset && view->offset < view->lines);
2990 view->ops->select(view, &view->line[view->lineno]);
2991 return;
2994 /* Repaint the old "current" line if we be scrolling */
2995 if (ABS(steps) < view->height)
2996 draw_view_line(view, view->lineno - steps - view->offset);
2998 if (scroll_steps) {
2999 do_scroll_view(view, scroll_steps);
3000 return;
3003 /* Draw the current line */
3004 draw_view_line(view, view->lineno - view->offset);
3006 wnoutrefresh(view->win);
3007 report("");
3012 * Searching
3015 static void search_view(struct view *view, enum request request);
3017 static bool
3018 grep_text(struct view *view, const char *text[])
3020 regmatch_t pmatch;
3021 size_t i;
3023 for (i = 0; text[i]; i++)
3024 if (*text[i] &&
3025 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3026 return TRUE;
3027 return FALSE;
3030 static void
3031 select_view_line(struct view *view, unsigned long lineno)
3033 unsigned long old_lineno = view->lineno;
3034 unsigned long old_offset = view->offset;
3036 if (goto_view_line(view, view->offset, lineno)) {
3037 if (view_is_displayed(view)) {
3038 if (old_offset != view->offset) {
3039 redraw_view(view);
3040 } else {
3041 draw_view_line(view, old_lineno - view->offset);
3042 draw_view_line(view, view->lineno - view->offset);
3043 wnoutrefresh(view->win);
3045 } else {
3046 view->ops->select(view, &view->line[view->lineno]);
3051 static void
3052 find_next(struct view *view, enum request request)
3054 unsigned long lineno = view->lineno;
3055 int direction;
3057 if (!*view->grep) {
3058 if (!*opt_search)
3059 report("No previous search");
3060 else
3061 search_view(view, request);
3062 return;
3065 switch (request) {
3066 case REQ_SEARCH:
3067 case REQ_FIND_NEXT:
3068 direction = 1;
3069 break;
3071 case REQ_SEARCH_BACK:
3072 case REQ_FIND_PREV:
3073 direction = -1;
3074 break;
3076 default:
3077 return;
3080 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3081 lineno += direction;
3083 /* Note, lineno is unsigned long so will wrap around in which case it
3084 * will become bigger than view->lines. */
3085 for (; lineno < view->lines; lineno += direction) {
3086 if (view->ops->grep(view, &view->line[lineno])) {
3087 select_view_line(view, lineno);
3088 report("Line %ld matches '%s'", lineno + 1, view->grep);
3089 return;
3093 report("No match found for '%s'", view->grep);
3096 static void
3097 search_view(struct view *view, enum request request)
3099 int regex_err;
3101 if (view->regex) {
3102 regfree(view->regex);
3103 *view->grep = 0;
3104 } else {
3105 view->regex = calloc(1, sizeof(*view->regex));
3106 if (!view->regex)
3107 return;
3110 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3111 if (regex_err != 0) {
3112 char buf[SIZEOF_STR] = "unknown error";
3114 regerror(regex_err, view->regex, buf, sizeof(buf));
3115 report("Search failed: %s", buf);
3116 return;
3119 string_copy(view->grep, opt_search);
3121 find_next(view, request);
3125 * Incremental updating
3128 static void
3129 reset_view(struct view *view)
3131 int i;
3133 for (i = 0; i < view->lines; i++)
3134 free(view->line[i].data);
3135 free(view->line);
3137 view->p_offset = view->offset;
3138 view->p_yoffset = view->yoffset;
3139 view->p_lineno = view->lineno;
3141 view->line = NULL;
3142 view->offset = 0;
3143 view->yoffset = 0;
3144 view->lines = 0;
3145 view->lineno = 0;
3146 view->vid[0] = 0;
3147 view->update_secs = 0;
3150 static const char *
3151 format_arg(const char *name)
3153 static struct {
3154 const char *name;
3155 size_t namelen;
3156 const char *value;
3157 const char *value_if_empty;
3158 } vars[] = {
3159 #define FORMAT_VAR(name, value, value_if_empty) \
3160 { name, STRING_SIZE(name), value, value_if_empty }
3161 FORMAT_VAR("%(directory)", opt_path, ""),
3162 FORMAT_VAR("%(file)", opt_file, ""),
3163 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3164 FORMAT_VAR("%(head)", ref_head, ""),
3165 FORMAT_VAR("%(commit)", ref_commit, ""),
3166 FORMAT_VAR("%(blob)", ref_blob, ""),
3167 FORMAT_VAR("%(branch)", ref_branch, ""),
3169 int i;
3171 for (i = 0; i < ARRAY_SIZE(vars); i++)
3172 if (!strncmp(name, vars[i].name, vars[i].namelen))
3173 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3175 report("Unknown replacement: `%s`", name);
3176 return NULL;
3179 static bool
3180 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3182 char buf[SIZEOF_STR];
3183 int argc;
3185 argv_free(*dst_argv);
3187 for (argc = 0; src_argv[argc]; argc++) {
3188 const char *arg = src_argv[argc];
3189 size_t bufpos = 0;
3191 if (!strcmp(arg, "%(file-args)")) {
3192 if (!argv_append_array(dst_argv, opt_file_args))
3193 break;
3194 continue;
3196 } else if (!strcmp(arg, "%(diff-args)")) {
3197 if (!argv_append_array(dst_argv, opt_diff_args))
3198 break;
3199 continue;
3201 } else if (!strcmp(arg, "%(rev-args)")) {
3202 if (!argv_append_array(dst_argv, opt_rev_args))
3203 break;
3204 continue;
3207 while (arg) {
3208 char *next = strstr(arg, "%(");
3209 int len = next - arg;
3210 const char *value;
3212 if (!next || !replace) {
3213 len = strlen(arg);
3214 value = "";
3216 } else {
3217 value = format_arg(next);
3219 if (!value) {
3220 return FALSE;
3224 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3225 return FALSE;
3227 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3230 if (!argv_append(dst_argv, buf))
3231 break;
3234 return src_argv[argc] == NULL;
3237 static bool
3238 restore_view_position(struct view *view)
3240 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3241 return FALSE;
3243 /* Changing the view position cancels the restoring. */
3244 /* FIXME: Changing back to the first line is not detected. */
3245 if (view->offset != 0 || view->lineno != 0) {
3246 view->p_restore = FALSE;
3247 return FALSE;
3250 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3251 view_is_displayed(view))
3252 werase(view->win);
3254 view->yoffset = view->p_yoffset;
3255 view->p_restore = FALSE;
3257 return TRUE;
3260 static void
3261 end_update(struct view *view, bool force)
3263 if (!view->pipe)
3264 return;
3265 while (!view->ops->read(view, NULL))
3266 if (!force)
3267 return;
3268 if (force)
3269 io_kill(view->pipe);
3270 io_done(view->pipe);
3271 view->pipe = NULL;
3274 static void
3275 setup_update(struct view *view, const char *vid)
3277 reset_view(view);
3278 string_copy_rev(view->vid, vid);
3279 view->pipe = &view->io;
3280 view->start_time = time(NULL);
3283 static bool
3284 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3286 view->dir = dir;
3287 return format_argv(&view->argv, argv, replace);
3290 static bool
3291 prepare_update(struct view *view, const char *argv[], const char *dir)
3293 if (view->pipe)
3294 end_update(view, TRUE);
3295 return prepare_io(view, dir, argv, FALSE);
3298 static bool
3299 start_update(struct view *view, const char **argv, const char *dir)
3301 if (view->pipe)
3302 io_done(view->pipe);
3303 return prepare_io(view, dir, argv, FALSE) &&
3304 io_run(&view->io, IO_RD, dir, view->argv);
3307 static bool
3308 prepare_update_file(struct view *view, const char *name)
3310 if (view->pipe)
3311 end_update(view, TRUE);
3312 argv_free(view->argv);
3313 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3316 static bool
3317 begin_update(struct view *view, bool refresh)
3319 if (view->pipe)
3320 end_update(view, TRUE);
3322 if (!refresh) {
3323 if (view->ops->prepare) {
3324 if (!view->ops->prepare(view))
3325 return FALSE;
3326 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3327 return FALSE;
3330 /* Put the current ref_* value to the view title ref
3331 * member. This is needed by the blob view. Most other
3332 * views sets it automatically after loading because the
3333 * first line is a commit line. */
3334 string_copy_rev(view->ref, view->id);
3337 if (view->argv && view->argv[0] &&
3338 !io_run(&view->io, IO_RD, view->dir, view->argv))
3339 return FALSE;
3341 setup_update(view, view->id);
3343 return TRUE;
3346 static bool
3347 update_view(struct view *view)
3349 char out_buffer[BUFSIZ * 2];
3350 char *line;
3351 /* Clear the view and redraw everything since the tree sorting
3352 * might have rearranged things. */
3353 bool redraw = view->lines == 0;
3354 bool can_read = TRUE;
3356 if (!view->pipe)
3357 return TRUE;
3359 if (!io_can_read(view->pipe)) {
3360 if (view->lines == 0 && view_is_displayed(view)) {
3361 time_t secs = time(NULL) - view->start_time;
3363 if (secs > 1 && secs > view->update_secs) {
3364 if (view->update_secs == 0)
3365 redraw_view(view);
3366 update_view_title(view);
3367 view->update_secs = secs;
3370 return TRUE;
3373 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3374 if (opt_iconv_in != ICONV_NONE) {
3375 ICONV_CONST char *inbuf = line;
3376 size_t inlen = strlen(line) + 1;
3378 char *outbuf = out_buffer;
3379 size_t outlen = sizeof(out_buffer);
3381 size_t ret;
3383 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3384 if (ret != (size_t) -1)
3385 line = out_buffer;
3388 if (!view->ops->read(view, line)) {
3389 report("Allocation failure");
3390 end_update(view, TRUE);
3391 return FALSE;
3396 unsigned long lines = view->lines;
3397 int digits;
3399 for (digits = 0; lines; digits++)
3400 lines /= 10;
3402 /* Keep the displayed view in sync with line number scaling. */
3403 if (digits != view->digits) {
3404 view->digits = digits;
3405 if (opt_line_number || view->type == VIEW_BLAME)
3406 redraw = TRUE;
3410 if (io_error(view->pipe)) {
3411 report("Failed to read: %s", io_strerror(view->pipe));
3412 end_update(view, TRUE);
3414 } else if (io_eof(view->pipe)) {
3415 if (view_is_displayed(view))
3416 report("");
3417 end_update(view, FALSE);
3420 if (restore_view_position(view))
3421 redraw = TRUE;
3423 if (!view_is_displayed(view))
3424 return TRUE;
3426 if (redraw)
3427 redraw_view_from(view, 0);
3428 else
3429 redraw_view_dirty(view);
3431 /* Update the title _after_ the redraw so that if the redraw picks up a
3432 * commit reference in view->ref it'll be available here. */
3433 update_view_title(view);
3434 return TRUE;
3437 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3439 static struct line *
3440 add_line_data(struct view *view, void *data, enum line_type type)
3442 struct line *line;
3444 if (!realloc_lines(&view->line, view->lines, 1))
3445 return NULL;
3447 line = &view->line[view->lines++];
3448 memset(line, 0, sizeof(*line));
3449 line->type = type;
3450 line->data = data;
3451 line->dirty = 1;
3453 return line;
3456 static struct line *
3457 add_line_text(struct view *view, const char *text, enum line_type type)
3459 char *data = text ? strdup(text) : NULL;
3461 return data ? add_line_data(view, data, type) : NULL;
3464 static struct line *
3465 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3467 char buf[SIZEOF_STR];
3468 va_list args;
3470 va_start(args, fmt);
3471 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3472 buf[0] = 0;
3473 va_end(args);
3475 return buf[0] ? add_line_text(view, buf, type) : NULL;
3479 * View opening
3482 enum open_flags {
3483 OPEN_DEFAULT = 0, /* Use default view switching. */
3484 OPEN_SPLIT = 1, /* Split current view. */
3485 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3486 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3487 OPEN_PREPARED = 32, /* Open already prepared command. */
3490 static void
3491 open_view(struct view *prev, enum request request, enum open_flags flags)
3493 bool split = !!(flags & OPEN_SPLIT);
3494 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3495 bool nomaximize = !!(flags & OPEN_REFRESH);
3496 struct view *view = VIEW(request);
3497 int nviews = displayed_views();
3498 struct view *base_view = display[0];
3500 if (view == prev && nviews == 1 && !reload) {
3501 report("Already in %s view", view->name);
3502 return;
3505 if (view->git_dir && !opt_git_dir[0]) {
3506 report("The %s view is disabled in pager view", view->name);
3507 return;
3510 if (split) {
3511 display[1] = view;
3512 current_view = 1;
3513 view->parent = prev;
3514 } else if (!nomaximize) {
3515 /* Maximize the current view. */
3516 memset(display, 0, sizeof(display));
3517 current_view = 0;
3518 display[current_view] = view;
3521 /* No prev signals that this is the first loaded view. */
3522 if (prev && view != prev) {
3523 view->prev = prev;
3526 /* Resize the view when switching between split- and full-screen,
3527 * or when switching between two different full-screen views. */
3528 if (nviews != displayed_views() ||
3529 (nviews == 1 && base_view != display[0]))
3530 resize_display();
3532 if (view->ops->open) {
3533 if (view->pipe)
3534 end_update(view, TRUE);
3535 if (!view->ops->open(view)) {
3536 report("Failed to load %s view", view->name);
3537 return;
3539 restore_view_position(view);
3541 } else if ((reload || strcmp(view->vid, view->id)) &&
3542 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3543 report("Failed to load %s view", view->name);
3544 return;
3547 if (split && prev->lineno - prev->offset >= prev->height) {
3548 /* Take the title line into account. */
3549 int lines = prev->lineno - prev->offset - prev->height + 1;
3551 /* Scroll the view that was split if the current line is
3552 * outside the new limited view. */
3553 do_scroll_view(prev, lines);
3556 if (prev && view != prev && split && view_is_displayed(prev)) {
3557 /* "Blur" the previous view. */
3558 update_view_title(prev);
3561 if (view->pipe && view->lines == 0) {
3562 /* Clear the old view and let the incremental updating refill
3563 * the screen. */
3564 werase(view->win);
3565 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3566 report("");
3567 } else if (view_is_displayed(view)) {
3568 redraw_view(view);
3569 report("");
3573 static void
3574 open_external_viewer(const char *argv[], const char *dir)
3576 def_prog_mode(); /* save current tty modes */
3577 endwin(); /* restore original tty modes */
3578 io_run_fg(argv, dir);
3579 fprintf(stderr, "Press Enter to continue");
3580 getc(opt_tty);
3581 reset_prog_mode();
3582 redraw_display(TRUE);
3585 static void
3586 open_mergetool(const char *file)
3588 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3590 open_external_viewer(mergetool_argv, opt_cdup);
3593 static void
3594 open_editor(const char *file)
3596 const char *editor_argv[] = { "vi", file, NULL };
3597 const char *editor;
3599 editor = getenv("GIT_EDITOR");
3600 if (!editor && *opt_editor)
3601 editor = opt_editor;
3602 if (!editor)
3603 editor = getenv("VISUAL");
3604 if (!editor)
3605 editor = getenv("EDITOR");
3606 if (!editor)
3607 editor = "vi";
3609 editor_argv[0] = editor;
3610 open_external_viewer(editor_argv, opt_cdup);
3613 static void
3614 open_run_request(enum request request)
3616 struct run_request *req = get_run_request(request);
3617 const char **argv = NULL;
3619 if (!req) {
3620 report("Unknown run request");
3621 return;
3624 if (format_argv(&argv, req->argv, TRUE))
3625 open_external_viewer(argv, NULL);
3626 if (argv)
3627 argv_free(argv);
3628 free(argv);
3632 * User request switch noodle
3635 static int
3636 view_driver(struct view *view, enum request request)
3638 int i;
3640 if (request == REQ_NONE)
3641 return TRUE;
3643 if (request > REQ_NONE) {
3644 open_run_request(request);
3645 view_request(view, REQ_REFRESH);
3646 return TRUE;
3649 request = view_request(view, request);
3650 if (request == REQ_NONE)
3651 return TRUE;
3653 switch (request) {
3654 case REQ_MOVE_UP:
3655 case REQ_MOVE_DOWN:
3656 case REQ_MOVE_PAGE_UP:
3657 case REQ_MOVE_PAGE_DOWN:
3658 case REQ_MOVE_FIRST_LINE:
3659 case REQ_MOVE_LAST_LINE:
3660 move_view(view, request);
3661 break;
3663 case REQ_SCROLL_LEFT:
3664 case REQ_SCROLL_RIGHT:
3665 case REQ_SCROLL_LINE_DOWN:
3666 case REQ_SCROLL_LINE_UP:
3667 case REQ_SCROLL_PAGE_DOWN:
3668 case REQ_SCROLL_PAGE_UP:
3669 scroll_view(view, request);
3670 break;
3672 case REQ_VIEW_BLAME:
3673 if (!opt_file[0]) {
3674 report("No file chosen, press %s to open tree view",
3675 get_key(view->keymap, REQ_VIEW_TREE));
3676 break;
3678 open_view(view, request, OPEN_DEFAULT);
3679 break;
3681 case REQ_VIEW_BLOB:
3682 if (!ref_blob[0]) {
3683 report("No file chosen, press %s to open tree view",
3684 get_key(view->keymap, REQ_VIEW_TREE));
3685 break;
3687 open_view(view, request, OPEN_DEFAULT);
3688 break;
3690 case REQ_VIEW_PAGER:
3691 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3692 report("No pager content, press %s to run command from prompt",
3693 get_key(view->keymap, REQ_PROMPT));
3694 break;
3696 open_view(view, request, OPEN_DEFAULT);
3697 break;
3699 case REQ_VIEW_STAGE:
3700 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3701 report("No stage content, press %s to open the status view and choose file",
3702 get_key(view->keymap, REQ_VIEW_STATUS));
3703 break;
3705 open_view(view, request, OPEN_DEFAULT);
3706 break;
3708 case REQ_VIEW_STATUS:
3709 if (opt_is_inside_work_tree == FALSE) {
3710 report("The status view requires a working tree");
3711 break;
3713 open_view(view, request, OPEN_DEFAULT);
3714 break;
3716 case REQ_VIEW_MAIN:
3717 case REQ_VIEW_DIFF:
3718 case REQ_VIEW_LOG:
3719 case REQ_VIEW_TREE:
3720 case REQ_VIEW_HELP:
3721 case REQ_VIEW_BRANCH:
3722 open_view(view, request, OPEN_DEFAULT);
3723 break;
3725 case REQ_NEXT:
3726 case REQ_PREVIOUS:
3727 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3729 if (view->parent) {
3730 int line;
3732 view = view->parent;
3733 line = view->lineno;
3734 move_view(view, request);
3735 if (view_is_displayed(view))
3736 update_view_title(view);
3737 if (line != view->lineno)
3738 view_request(view, REQ_ENTER);
3739 } else {
3740 move_view(view, request);
3742 break;
3744 case REQ_VIEW_NEXT:
3746 int nviews = displayed_views();
3747 int next_view = (current_view + 1) % nviews;
3749 if (next_view == current_view) {
3750 report("Only one view is displayed");
3751 break;
3754 current_view = next_view;
3755 /* Blur out the title of the previous view. */
3756 update_view_title(view);
3757 report("");
3758 break;
3760 case REQ_REFRESH:
3761 report("Refreshing is not yet supported for the %s view", view->name);
3762 break;
3764 case REQ_MAXIMIZE:
3765 if (displayed_views() == 2)
3766 maximize_view(view);
3767 break;
3769 case REQ_OPTIONS:
3770 open_option_menu();
3771 break;
3773 case REQ_TOGGLE_LINENO:
3774 toggle_view_option(&opt_line_number, "line numbers");
3775 break;
3777 case REQ_TOGGLE_DATE:
3778 toggle_date();
3779 break;
3781 case REQ_TOGGLE_AUTHOR:
3782 toggle_author();
3783 break;
3785 case REQ_TOGGLE_REV_GRAPH:
3786 toggle_view_option(&opt_rev_graph, "revision graph display");
3787 break;
3789 case REQ_TOGGLE_REFS:
3790 toggle_view_option(&opt_show_refs, "reference display");
3791 break;
3793 case REQ_TOGGLE_SORT_FIELD:
3794 case REQ_TOGGLE_SORT_ORDER:
3795 report("Sorting is not yet supported for the %s view", view->name);
3796 break;
3798 case REQ_SEARCH:
3799 case REQ_SEARCH_BACK:
3800 search_view(view, request);
3801 break;
3803 case REQ_FIND_NEXT:
3804 case REQ_FIND_PREV:
3805 find_next(view, request);
3806 break;
3808 case REQ_STOP_LOADING:
3809 foreach_view(view, i) {
3810 if (view->pipe)
3811 report("Stopped loading the %s view", view->name),
3812 end_update(view, TRUE);
3814 break;
3816 case REQ_SHOW_VERSION:
3817 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3818 return TRUE;
3820 case REQ_SCREEN_REDRAW:
3821 redraw_display(TRUE);
3822 break;
3824 case REQ_EDIT:
3825 report("Nothing to edit");
3826 break;
3828 case REQ_ENTER:
3829 report("Nothing to enter");
3830 break;
3832 case REQ_VIEW_CLOSE:
3833 /* XXX: Mark closed views by letting view->prev point to the
3834 * view itself. Parents to closed view should never be
3835 * followed. */
3836 if (view->prev && view->prev != view) {
3837 maximize_view(view->prev);
3838 view->prev = view;
3839 break;
3841 /* Fall-through */
3842 case REQ_QUIT:
3843 return FALSE;
3845 default:
3846 report("Unknown key, press %s for help",
3847 get_key(view->keymap, REQ_VIEW_HELP));
3848 return TRUE;
3851 return TRUE;
3856 * View backend utilities
3859 enum sort_field {
3860 ORDERBY_NAME,
3861 ORDERBY_DATE,
3862 ORDERBY_AUTHOR,
3865 struct sort_state {
3866 const enum sort_field *fields;
3867 size_t size, current;
3868 bool reverse;
3871 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3872 #define get_sort_field(state) ((state).fields[(state).current])
3873 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3875 static void
3876 sort_view(struct view *view, enum request request, struct sort_state *state,
3877 int (*compare)(const void *, const void *))
3879 switch (request) {
3880 case REQ_TOGGLE_SORT_FIELD:
3881 state->current = (state->current + 1) % state->size;
3882 break;
3884 case REQ_TOGGLE_SORT_ORDER:
3885 state->reverse = !state->reverse;
3886 break;
3887 default:
3888 die("Not a sort request");
3891 qsort(view->line, view->lines, sizeof(*view->line), compare);
3892 redraw_view(view);
3895 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3897 /* Small author cache to reduce memory consumption. It uses binary
3898 * search to lookup or find place to position new entries. No entries
3899 * are ever freed. */
3900 static const char *
3901 get_author(const char *name)
3903 static const char **authors;
3904 static size_t authors_size;
3905 int from = 0, to = authors_size - 1;
3907 while (from <= to) {
3908 size_t pos = (to + from) / 2;
3909 int cmp = strcmp(name, authors[pos]);
3911 if (!cmp)
3912 return authors[pos];
3914 if (cmp < 0)
3915 to = pos - 1;
3916 else
3917 from = pos + 1;
3920 if (!realloc_authors(&authors, authors_size, 1))
3921 return NULL;
3922 name = strdup(name);
3923 if (!name)
3924 return NULL;
3926 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3927 authors[from] = name;
3928 authors_size++;
3930 return name;
3933 static void
3934 parse_timesec(struct time *time, const char *sec)
3936 time->sec = (time_t) atol(sec);
3939 static void
3940 parse_timezone(struct time *time, const char *zone)
3942 long tz;
3944 tz = ('0' - zone[1]) * 60 * 60 * 10;
3945 tz += ('0' - zone[2]) * 60 * 60;
3946 tz += ('0' - zone[3]) * 60 * 10;
3947 tz += ('0' - zone[4]) * 60;
3949 if (zone[0] == '-')
3950 tz = -tz;
3952 time->tz = tz;
3953 time->sec -= tz;
3956 /* Parse author lines where the name may be empty:
3957 * author <email@address.tld> 1138474660 +0100
3959 static void
3960 parse_author_line(char *ident, const char **author, struct time *time)
3962 char *nameend = strchr(ident, '<');
3963 char *emailend = strchr(ident, '>');
3965 if (nameend && emailend)
3966 *nameend = *emailend = 0;
3967 ident = chomp_string(ident);
3968 if (!*ident) {
3969 if (nameend)
3970 ident = chomp_string(nameend + 1);
3971 if (!*ident)
3972 ident = "Unknown";
3975 *author = get_author(ident);
3977 /* Parse epoch and timezone */
3978 if (emailend && emailend[1] == ' ') {
3979 char *secs = emailend + 2;
3980 char *zone = strchr(secs, ' ');
3982 parse_timesec(time, secs);
3984 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3985 parse_timezone(time, zone + 1);
3990 * Pager backend
3993 static bool
3994 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3996 if (opt_line_number && draw_lineno(view, lineno))
3997 return TRUE;
3999 draw_text(view, line->type, line->data, TRUE);
4000 return TRUE;
4003 static bool
4004 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4006 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4007 char ref[SIZEOF_STR];
4009 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4010 return TRUE;
4012 /* This is the only fatal call, since it can "corrupt" the buffer. */
4013 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4014 return FALSE;
4016 return TRUE;
4019 static void
4020 add_pager_refs(struct view *view, struct line *line)
4022 char buf[SIZEOF_STR];
4023 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4024 struct ref_list *list;
4025 size_t bufpos = 0, i;
4026 const char *sep = "Refs: ";
4027 bool is_tag = FALSE;
4029 assert(line->type == LINE_COMMIT);
4031 list = get_ref_list(commit_id);
4032 if (!list) {
4033 if (view->type == VIEW_DIFF)
4034 goto try_add_describe_ref;
4035 return;
4038 for (i = 0; i < list->size; i++) {
4039 struct ref *ref = list->refs[i];
4040 const char *fmt = ref->tag ? "%s[%s]" :
4041 ref->remote ? "%s<%s>" : "%s%s";
4043 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4044 return;
4045 sep = ", ";
4046 if (ref->tag)
4047 is_tag = TRUE;
4050 if (!is_tag && view->type == VIEW_DIFF) {
4051 try_add_describe_ref:
4052 /* Add <tag>-g<commit_id> "fake" reference. */
4053 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4054 return;
4057 if (bufpos == 0)
4058 return;
4060 add_line_text(view, buf, LINE_PP_REFS);
4063 static bool
4064 pager_read(struct view *view, char *data)
4066 struct line *line;
4068 if (!data)
4069 return TRUE;
4071 line = add_line_text(view, data, get_line_type(data));
4072 if (!line)
4073 return FALSE;
4075 if (line->type == LINE_COMMIT &&
4076 (view->type == VIEW_DIFF ||
4077 view->type == VIEW_LOG))
4078 add_pager_refs(view, line);
4080 return TRUE;
4083 static enum request
4084 pager_request(struct view *view, enum request request, struct line *line)
4086 int split = 0;
4088 if (request != REQ_ENTER)
4089 return request;
4091 if (line->type == LINE_COMMIT &&
4092 (view->type == VIEW_LOG ||
4093 view->type == VIEW_PAGER)) {
4094 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4095 split = 1;
4098 /* Always scroll the view even if it was split. That way
4099 * you can use Enter to scroll through the log view and
4100 * split open each commit diff. */
4101 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4103 /* FIXME: A minor workaround. Scrolling the view will call report("")
4104 * but if we are scrolling a non-current view this won't properly
4105 * update the view title. */
4106 if (split)
4107 update_view_title(view);
4109 return REQ_NONE;
4112 static bool
4113 pager_grep(struct view *view, struct line *line)
4115 const char *text[] = { line->data, NULL };
4117 return grep_text(view, text);
4120 static void
4121 pager_select(struct view *view, struct line *line)
4123 if (line->type == LINE_COMMIT) {
4124 char *text = (char *)line->data + STRING_SIZE("commit ");
4126 if (view->type != VIEW_PAGER)
4127 string_copy_rev(view->ref, text);
4128 string_copy_rev(ref_commit, text);
4132 static struct view_ops pager_ops = {
4133 "line",
4134 NULL,
4135 NULL,
4136 pager_read,
4137 pager_draw,
4138 pager_request,
4139 pager_grep,
4140 pager_select,
4143 static const char *log_argv[SIZEOF_ARG] = {
4144 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4147 static enum request
4148 log_request(struct view *view, enum request request, struct line *line)
4150 switch (request) {
4151 case REQ_REFRESH:
4152 load_refs();
4153 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4154 return REQ_NONE;
4155 default:
4156 return pager_request(view, request, line);
4160 static struct view_ops log_ops = {
4161 "line",
4162 log_argv,
4163 NULL,
4164 pager_read,
4165 pager_draw,
4166 log_request,
4167 pager_grep,
4168 pager_select,
4171 static const char *diff_argv[SIZEOF_ARG] = {
4172 "git", "show", "--pretty=fuller", "--no-color", "--root",
4173 "--patch-with-stat", "--find-copies-harder", "-C",
4174 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4177 static struct view_ops diff_ops = {
4178 "line",
4179 diff_argv,
4180 NULL,
4181 pager_read,
4182 pager_draw,
4183 pager_request,
4184 pager_grep,
4185 pager_select,
4189 * Help backend
4192 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4194 static bool
4195 help_open_keymap_title(struct view *view, enum keymap keymap)
4197 struct line *line;
4199 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4200 help_keymap_hidden[keymap] ? '+' : '-',
4201 enum_name(keymap_table[keymap]));
4202 if (line)
4203 line->other = keymap;
4205 return help_keymap_hidden[keymap];
4208 static void
4209 help_open_keymap(struct view *view, enum keymap keymap)
4211 const char *group = NULL;
4212 char buf[SIZEOF_STR];
4213 size_t bufpos;
4214 bool add_title = TRUE;
4215 int i;
4217 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4218 const char *key = NULL;
4220 if (req_info[i].request == REQ_NONE)
4221 continue;
4223 if (!req_info[i].request) {
4224 group = req_info[i].help;
4225 continue;
4228 key = get_keys(keymap, req_info[i].request, TRUE);
4229 if (!key || !*key)
4230 continue;
4232 if (add_title && help_open_keymap_title(view, keymap))
4233 return;
4234 add_title = FALSE;
4236 if (group) {
4237 add_line_text(view, group, LINE_HELP_GROUP);
4238 group = NULL;
4241 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4242 enum_name(req_info[i]), req_info[i].help);
4245 group = "External commands:";
4247 for (i = 0; i < run_requests; i++) {
4248 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4249 const char *key;
4250 int argc;
4252 if (!req || req->keymap != keymap)
4253 continue;
4255 key = get_key_name(req->key);
4256 if (!*key)
4257 key = "(no key defined)";
4259 if (add_title && help_open_keymap_title(view, keymap))
4260 return;
4261 if (group) {
4262 add_line_text(view, group, LINE_HELP_GROUP);
4263 group = NULL;
4266 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4267 if (!string_format_from(buf, &bufpos, "%s%s",
4268 argc ? " " : "", req->argv[argc]))
4269 return;
4271 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4275 static bool
4276 help_open(struct view *view)
4278 enum keymap keymap;
4280 reset_view(view);
4281 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4282 add_line_text(view, "", LINE_DEFAULT);
4284 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4285 help_open_keymap(view, keymap);
4287 return TRUE;
4290 static enum request
4291 help_request(struct view *view, enum request request, struct line *line)
4293 switch (request) {
4294 case REQ_ENTER:
4295 if (line->type == LINE_HELP_KEYMAP) {
4296 help_keymap_hidden[line->other] =
4297 !help_keymap_hidden[line->other];
4298 view->p_restore = TRUE;
4299 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4302 return REQ_NONE;
4303 default:
4304 return pager_request(view, request, line);
4308 static struct view_ops help_ops = {
4309 "line",
4310 NULL,
4311 help_open,
4312 NULL,
4313 pager_draw,
4314 help_request,
4315 pager_grep,
4316 pager_select,
4321 * Tree backend
4324 struct tree_stack_entry {
4325 struct tree_stack_entry *prev; /* Entry below this in the stack */
4326 unsigned long lineno; /* Line number to restore */
4327 char *name; /* Position of name in opt_path */
4330 /* The top of the path stack. */
4331 static struct tree_stack_entry *tree_stack = NULL;
4332 unsigned long tree_lineno = 0;
4334 static void
4335 pop_tree_stack_entry(void)
4337 struct tree_stack_entry *entry = tree_stack;
4339 tree_lineno = entry->lineno;
4340 entry->name[0] = 0;
4341 tree_stack = entry->prev;
4342 free(entry);
4345 static void
4346 push_tree_stack_entry(const char *name, unsigned long lineno)
4348 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4349 size_t pathlen = strlen(opt_path);
4351 if (!entry)
4352 return;
4354 entry->prev = tree_stack;
4355 entry->name = opt_path + pathlen;
4356 tree_stack = entry;
4358 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4359 pop_tree_stack_entry();
4360 return;
4363 /* Move the current line to the first tree entry. */
4364 tree_lineno = 1;
4365 entry->lineno = lineno;
4368 /* Parse output from git-ls-tree(1):
4370 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4373 #define SIZEOF_TREE_ATTR \
4374 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4376 #define SIZEOF_TREE_MODE \
4377 STRING_SIZE("100644 ")
4379 #define TREE_ID_OFFSET \
4380 STRING_SIZE("100644 blob ")
4382 struct tree_entry {
4383 char id[SIZEOF_REV];
4384 mode_t mode;
4385 struct time time; /* Date from the author ident. */
4386 const char *author; /* Author of the commit. */
4387 char name[1];
4390 static const char *
4391 tree_path(const struct line *line)
4393 return ((struct tree_entry *) line->data)->name;
4396 static int
4397 tree_compare_entry(const struct line *line1, const struct line *line2)
4399 if (line1->type != line2->type)
4400 return line1->type == LINE_TREE_DIR ? -1 : 1;
4401 return strcmp(tree_path(line1), tree_path(line2));
4404 static const enum sort_field tree_sort_fields[] = {
4405 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4407 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4409 static int
4410 tree_compare(const void *l1, const void *l2)
4412 const struct line *line1 = (const struct line *) l1;
4413 const struct line *line2 = (const struct line *) l2;
4414 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4415 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4417 if (line1->type == LINE_TREE_HEAD)
4418 return -1;
4419 if (line2->type == LINE_TREE_HEAD)
4420 return 1;
4422 switch (get_sort_field(tree_sort_state)) {
4423 case ORDERBY_DATE:
4424 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4426 case ORDERBY_AUTHOR:
4427 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4429 case ORDERBY_NAME:
4430 default:
4431 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4436 static struct line *
4437 tree_entry(struct view *view, enum line_type type, const char *path,
4438 const char *mode, const char *id)
4440 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4441 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4443 if (!entry || !line) {
4444 free(entry);
4445 return NULL;
4448 strncpy(entry->name, path, strlen(path));
4449 if (mode)
4450 entry->mode = strtoul(mode, NULL, 8);
4451 if (id)
4452 string_copy_rev(entry->id, id);
4454 return line;
4457 static bool
4458 tree_read_date(struct view *view, char *text, bool *read_date)
4460 static const char *author_name;
4461 static struct time author_time;
4463 if (!text && *read_date) {
4464 *read_date = FALSE;
4465 return TRUE;
4467 } else if (!text) {
4468 char *path = *opt_path ? opt_path : ".";
4469 /* Find next entry to process */
4470 const char *log_file[] = {
4471 "git", "log", "--no-color", "--pretty=raw",
4472 "--cc", "--raw", view->id, "--", path, NULL
4475 if (!view->lines) {
4476 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4477 report("Tree is empty");
4478 return TRUE;
4481 if (!start_update(view, log_file, opt_cdup)) {
4482 report("Failed to load tree data");
4483 return TRUE;
4486 *read_date = TRUE;
4487 return FALSE;
4489 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4490 parse_author_line(text + STRING_SIZE("author "),
4491 &author_name, &author_time);
4493 } else if (*text == ':') {
4494 char *pos;
4495 size_t annotated = 1;
4496 size_t i;
4498 pos = strchr(text, '\t');
4499 if (!pos)
4500 return TRUE;
4501 text = pos + 1;
4502 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4503 text += strlen(opt_path);
4504 pos = strchr(text, '/');
4505 if (pos)
4506 *pos = 0;
4508 for (i = 1; i < view->lines; i++) {
4509 struct line *line = &view->line[i];
4510 struct tree_entry *entry = line->data;
4512 annotated += !!entry->author;
4513 if (entry->author || strcmp(entry->name, text))
4514 continue;
4516 entry->author = author_name;
4517 entry->time = author_time;
4518 line->dirty = 1;
4519 break;
4522 if (annotated == view->lines)
4523 io_kill(view->pipe);
4525 return TRUE;
4528 static bool
4529 tree_read(struct view *view, char *text)
4531 static bool read_date = FALSE;
4532 struct tree_entry *data;
4533 struct line *entry, *line;
4534 enum line_type type;
4535 size_t textlen = text ? strlen(text) : 0;
4536 char *path = text + SIZEOF_TREE_ATTR;
4538 if (read_date || !text)
4539 return tree_read_date(view, text, &read_date);
4541 if (textlen <= SIZEOF_TREE_ATTR)
4542 return FALSE;
4543 if (view->lines == 0 &&
4544 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4545 return FALSE;
4547 /* Strip the path part ... */
4548 if (*opt_path) {
4549 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4550 size_t striplen = strlen(opt_path);
4552 if (pathlen > striplen)
4553 memmove(path, path + striplen,
4554 pathlen - striplen + 1);
4556 /* Insert "link" to parent directory. */
4557 if (view->lines == 1 &&
4558 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4559 return FALSE;
4562 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4563 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4564 if (!entry)
4565 return FALSE;
4566 data = entry->data;
4568 /* Skip "Directory ..." and ".." line. */
4569 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4570 if (tree_compare_entry(line, entry) <= 0)
4571 continue;
4573 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4575 line->data = data;
4576 line->type = type;
4577 for (; line <= entry; line++)
4578 line->dirty = line->cleareol = 1;
4579 return TRUE;
4582 if (tree_lineno > view->lineno) {
4583 view->lineno = tree_lineno;
4584 tree_lineno = 0;
4587 return TRUE;
4590 static bool
4591 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4593 struct tree_entry *entry = line->data;
4595 if (line->type == LINE_TREE_HEAD) {
4596 if (draw_text(view, line->type, "Directory path /", TRUE))
4597 return TRUE;
4598 } else {
4599 if (draw_mode(view, entry->mode))
4600 return TRUE;
4602 if (opt_author && draw_author(view, entry->author))
4603 return TRUE;
4605 if (opt_date && draw_date(view, &entry->time))
4606 return TRUE;
4608 if (draw_text(view, line->type, entry->name, TRUE))
4609 return TRUE;
4610 return TRUE;
4613 static void
4614 open_blob_editor(const char *id)
4616 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4617 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4618 int fd = mkstemp(file);
4620 if (fd == -1)
4621 report("Failed to create temporary file");
4622 else if (!io_run_append(blob_argv, fd))
4623 report("Failed to save blob data to file");
4624 else
4625 open_editor(file);
4626 if (fd != -1)
4627 unlink(file);
4630 static enum request
4631 tree_request(struct view *view, enum request request, struct line *line)
4633 enum open_flags flags;
4634 struct tree_entry *entry = line->data;
4636 switch (request) {
4637 case REQ_VIEW_BLAME:
4638 if (line->type != LINE_TREE_FILE) {
4639 report("Blame only supported for files");
4640 return REQ_NONE;
4643 string_copy(opt_ref, view->vid);
4644 return request;
4646 case REQ_EDIT:
4647 if (line->type != LINE_TREE_FILE) {
4648 report("Edit only supported for files");
4649 } else if (!is_head_commit(view->vid)) {
4650 open_blob_editor(entry->id);
4651 } else {
4652 open_editor(opt_file);
4654 return REQ_NONE;
4656 case REQ_TOGGLE_SORT_FIELD:
4657 case REQ_TOGGLE_SORT_ORDER:
4658 sort_view(view, request, &tree_sort_state, tree_compare);
4659 return REQ_NONE;
4661 case REQ_PARENT:
4662 if (!*opt_path) {
4663 /* quit view if at top of tree */
4664 return REQ_VIEW_CLOSE;
4666 /* fake 'cd ..' */
4667 line = &view->line[1];
4668 break;
4670 case REQ_ENTER:
4671 break;
4673 default:
4674 return request;
4677 /* Cleanup the stack if the tree view is at a different tree. */
4678 while (!*opt_path && tree_stack)
4679 pop_tree_stack_entry();
4681 switch (line->type) {
4682 case LINE_TREE_DIR:
4683 /* Depending on whether it is a subdirectory or parent link
4684 * mangle the path buffer. */
4685 if (line == &view->line[1] && *opt_path) {
4686 pop_tree_stack_entry();
4688 } else {
4689 const char *basename = tree_path(line);
4691 push_tree_stack_entry(basename, view->lineno);
4694 /* Trees and subtrees share the same ID, so they are not not
4695 * unique like blobs. */
4696 flags = OPEN_RELOAD;
4697 request = REQ_VIEW_TREE;
4698 break;
4700 case LINE_TREE_FILE:
4701 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4702 request = REQ_VIEW_BLOB;
4703 break;
4705 default:
4706 return REQ_NONE;
4709 open_view(view, request, flags);
4710 if (request == REQ_VIEW_TREE)
4711 view->lineno = tree_lineno;
4713 return REQ_NONE;
4716 static bool
4717 tree_grep(struct view *view, struct line *line)
4719 struct tree_entry *entry = line->data;
4720 const char *text[] = {
4721 entry->name,
4722 opt_author ? entry->author : "",
4723 mkdate(&entry->time, opt_date),
4724 NULL
4727 return grep_text(view, text);
4730 static void
4731 tree_select(struct view *view, struct line *line)
4733 struct tree_entry *entry = line->data;
4735 if (line->type == LINE_TREE_FILE) {
4736 string_copy_rev(ref_blob, entry->id);
4737 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4739 } else if (line->type != LINE_TREE_DIR) {
4740 return;
4743 string_copy_rev(view->ref, entry->id);
4746 static bool
4747 tree_prepare(struct view *view)
4749 if (view->lines == 0 && opt_prefix[0]) {
4750 char *pos = opt_prefix;
4752 while (pos && *pos) {
4753 char *end = strchr(pos, '/');
4755 if (end)
4756 *end = 0;
4757 push_tree_stack_entry(pos, 0);
4758 pos = end;
4759 if (end) {
4760 *end = '/';
4761 pos++;
4765 } else if (strcmp(view->vid, view->id)) {
4766 opt_path[0] = 0;
4769 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4772 static const char *tree_argv[SIZEOF_ARG] = {
4773 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4776 static struct view_ops tree_ops = {
4777 "file",
4778 tree_argv,
4779 NULL,
4780 tree_read,
4781 tree_draw,
4782 tree_request,
4783 tree_grep,
4784 tree_select,
4785 tree_prepare,
4788 static bool
4789 blob_read(struct view *view, char *line)
4791 if (!line)
4792 return TRUE;
4793 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4796 static enum request
4797 blob_request(struct view *view, enum request request, struct line *line)
4799 switch (request) {
4800 case REQ_EDIT:
4801 open_blob_editor(view->vid);
4802 return REQ_NONE;
4803 default:
4804 return pager_request(view, request, line);
4808 static const char *blob_argv[SIZEOF_ARG] = {
4809 "git", "cat-file", "blob", "%(blob)", NULL
4812 static struct view_ops blob_ops = {
4813 "line",
4814 blob_argv,
4815 NULL,
4816 blob_read,
4817 pager_draw,
4818 blob_request,
4819 pager_grep,
4820 pager_select,
4824 * Blame backend
4826 * Loading the blame view is a two phase job:
4828 * 1. File content is read either using opt_file from the
4829 * filesystem or using git-cat-file.
4830 * 2. Then blame information is incrementally added by
4831 * reading output from git-blame.
4834 struct blame_commit {
4835 char id[SIZEOF_REV]; /* SHA1 ID. */
4836 char title[128]; /* First line of the commit message. */
4837 const char *author; /* Author of the commit. */
4838 struct time time; /* Date from the author ident. */
4839 char filename[128]; /* Name of file. */
4840 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4841 char parent_filename[128]; /* Parent/previous name of file. */
4844 struct blame {
4845 struct blame_commit *commit;
4846 unsigned long lineno;
4847 char text[1];
4850 static bool
4851 blame_open(struct view *view)
4853 char path[SIZEOF_STR];
4854 size_t i;
4856 if (!view->prev && *opt_prefix) {
4857 string_copy(path, opt_file);
4858 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4859 return FALSE;
4862 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4863 const char *blame_cat_file_argv[] = {
4864 "git", "cat-file", "blob", path, NULL
4867 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4868 !start_update(view, blame_cat_file_argv, opt_cdup))
4869 return FALSE;
4872 /* First pass: remove multiple references to the same commit. */
4873 for (i = 0; i < view->lines; i++) {
4874 struct blame *blame = view->line[i].data;
4876 if (blame->commit && blame->commit->id[0])
4877 blame->commit->id[0] = 0;
4878 else
4879 blame->commit = NULL;
4882 /* Second pass: free existing references. */
4883 for (i = 0; i < view->lines; i++) {
4884 struct blame *blame = view->line[i].data;
4886 if (blame->commit)
4887 free(blame->commit);
4890 setup_update(view, opt_file);
4891 string_format(view->ref, "%s ...", opt_file);
4893 return TRUE;
4896 static struct blame_commit *
4897 get_blame_commit(struct view *view, const char *id)
4899 size_t i;
4901 for (i = 0; i < view->lines; i++) {
4902 struct blame *blame = view->line[i].data;
4904 if (!blame->commit)
4905 continue;
4907 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4908 return blame->commit;
4912 struct blame_commit *commit = calloc(1, sizeof(*commit));
4914 if (commit)
4915 string_ncopy(commit->id, id, SIZEOF_REV);
4916 return commit;
4920 static bool
4921 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4923 const char *pos = *posref;
4925 *posref = NULL;
4926 pos = strchr(pos + 1, ' ');
4927 if (!pos || !isdigit(pos[1]))
4928 return FALSE;
4929 *number = atoi(pos + 1);
4930 if (*number < min || *number > max)
4931 return FALSE;
4933 *posref = pos;
4934 return TRUE;
4937 static struct blame_commit *
4938 parse_blame_commit(struct view *view, const char *text, int *blamed)
4940 struct blame_commit *commit;
4941 struct blame *blame;
4942 const char *pos = text + SIZEOF_REV - 2;
4943 size_t orig_lineno = 0;
4944 size_t lineno;
4945 size_t group;
4947 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4948 return NULL;
4950 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4951 !parse_number(&pos, &lineno, 1, view->lines) ||
4952 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4953 return NULL;
4955 commit = get_blame_commit(view, text);
4956 if (!commit)
4957 return NULL;
4959 *blamed += group;
4960 while (group--) {
4961 struct line *line = &view->line[lineno + group - 1];
4963 blame = line->data;
4964 blame->commit = commit;
4965 blame->lineno = orig_lineno + group - 1;
4966 line->dirty = 1;
4969 return commit;
4972 static bool
4973 blame_read_file(struct view *view, const char *line, bool *read_file)
4975 if (!line) {
4976 const char *blame_argv[] = {
4977 "git", "blame", "--incremental",
4978 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4981 if (view->lines == 0 && !view->prev)
4982 die("No blame exist for %s", view->vid);
4984 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4985 report("Failed to load blame data");
4986 return TRUE;
4989 *read_file = FALSE;
4990 return FALSE;
4992 } else {
4993 size_t linelen = strlen(line);
4994 struct blame *blame = malloc(sizeof(*blame) + linelen);
4996 if (!blame)
4997 return FALSE;
4999 blame->commit = NULL;
5000 strncpy(blame->text, line, linelen);
5001 blame->text[linelen] = 0;
5002 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5006 static bool
5007 match_blame_header(const char *name, char **line)
5009 size_t namelen = strlen(name);
5010 bool matched = !strncmp(name, *line, namelen);
5012 if (matched)
5013 *line += namelen;
5015 return matched;
5018 static bool
5019 blame_read(struct view *view, char *line)
5021 static struct blame_commit *commit = NULL;
5022 static int blamed = 0;
5023 static bool read_file = TRUE;
5025 if (read_file)
5026 return blame_read_file(view, line, &read_file);
5028 if (!line) {
5029 /* Reset all! */
5030 commit = NULL;
5031 blamed = 0;
5032 read_file = TRUE;
5033 string_format(view->ref, "%s", view->vid);
5034 if (view_is_displayed(view)) {
5035 update_view_title(view);
5036 redraw_view_from(view, 0);
5038 return TRUE;
5041 if (!commit) {
5042 commit = parse_blame_commit(view, line, &blamed);
5043 string_format(view->ref, "%s %2d%%", view->vid,
5044 view->lines ? blamed * 100 / view->lines : 0);
5046 } else if (match_blame_header("author ", &line)) {
5047 commit->author = get_author(line);
5049 } else if (match_blame_header("author-time ", &line)) {
5050 parse_timesec(&commit->time, line);
5052 } else if (match_blame_header("author-tz ", &line)) {
5053 parse_timezone(&commit->time, line);
5055 } else if (match_blame_header("summary ", &line)) {
5056 string_ncopy(commit->title, line, strlen(line));
5058 } else if (match_blame_header("previous ", &line)) {
5059 if (strlen(line) <= SIZEOF_REV)
5060 return FALSE;
5061 string_copy_rev(commit->parent_id, line);
5062 line += SIZEOF_REV;
5063 string_ncopy(commit->parent_filename, line, strlen(line));
5065 } else if (match_blame_header("filename ", &line)) {
5066 string_ncopy(commit->filename, line, strlen(line));
5067 commit = NULL;
5070 return TRUE;
5073 static bool
5074 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5076 struct blame *blame = line->data;
5077 struct time *time = NULL;
5078 const char *id = NULL, *author = NULL;
5080 if (blame->commit && *blame->commit->filename) {
5081 id = blame->commit->id;
5082 author = blame->commit->author;
5083 time = &blame->commit->time;
5086 if (opt_date && draw_date(view, time))
5087 return TRUE;
5089 if (opt_author && draw_author(view, author))
5090 return TRUE;
5092 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5093 return TRUE;
5095 if (draw_lineno(view, lineno))
5096 return TRUE;
5098 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5099 return TRUE;
5102 static bool
5103 check_blame_commit(struct blame *blame, bool check_null_id)
5105 if (!blame->commit)
5106 report("Commit data not loaded yet");
5107 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5108 report("No commit exist for the selected line");
5109 else
5110 return TRUE;
5111 return FALSE;
5114 static void
5115 setup_blame_parent_line(struct view *view, struct blame *blame)
5117 char from[SIZEOF_REF + SIZEOF_STR];
5118 char to[SIZEOF_REF + SIZEOF_STR];
5119 const char *diff_tree_argv[] = {
5120 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5121 "-U0", from, to, "--", NULL
5123 struct io io;
5124 int parent_lineno = -1;
5125 int blamed_lineno = -1;
5126 char *line;
5128 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5129 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5130 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5131 return;
5133 while ((line = io_get(&io, '\n', TRUE))) {
5134 if (*line == '@') {
5135 char *pos = strchr(line, '+');
5137 parent_lineno = atoi(line + 4);
5138 if (pos)
5139 blamed_lineno = atoi(pos + 1);
5141 } else if (*line == '+' && parent_lineno != -1) {
5142 if (blame->lineno == blamed_lineno - 1 &&
5143 !strcmp(blame->text, line + 1)) {
5144 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5145 break;
5147 blamed_lineno++;
5151 io_done(&io);
5154 static enum request
5155 blame_request(struct view *view, enum request request, struct line *line)
5157 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5158 struct blame *blame = line->data;
5160 switch (request) {
5161 case REQ_VIEW_BLAME:
5162 if (check_blame_commit(blame, TRUE)) {
5163 string_copy(opt_ref, blame->commit->id);
5164 string_copy(opt_file, blame->commit->filename);
5165 if (blame->lineno)
5166 view->lineno = blame->lineno;
5167 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5169 break;
5171 case REQ_PARENT:
5172 if (!check_blame_commit(blame, TRUE))
5173 break;
5174 if (!*blame->commit->parent_id) {
5175 report("The selected commit has no parents");
5176 } else {
5177 string_copy_rev(opt_ref, blame->commit->parent_id);
5178 string_copy(opt_file, blame->commit->parent_filename);
5179 setup_blame_parent_line(view, blame);
5180 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5182 break;
5184 case REQ_ENTER:
5185 if (!check_blame_commit(blame, FALSE))
5186 break;
5188 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5189 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5190 break;
5192 if (!strcmp(blame->commit->id, NULL_ID)) {
5193 struct view *diff = VIEW(REQ_VIEW_DIFF);
5194 const char *diff_index_argv[] = {
5195 "git", "diff-index", "--root", "--patch-with-stat",
5196 "-C", "-M", "HEAD", "--", view->vid, NULL
5199 if (!*blame->commit->parent_id) {
5200 diff_index_argv[1] = "diff";
5201 diff_index_argv[2] = "--no-color";
5202 diff_index_argv[6] = "--";
5203 diff_index_argv[7] = "/dev/null";
5206 if (!prepare_update(diff, diff_index_argv, NULL)) {
5207 report("Failed to allocate diff command");
5208 break;
5210 flags |= OPEN_PREPARED;
5213 open_view(view, REQ_VIEW_DIFF, flags);
5214 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5215 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5216 break;
5218 default:
5219 return request;
5222 return REQ_NONE;
5225 static bool
5226 blame_grep(struct view *view, struct line *line)
5228 struct blame *blame = line->data;
5229 struct blame_commit *commit = blame->commit;
5230 const char *text[] = {
5231 blame->text,
5232 commit ? commit->title : "",
5233 commit ? commit->id : "",
5234 commit && opt_author ? commit->author : "",
5235 commit ? mkdate(&commit->time, opt_date) : "",
5236 NULL
5239 return grep_text(view, text);
5242 static void
5243 blame_select(struct view *view, struct line *line)
5245 struct blame *blame = line->data;
5246 struct blame_commit *commit = blame->commit;
5248 if (!commit)
5249 return;
5251 if (!strcmp(commit->id, NULL_ID))
5252 string_ncopy(ref_commit, "HEAD", 4);
5253 else
5254 string_copy_rev(ref_commit, commit->id);
5257 static struct view_ops blame_ops = {
5258 "line",
5259 NULL,
5260 blame_open,
5261 blame_read,
5262 blame_draw,
5263 blame_request,
5264 blame_grep,
5265 blame_select,
5269 * Branch backend
5272 struct branch {
5273 const char *author; /* Author of the last commit. */
5274 struct time time; /* Date of the last activity. */
5275 const struct ref *ref; /* Name and commit ID information. */
5278 static const struct ref branch_all;
5280 static const enum sort_field branch_sort_fields[] = {
5281 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5283 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5285 static int
5286 branch_compare(const void *l1, const void *l2)
5288 const struct branch *branch1 = ((const struct line *) l1)->data;
5289 const struct branch *branch2 = ((const struct line *) l2)->data;
5291 switch (get_sort_field(branch_sort_state)) {
5292 case ORDERBY_DATE:
5293 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5295 case ORDERBY_AUTHOR:
5296 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5298 case ORDERBY_NAME:
5299 default:
5300 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5304 static bool
5305 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5307 struct branch *branch = line->data;
5308 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5310 if (opt_date && draw_date(view, &branch->time))
5311 return TRUE;
5313 if (opt_author && draw_author(view, branch->author))
5314 return TRUE;
5316 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5317 return TRUE;
5320 static enum request
5321 branch_request(struct view *view, enum request request, struct line *line)
5323 struct branch *branch = line->data;
5325 switch (request) {
5326 case REQ_REFRESH:
5327 load_refs();
5328 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5329 return REQ_NONE;
5331 case REQ_TOGGLE_SORT_FIELD:
5332 case REQ_TOGGLE_SORT_ORDER:
5333 sort_view(view, request, &branch_sort_state, branch_compare);
5334 return REQ_NONE;
5336 case REQ_ENTER:
5338 const struct ref *ref = branch->ref;
5339 const char *all_branches_argv[] = {
5340 "git", "log", "--no-color", "--pretty=raw", "--parents",
5341 "--topo-order",
5342 ref == &branch_all ? "--all" : ref->name, NULL
5344 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5346 if (!prepare_update(main_view, all_branches_argv, NULL))
5347 report("Failed to load view of all branches");
5348 else
5349 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5350 return REQ_NONE;
5352 default:
5353 return request;
5357 static bool
5358 branch_read(struct view *view, char *line)
5360 static char id[SIZEOF_REV];
5361 struct branch *reference;
5362 size_t i;
5364 if (!line)
5365 return TRUE;
5367 switch (get_line_type(line)) {
5368 case LINE_COMMIT:
5369 string_copy_rev(id, line + STRING_SIZE("commit "));
5370 return TRUE;
5372 case LINE_AUTHOR:
5373 for (i = 0, reference = NULL; i < view->lines; i++) {
5374 struct branch *branch = view->line[i].data;
5376 if (strcmp(branch->ref->id, id))
5377 continue;
5379 view->line[i].dirty = TRUE;
5380 if (reference) {
5381 branch->author = reference->author;
5382 branch->time = reference->time;
5383 continue;
5386 parse_author_line(line + STRING_SIZE("author "),
5387 &branch->author, &branch->time);
5388 reference = branch;
5390 return TRUE;
5392 default:
5393 return TRUE;
5398 static bool
5399 branch_open_visitor(void *data, const struct ref *ref)
5401 struct view *view = data;
5402 struct branch *branch;
5404 if (ref->tag || ref->ltag || ref->remote)
5405 return TRUE;
5407 branch = calloc(1, sizeof(*branch));
5408 if (!branch)
5409 return FALSE;
5411 branch->ref = ref;
5412 return !!add_line_data(view, branch, LINE_DEFAULT);
5415 static bool
5416 branch_open(struct view *view)
5418 const char *branch_log[] = {
5419 "git", "log", "--no-color", "--pretty=raw",
5420 "--simplify-by-decoration", "--all", NULL
5423 if (!start_update(view, branch_log, NULL)) {
5424 report("Failed to load branch data");
5425 return TRUE;
5428 setup_update(view, view->id);
5429 branch_open_visitor(view, &branch_all);
5430 foreach_ref(branch_open_visitor, view);
5431 view->p_restore = TRUE;
5433 return TRUE;
5436 static bool
5437 branch_grep(struct view *view, struct line *line)
5439 struct branch *branch = line->data;
5440 const char *text[] = {
5441 branch->ref->name,
5442 branch->author,
5443 NULL
5446 return grep_text(view, text);
5449 static void
5450 branch_select(struct view *view, struct line *line)
5452 struct branch *branch = line->data;
5454 string_copy_rev(view->ref, branch->ref->id);
5455 string_copy_rev(ref_commit, branch->ref->id);
5456 string_copy_rev(ref_head, branch->ref->id);
5457 string_copy_rev(ref_branch, branch->ref->name);
5460 static struct view_ops branch_ops = {
5461 "branch",
5462 NULL,
5463 branch_open,
5464 branch_read,
5465 branch_draw,
5466 branch_request,
5467 branch_grep,
5468 branch_select,
5472 * Status backend
5475 struct status {
5476 char status;
5477 struct {
5478 mode_t mode;
5479 char rev[SIZEOF_REV];
5480 char name[SIZEOF_STR];
5481 } old;
5482 struct {
5483 mode_t mode;
5484 char rev[SIZEOF_REV];
5485 char name[SIZEOF_STR];
5486 } new;
5489 static char status_onbranch[SIZEOF_STR];
5490 static struct status stage_status;
5491 static enum line_type stage_line_type;
5492 static size_t stage_chunks;
5493 static int *stage_chunk;
5495 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5497 /* This should work even for the "On branch" line. */
5498 static inline bool
5499 status_has_none(struct view *view, struct line *line)
5501 return line < view->line + view->lines && !line[1].data;
5504 /* Get fields from the diff line:
5505 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5507 static inline bool
5508 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5510 const char *old_mode = buf + 1;
5511 const char *new_mode = buf + 8;
5512 const char *old_rev = buf + 15;
5513 const char *new_rev = buf + 56;
5514 const char *status = buf + 97;
5516 if (bufsize < 98 ||
5517 old_mode[-1] != ':' ||
5518 new_mode[-1] != ' ' ||
5519 old_rev[-1] != ' ' ||
5520 new_rev[-1] != ' ' ||
5521 status[-1] != ' ')
5522 return FALSE;
5524 file->status = *status;
5526 string_copy_rev(file->old.rev, old_rev);
5527 string_copy_rev(file->new.rev, new_rev);
5529 file->old.mode = strtoul(old_mode, NULL, 8);
5530 file->new.mode = strtoul(new_mode, NULL, 8);
5532 file->old.name[0] = file->new.name[0] = 0;
5534 return TRUE;
5537 static bool
5538 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5540 struct status *unmerged = NULL;
5541 char *buf;
5542 struct io io;
5544 if (!io_run(&io, IO_RD, opt_cdup, argv))
5545 return FALSE;
5547 add_line_data(view, NULL, type);
5549 while ((buf = io_get(&io, 0, TRUE))) {
5550 struct status *file = unmerged;
5552 if (!file) {
5553 file = calloc(1, sizeof(*file));
5554 if (!file || !add_line_data(view, file, type))
5555 goto error_out;
5558 /* Parse diff info part. */
5559 if (status) {
5560 file->status = status;
5561 if (status == 'A')
5562 string_copy(file->old.rev, NULL_ID);
5564 } else if (!file->status || file == unmerged) {
5565 if (!status_get_diff(file, buf, strlen(buf)))
5566 goto error_out;
5568 buf = io_get(&io, 0, TRUE);
5569 if (!buf)
5570 break;
5572 /* Collapse all modified entries that follow an
5573 * associated unmerged entry. */
5574 if (unmerged == file) {
5575 unmerged->status = 'U';
5576 unmerged = NULL;
5577 } else if (file->status == 'U') {
5578 unmerged = file;
5582 /* Grab the old name for rename/copy. */
5583 if (!*file->old.name &&
5584 (file->status == 'R' || file->status == 'C')) {
5585 string_ncopy(file->old.name, buf, strlen(buf));
5587 buf = io_get(&io, 0, TRUE);
5588 if (!buf)
5589 break;
5592 /* git-ls-files just delivers a NUL separated list of
5593 * file names similar to the second half of the
5594 * git-diff-* output. */
5595 string_ncopy(file->new.name, buf, strlen(buf));
5596 if (!*file->old.name)
5597 string_copy(file->old.name, file->new.name);
5598 file = NULL;
5601 if (io_error(&io)) {
5602 error_out:
5603 io_done(&io);
5604 return FALSE;
5607 if (!view->line[view->lines - 1].data)
5608 add_line_data(view, NULL, LINE_STAT_NONE);
5610 io_done(&io);
5611 return TRUE;
5614 /* Don't show unmerged entries in the staged section. */
5615 static const char *status_diff_index_argv[] = {
5616 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5617 "--cached", "-M", "HEAD", NULL
5620 static const char *status_diff_files_argv[] = {
5621 "git", "diff-files", "-z", NULL
5624 static const char *status_list_other_argv[] = {
5625 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5628 static const char *status_list_no_head_argv[] = {
5629 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5632 static const char *update_index_argv[] = {
5633 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5636 /* Restore the previous line number to stay in the context or select a
5637 * line with something that can be updated. */
5638 static void
5639 status_restore(struct view *view)
5641 if (view->p_lineno >= view->lines)
5642 view->p_lineno = view->lines - 1;
5643 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5644 view->p_lineno++;
5645 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5646 view->p_lineno--;
5648 /* If the above fails, always skip the "On branch" line. */
5649 if (view->p_lineno < view->lines)
5650 view->lineno = view->p_lineno;
5651 else
5652 view->lineno = 1;
5654 if (view->lineno < view->offset)
5655 view->offset = view->lineno;
5656 else if (view->offset + view->height <= view->lineno)
5657 view->offset = view->lineno - view->height + 1;
5659 view->p_restore = FALSE;
5662 static void
5663 status_update_onbranch(void)
5665 static const char *paths[][2] = {
5666 { "rebase-apply/rebasing", "Rebasing" },
5667 { "rebase-apply/applying", "Applying mailbox" },
5668 { "rebase-apply/", "Rebasing mailbox" },
5669 { "rebase-merge/interactive", "Interactive rebase" },
5670 { "rebase-merge/", "Rebase merge" },
5671 { "MERGE_HEAD", "Merging" },
5672 { "BISECT_LOG", "Bisecting" },
5673 { "HEAD", "On branch" },
5675 char buf[SIZEOF_STR];
5676 struct stat stat;
5677 int i;
5679 if (is_initial_commit()) {
5680 string_copy(status_onbranch, "Initial commit");
5681 return;
5684 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5685 char *head = opt_head;
5687 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5688 lstat(buf, &stat) < 0)
5689 continue;
5691 if (!*opt_head) {
5692 struct io io;
5694 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5695 io_read_buf(&io, buf, sizeof(buf))) {
5696 head = buf;
5697 if (!prefixcmp(head, "refs/heads/"))
5698 head += STRING_SIZE("refs/heads/");
5702 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5703 string_copy(status_onbranch, opt_head);
5704 return;
5707 string_copy(status_onbranch, "Not currently on any branch");
5710 /* First parse staged info using git-diff-index(1), then parse unstaged
5711 * info using git-diff-files(1), and finally untracked files using
5712 * git-ls-files(1). */
5713 static bool
5714 status_open(struct view *view)
5716 reset_view(view);
5718 add_line_data(view, NULL, LINE_STAT_HEAD);
5719 status_update_onbranch();
5721 io_run_bg(update_index_argv);
5723 if (is_initial_commit()) {
5724 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5725 return FALSE;
5726 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5727 return FALSE;
5730 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5731 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5732 return FALSE;
5734 /* Restore the exact position or use the specialized restore
5735 * mode? */
5736 if (!view->p_restore)
5737 status_restore(view);
5738 return TRUE;
5741 static bool
5742 status_draw(struct view *view, struct line *line, unsigned int lineno)
5744 struct status *status = line->data;
5745 enum line_type type;
5746 const char *text;
5748 if (!status) {
5749 switch (line->type) {
5750 case LINE_STAT_STAGED:
5751 type = LINE_STAT_SECTION;
5752 text = "Changes to be committed:";
5753 break;
5755 case LINE_STAT_UNSTAGED:
5756 type = LINE_STAT_SECTION;
5757 text = "Changed but not updated:";
5758 break;
5760 case LINE_STAT_UNTRACKED:
5761 type = LINE_STAT_SECTION;
5762 text = "Untracked files:";
5763 break;
5765 case LINE_STAT_NONE:
5766 type = LINE_DEFAULT;
5767 text = " (no files)";
5768 break;
5770 case LINE_STAT_HEAD:
5771 type = LINE_STAT_HEAD;
5772 text = status_onbranch;
5773 break;
5775 default:
5776 return FALSE;
5778 } else {
5779 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5781 buf[0] = status->status;
5782 if (draw_text(view, line->type, buf, TRUE))
5783 return TRUE;
5784 type = LINE_DEFAULT;
5785 text = status->new.name;
5788 draw_text(view, type, text, TRUE);
5789 return TRUE;
5792 static enum request
5793 status_load_error(struct view *view, struct view *stage, const char *path)
5795 if (displayed_views() == 2 || display[current_view] != view)
5796 maximize_view(view);
5797 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5798 return REQ_NONE;
5801 static enum request
5802 status_enter(struct view *view, struct line *line)
5804 struct status *status = line->data;
5805 const char *oldpath = status ? status->old.name : NULL;
5806 /* Diffs for unmerged entries are empty when passing the new
5807 * path, so leave it empty. */
5808 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5809 const char *info;
5810 enum open_flags split;
5811 struct view *stage = VIEW(REQ_VIEW_STAGE);
5813 if (line->type == LINE_STAT_NONE ||
5814 (!status && line[1].type == LINE_STAT_NONE)) {
5815 report("No file to diff");
5816 return REQ_NONE;
5819 switch (line->type) {
5820 case LINE_STAT_STAGED:
5821 if (is_initial_commit()) {
5822 const char *no_head_diff_argv[] = {
5823 "git", "diff", "--no-color", "--patch-with-stat",
5824 "--", "/dev/null", newpath, NULL
5827 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5828 return status_load_error(view, stage, newpath);
5829 } else {
5830 const char *index_show_argv[] = {
5831 "git", "diff-index", "--root", "--patch-with-stat",
5832 "-C", "-M", "--cached", "HEAD", "--",
5833 oldpath, newpath, NULL
5836 if (!prepare_update(stage, index_show_argv, opt_cdup))
5837 return status_load_error(view, stage, newpath);
5840 if (status)
5841 info = "Staged changes to %s";
5842 else
5843 info = "Staged changes";
5844 break;
5846 case LINE_STAT_UNSTAGED:
5848 const char *files_show_argv[] = {
5849 "git", "diff-files", "--root", "--patch-with-stat",
5850 "-C", "-M", "--", oldpath, newpath, NULL
5853 if (!prepare_update(stage, files_show_argv, opt_cdup))
5854 return status_load_error(view, stage, newpath);
5855 if (status)
5856 info = "Unstaged changes to %s";
5857 else
5858 info = "Unstaged changes";
5859 break;
5861 case LINE_STAT_UNTRACKED:
5862 if (!newpath) {
5863 report("No file to show");
5864 return REQ_NONE;
5867 if (!suffixcmp(status->new.name, -1, "/")) {
5868 report("Cannot display a directory");
5869 return REQ_NONE;
5872 if (!prepare_update_file(stage, newpath))
5873 return status_load_error(view, stage, newpath);
5874 info = "Untracked file %s";
5875 break;
5877 case LINE_STAT_HEAD:
5878 return REQ_NONE;
5880 default:
5881 die("line type %d not handled in switch", line->type);
5884 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5885 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5886 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5887 if (status) {
5888 stage_status = *status;
5889 } else {
5890 memset(&stage_status, 0, sizeof(stage_status));
5893 stage_line_type = line->type;
5894 stage_chunks = 0;
5895 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5898 return REQ_NONE;
5901 static bool
5902 status_exists(struct status *status, enum line_type type)
5904 struct view *view = VIEW(REQ_VIEW_STATUS);
5905 unsigned long lineno;
5907 for (lineno = 0; lineno < view->lines; lineno++) {
5908 struct line *line = &view->line[lineno];
5909 struct status *pos = line->data;
5911 if (line->type != type)
5912 continue;
5913 if (!pos && (!status || !status->status) && line[1].data) {
5914 select_view_line(view, lineno);
5915 return TRUE;
5917 if (pos && !strcmp(status->new.name, pos->new.name)) {
5918 select_view_line(view, lineno);
5919 return TRUE;
5923 return FALSE;
5927 static bool
5928 status_update_prepare(struct io *io, enum line_type type)
5930 const char *staged_argv[] = {
5931 "git", "update-index", "-z", "--index-info", NULL
5933 const char *others_argv[] = {
5934 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5937 switch (type) {
5938 case LINE_STAT_STAGED:
5939 return io_run(io, IO_WR, opt_cdup, staged_argv);
5941 case LINE_STAT_UNSTAGED:
5942 case LINE_STAT_UNTRACKED:
5943 return io_run(io, IO_WR, opt_cdup, others_argv);
5945 default:
5946 die("line type %d not handled in switch", type);
5947 return FALSE;
5951 static bool
5952 status_update_write(struct io *io, struct status *status, enum line_type type)
5954 char buf[SIZEOF_STR];
5955 size_t bufsize = 0;
5957 switch (type) {
5958 case LINE_STAT_STAGED:
5959 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5960 status->old.mode,
5961 status->old.rev,
5962 status->old.name, 0))
5963 return FALSE;
5964 break;
5966 case LINE_STAT_UNSTAGED:
5967 case LINE_STAT_UNTRACKED:
5968 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5969 return FALSE;
5970 break;
5972 default:
5973 die("line type %d not handled in switch", type);
5976 return io_write(io, buf, bufsize);
5979 static bool
5980 status_update_file(struct status *status, enum line_type type)
5982 struct io io;
5983 bool result;
5985 if (!status_update_prepare(&io, type))
5986 return FALSE;
5988 result = status_update_write(&io, status, type);
5989 return io_done(&io) && result;
5992 static bool
5993 status_update_files(struct view *view, struct line *line)
5995 char buf[sizeof(view->ref)];
5996 struct io io;
5997 bool result = TRUE;
5998 struct line *pos = view->line + view->lines;
5999 int files = 0;
6000 int file, done;
6001 int cursor_y = -1, cursor_x = -1;
6003 if (!status_update_prepare(&io, line->type))
6004 return FALSE;
6006 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6007 files++;
6009 string_copy(buf, view->ref);
6010 getsyx(cursor_y, cursor_x);
6011 for (file = 0, done = 5; result && file < files; line++, file++) {
6012 int almost_done = file * 100 / files;
6014 if (almost_done > done) {
6015 done = almost_done;
6016 string_format(view->ref, "updating file %u of %u (%d%% done)",
6017 file, files, done);
6018 update_view_title(view);
6019 setsyx(cursor_y, cursor_x);
6020 doupdate();
6022 result = status_update_write(&io, line->data, line->type);
6024 string_copy(view->ref, buf);
6026 return io_done(&io) && result;
6029 static bool
6030 status_update(struct view *view)
6032 struct line *line = &view->line[view->lineno];
6034 assert(view->lines);
6036 if (!line->data) {
6037 /* This should work even for the "On branch" line. */
6038 if (line < view->line + view->lines && !line[1].data) {
6039 report("Nothing to update");
6040 return FALSE;
6043 if (!status_update_files(view, line + 1)) {
6044 report("Failed to update file status");
6045 return FALSE;
6048 } else if (!status_update_file(line->data, line->type)) {
6049 report("Failed to update file status");
6050 return FALSE;
6053 return TRUE;
6056 static bool
6057 status_revert(struct status *status, enum line_type type, bool has_none)
6059 if (!status || type != LINE_STAT_UNSTAGED) {
6060 if (type == LINE_STAT_STAGED) {
6061 report("Cannot revert changes to staged files");
6062 } else if (type == LINE_STAT_UNTRACKED) {
6063 report("Cannot revert changes to untracked files");
6064 } else if (has_none) {
6065 report("Nothing to revert");
6066 } else {
6067 report("Cannot revert changes to multiple files");
6070 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6071 char mode[10] = "100644";
6072 const char *reset_argv[] = {
6073 "git", "update-index", "--cacheinfo", mode,
6074 status->old.rev, status->old.name, NULL
6076 const char *checkout_argv[] = {
6077 "git", "checkout", "--", status->old.name, NULL
6080 if (status->status == 'U') {
6081 string_format(mode, "%5o", status->old.mode);
6083 if (status->old.mode == 0 && status->new.mode == 0) {
6084 reset_argv[2] = "--force-remove";
6085 reset_argv[3] = status->old.name;
6086 reset_argv[4] = NULL;
6089 if (!io_run_fg(reset_argv, opt_cdup))
6090 return FALSE;
6091 if (status->old.mode == 0 && status->new.mode == 0)
6092 return TRUE;
6095 return io_run_fg(checkout_argv, opt_cdup);
6098 return FALSE;
6101 static enum request
6102 status_request(struct view *view, enum request request, struct line *line)
6104 struct status *status = line->data;
6106 switch (request) {
6107 case REQ_STATUS_UPDATE:
6108 if (!status_update(view))
6109 return REQ_NONE;
6110 break;
6112 case REQ_STATUS_REVERT:
6113 if (!status_revert(status, line->type, status_has_none(view, line)))
6114 return REQ_NONE;
6115 break;
6117 case REQ_STATUS_MERGE:
6118 if (!status || status->status != 'U') {
6119 report("Merging only possible for files with unmerged status ('U').");
6120 return REQ_NONE;
6122 open_mergetool(status->new.name);
6123 break;
6125 case REQ_EDIT:
6126 if (!status)
6127 return request;
6128 if (status->status == 'D') {
6129 report("File has been deleted.");
6130 return REQ_NONE;
6133 open_editor(status->new.name);
6134 break;
6136 case REQ_VIEW_BLAME:
6137 if (status)
6138 opt_ref[0] = 0;
6139 return request;
6141 case REQ_ENTER:
6142 /* After returning the status view has been split to
6143 * show the stage view. No further reloading is
6144 * necessary. */
6145 return status_enter(view, line);
6147 case REQ_REFRESH:
6148 /* Simply reload the view. */
6149 break;
6151 default:
6152 return request;
6155 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6157 return REQ_NONE;
6160 static void
6161 status_select(struct view *view, struct line *line)
6163 struct status *status = line->data;
6164 char file[SIZEOF_STR] = "all files";
6165 const char *text;
6166 const char *key;
6168 if (status && !string_format(file, "'%s'", status->new.name))
6169 return;
6171 if (!status && line[1].type == LINE_STAT_NONE)
6172 line++;
6174 switch (line->type) {
6175 case LINE_STAT_STAGED:
6176 text = "Press %s to unstage %s for commit";
6177 break;
6179 case LINE_STAT_UNSTAGED:
6180 text = "Press %s to stage %s for commit";
6181 break;
6183 case LINE_STAT_UNTRACKED:
6184 text = "Press %s to stage %s for addition";
6185 break;
6187 case LINE_STAT_HEAD:
6188 case LINE_STAT_NONE:
6189 text = "Nothing to update";
6190 break;
6192 default:
6193 die("line type %d not handled in switch", line->type);
6196 if (status && status->status == 'U') {
6197 text = "Press %s to resolve conflict in %s";
6198 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6200 } else {
6201 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6204 string_format(view->ref, text, key, file);
6205 if (status)
6206 string_copy(opt_file, status->new.name);
6209 static bool
6210 status_grep(struct view *view, struct line *line)
6212 struct status *status = line->data;
6214 if (status) {
6215 const char buf[2] = { status->status, 0 };
6216 const char *text[] = { status->new.name, buf, NULL };
6218 return grep_text(view, text);
6221 return FALSE;
6224 static struct view_ops status_ops = {
6225 "file",
6226 NULL,
6227 status_open,
6228 NULL,
6229 status_draw,
6230 status_request,
6231 status_grep,
6232 status_select,
6236 static bool
6237 stage_diff_write(struct io *io, struct line *line, struct line *end)
6239 while (line < end) {
6240 if (!io_write(io, line->data, strlen(line->data)) ||
6241 !io_write(io, "\n", 1))
6242 return FALSE;
6243 line++;
6244 if (line->type == LINE_DIFF_CHUNK ||
6245 line->type == LINE_DIFF_HEADER)
6246 break;
6249 return TRUE;
6252 static struct line *
6253 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6255 for (; view->line < line; line--)
6256 if (line->type == type)
6257 return line;
6259 return NULL;
6262 static bool
6263 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6265 const char *apply_argv[SIZEOF_ARG] = {
6266 "git", "apply", "--whitespace=nowarn", NULL
6268 struct line *diff_hdr;
6269 struct io io;
6270 int argc = 3;
6272 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6273 if (!diff_hdr)
6274 return FALSE;
6276 if (!revert)
6277 apply_argv[argc++] = "--cached";
6278 if (revert || stage_line_type == LINE_STAT_STAGED)
6279 apply_argv[argc++] = "-R";
6280 apply_argv[argc++] = "-";
6281 apply_argv[argc++] = NULL;
6282 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6283 return FALSE;
6285 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6286 !stage_diff_write(&io, chunk, view->line + view->lines))
6287 chunk = NULL;
6289 io_done(&io);
6290 io_run_bg(update_index_argv);
6292 return chunk ? TRUE : FALSE;
6295 static bool
6296 stage_update(struct view *view, struct line *line)
6298 struct line *chunk = NULL;
6300 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6301 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6303 if (chunk) {
6304 if (!stage_apply_chunk(view, chunk, FALSE)) {
6305 report("Failed to apply chunk");
6306 return FALSE;
6309 } else if (!stage_status.status) {
6310 view = VIEW(REQ_VIEW_STATUS);
6312 for (line = view->line; line < view->line + view->lines; line++)
6313 if (line->type == stage_line_type)
6314 break;
6316 if (!status_update_files(view, line + 1)) {
6317 report("Failed to update files");
6318 return FALSE;
6321 } else if (!status_update_file(&stage_status, stage_line_type)) {
6322 report("Failed to update file");
6323 return FALSE;
6326 return TRUE;
6329 static bool
6330 stage_revert(struct view *view, struct line *line)
6332 struct line *chunk = NULL;
6334 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6335 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6337 if (chunk) {
6338 if (!prompt_yesno("Are you sure you want to revert changes?"))
6339 return FALSE;
6341 if (!stage_apply_chunk(view, chunk, TRUE)) {
6342 report("Failed to revert chunk");
6343 return FALSE;
6345 return TRUE;
6347 } else {
6348 return status_revert(stage_status.status ? &stage_status : NULL,
6349 stage_line_type, FALSE);
6354 static void
6355 stage_next(struct view *view, struct line *line)
6357 int i;
6359 if (!stage_chunks) {
6360 for (line = view->line; line < view->line + view->lines; line++) {
6361 if (line->type != LINE_DIFF_CHUNK)
6362 continue;
6364 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6365 report("Allocation failure");
6366 return;
6369 stage_chunk[stage_chunks++] = line - view->line;
6373 for (i = 0; i < stage_chunks; i++) {
6374 if (stage_chunk[i] > view->lineno) {
6375 do_scroll_view(view, stage_chunk[i] - view->lineno);
6376 report("Chunk %d of %d", i + 1, stage_chunks);
6377 return;
6381 report("No next chunk found");
6384 static enum request
6385 stage_request(struct view *view, enum request request, struct line *line)
6387 switch (request) {
6388 case REQ_STATUS_UPDATE:
6389 if (!stage_update(view, line))
6390 return REQ_NONE;
6391 break;
6393 case REQ_STATUS_REVERT:
6394 if (!stage_revert(view, line))
6395 return REQ_NONE;
6396 break;
6398 case REQ_STAGE_NEXT:
6399 if (stage_line_type == LINE_STAT_UNTRACKED) {
6400 report("File is untracked; press %s to add",
6401 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6402 return REQ_NONE;
6404 stage_next(view, line);
6405 return REQ_NONE;
6407 case REQ_EDIT:
6408 if (!stage_status.new.name[0])
6409 return request;
6410 if (stage_status.status == 'D') {
6411 report("File has been deleted.");
6412 return REQ_NONE;
6415 open_editor(stage_status.new.name);
6416 break;
6418 case REQ_REFRESH:
6419 /* Reload everything ... */
6420 break;
6422 case REQ_VIEW_BLAME:
6423 if (stage_status.new.name[0]) {
6424 string_copy(opt_file, stage_status.new.name);
6425 opt_ref[0] = 0;
6427 return request;
6429 case REQ_ENTER:
6430 return pager_request(view, request, line);
6432 default:
6433 return request;
6436 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6437 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6439 /* Check whether the staged entry still exists, and close the
6440 * stage view if it doesn't. */
6441 if (!status_exists(&stage_status, stage_line_type)) {
6442 status_restore(VIEW(REQ_VIEW_STATUS));
6443 return REQ_VIEW_CLOSE;
6446 if (stage_line_type == LINE_STAT_UNTRACKED) {
6447 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6448 report("Cannot display a directory");
6449 return REQ_NONE;
6452 if (!prepare_update_file(view, stage_status.new.name)) {
6453 report("Failed to open file: %s", strerror(errno));
6454 return REQ_NONE;
6457 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6459 return REQ_NONE;
6462 static struct view_ops stage_ops = {
6463 "line",
6464 NULL,
6465 NULL,
6466 pager_read,
6467 pager_draw,
6468 stage_request,
6469 pager_grep,
6470 pager_select,
6475 * Revision graph
6478 struct commit {
6479 char id[SIZEOF_REV]; /* SHA1 ID. */
6480 char title[128]; /* First line of the commit message. */
6481 const char *author; /* Author of the commit. */
6482 struct time time; /* Date from the author ident. */
6483 struct ref_list *refs; /* Repository references. */
6484 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6485 size_t graph_size; /* The width of the graph array. */
6486 bool has_parents; /* Rewritten --parents seen. */
6489 /* Size of rev graph with no "padding" columns */
6490 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6492 struct rev_graph {
6493 struct rev_graph *prev, *next, *parents;
6494 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6495 size_t size;
6496 struct commit *commit;
6497 size_t pos;
6498 unsigned int boundary:1;
6501 /* Parents of the commit being visualized. */
6502 static struct rev_graph graph_parents[4];
6504 /* The current stack of revisions on the graph. */
6505 static struct rev_graph graph_stacks[4] = {
6506 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6507 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6508 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6509 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6512 static inline bool
6513 graph_parent_is_merge(struct rev_graph *graph)
6515 return graph->parents->size > 1;
6518 static inline void
6519 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6521 struct commit *commit = graph->commit;
6523 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6524 commit->graph[commit->graph_size++] = symbol;
6527 static void
6528 clear_rev_graph(struct rev_graph *graph)
6530 graph->boundary = 0;
6531 graph->size = graph->pos = 0;
6532 graph->commit = NULL;
6533 memset(graph->parents, 0, sizeof(*graph->parents));
6536 static void
6537 done_rev_graph(struct rev_graph *graph)
6539 if (graph_parent_is_merge(graph) &&
6540 graph->pos < graph->size - 1 &&
6541 graph->next->size == graph->size + graph->parents->size - 1) {
6542 size_t i = graph->pos + graph->parents->size - 1;
6544 graph->commit->graph_size = i * 2;
6545 while (i < graph->next->size - 1) {
6546 append_to_rev_graph(graph, ' ');
6547 append_to_rev_graph(graph, '\\');
6548 i++;
6552 clear_rev_graph(graph);
6555 static void
6556 push_rev_graph(struct rev_graph *graph, const char *parent)
6558 int i;
6560 /* "Collapse" duplicate parents lines.
6562 * FIXME: This needs to also update update the drawn graph but
6563 * for now it just serves as a method for pruning graph lines. */
6564 for (i = 0; i < graph->size; i++)
6565 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6566 return;
6568 if (graph->size < SIZEOF_REVITEMS) {
6569 string_copy_rev(graph->rev[graph->size++], parent);
6573 static chtype
6574 get_rev_graph_symbol(struct rev_graph *graph)
6576 chtype symbol;
6578 if (graph->boundary)
6579 symbol = REVGRAPH_BOUND;
6580 else if (graph->parents->size == 0)
6581 symbol = REVGRAPH_INIT;
6582 else if (graph_parent_is_merge(graph))
6583 symbol = REVGRAPH_MERGE;
6584 else if (graph->pos >= graph->size)
6585 symbol = REVGRAPH_BRANCH;
6586 else
6587 symbol = REVGRAPH_COMMIT;
6589 return symbol;
6592 static void
6593 draw_rev_graph(struct rev_graph *graph)
6595 struct rev_filler {
6596 chtype separator, line;
6598 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6599 static struct rev_filler fillers[] = {
6600 { ' ', '|' },
6601 { '`', '.' },
6602 { '\'', ' ' },
6603 { '/', ' ' },
6605 chtype symbol = get_rev_graph_symbol(graph);
6606 struct rev_filler *filler;
6607 size_t i;
6609 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6610 filler = &fillers[DEFAULT];
6612 for (i = 0; i < graph->pos; i++) {
6613 append_to_rev_graph(graph, filler->line);
6614 if (graph_parent_is_merge(graph->prev) &&
6615 graph->prev->pos == i)
6616 filler = &fillers[RSHARP];
6618 append_to_rev_graph(graph, filler->separator);
6621 /* Place the symbol for this revision. */
6622 append_to_rev_graph(graph, symbol);
6624 if (graph->prev->size > graph->size)
6625 filler = &fillers[RDIAG];
6626 else
6627 filler = &fillers[DEFAULT];
6629 i++;
6631 for (; i < graph->size; i++) {
6632 append_to_rev_graph(graph, filler->separator);
6633 append_to_rev_graph(graph, filler->line);
6634 if (graph_parent_is_merge(graph->prev) &&
6635 i < graph->prev->pos + graph->parents->size)
6636 filler = &fillers[RSHARP];
6637 if (graph->prev->size > graph->size)
6638 filler = &fillers[LDIAG];
6641 if (graph->prev->size > graph->size) {
6642 append_to_rev_graph(graph, filler->separator);
6643 if (filler->line != ' ')
6644 append_to_rev_graph(graph, filler->line);
6648 /* Prepare the next rev graph */
6649 static void
6650 prepare_rev_graph(struct rev_graph *graph)
6652 size_t i;
6654 /* First, traverse all lines of revisions up to the active one. */
6655 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6656 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6657 break;
6659 push_rev_graph(graph->next, graph->rev[graph->pos]);
6662 /* Interleave the new revision parent(s). */
6663 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6664 push_rev_graph(graph->next, graph->parents->rev[i]);
6666 /* Lastly, put any remaining revisions. */
6667 for (i = graph->pos + 1; i < graph->size; i++)
6668 push_rev_graph(graph->next, graph->rev[i]);
6671 static void
6672 update_rev_graph(struct view *view, struct rev_graph *graph)
6674 /* If this is the finalizing update ... */
6675 if (graph->commit)
6676 prepare_rev_graph(graph);
6678 /* Graph visualization needs a one rev look-ahead,
6679 * so the first update doesn't visualize anything. */
6680 if (!graph->prev->commit)
6681 return;
6683 if (view->lines > 2)
6684 view->line[view->lines - 3].dirty = 1;
6685 if (view->lines > 1)
6686 view->line[view->lines - 2].dirty = 1;
6687 draw_rev_graph(graph->prev);
6688 done_rev_graph(graph->prev->prev);
6693 * Main view backend
6696 static const char *main_argv[SIZEOF_ARG] = {
6697 "git", "log", "--no-color", "--pretty=raw", "--parents",
6698 "--topo-order", "%(diff-args)", "%(rev-args)",
6699 "--", "%(file-args)", NULL
6702 static bool
6703 main_draw(struct view *view, struct line *line, unsigned int lineno)
6705 struct commit *commit = line->data;
6707 if (!commit->author)
6708 return FALSE;
6710 if (opt_date && draw_date(view, &commit->time))
6711 return TRUE;
6713 if (opt_author && draw_author(view, commit->author))
6714 return TRUE;
6716 if (opt_rev_graph && commit->graph_size &&
6717 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6718 return TRUE;
6720 if (opt_show_refs && commit->refs) {
6721 size_t i;
6723 for (i = 0; i < commit->refs->size; i++) {
6724 struct ref *ref = commit->refs->refs[i];
6725 enum line_type type;
6727 if (ref->head)
6728 type = LINE_MAIN_HEAD;
6729 else if (ref->ltag)
6730 type = LINE_MAIN_LOCAL_TAG;
6731 else if (ref->tag)
6732 type = LINE_MAIN_TAG;
6733 else if (ref->tracked)
6734 type = LINE_MAIN_TRACKED;
6735 else if (ref->remote)
6736 type = LINE_MAIN_REMOTE;
6737 else
6738 type = LINE_MAIN_REF;
6740 if (draw_text(view, type, "[", TRUE) ||
6741 draw_text(view, type, ref->name, TRUE) ||
6742 draw_text(view, type, "]", TRUE))
6743 return TRUE;
6745 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6746 return TRUE;
6750 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6751 return TRUE;
6754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6755 static bool
6756 main_read(struct view *view, char *line)
6758 static struct rev_graph *graph = graph_stacks;
6759 enum line_type type;
6760 struct commit *commit;
6762 if (!line) {
6763 int i;
6765 if (!view->lines && !view->prev)
6766 die("No revisions match the given arguments.");
6767 if (view->lines > 0) {
6768 commit = view->line[view->lines - 1].data;
6769 view->line[view->lines - 1].dirty = 1;
6770 if (!commit->author) {
6771 view->lines--;
6772 free(commit);
6773 graph->commit = NULL;
6776 update_rev_graph(view, graph);
6778 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6779 clear_rev_graph(&graph_stacks[i]);
6780 return TRUE;
6783 type = get_line_type(line);
6784 if (type == LINE_COMMIT) {
6785 commit = calloc(1, sizeof(struct commit));
6786 if (!commit)
6787 return FALSE;
6789 line += STRING_SIZE("commit ");
6790 if (*line == '-') {
6791 graph->boundary = 1;
6792 line++;
6795 string_copy_rev(commit->id, line);
6796 commit->refs = get_ref_list(commit->id);
6797 graph->commit = commit;
6798 add_line_data(view, commit, LINE_MAIN_COMMIT);
6800 while ((line = strchr(line, ' '))) {
6801 line++;
6802 push_rev_graph(graph->parents, line);
6803 commit->has_parents = TRUE;
6805 return TRUE;
6808 if (!view->lines)
6809 return TRUE;
6810 commit = view->line[view->lines - 1].data;
6812 switch (type) {
6813 case LINE_PARENT:
6814 if (commit->has_parents)
6815 break;
6816 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6817 break;
6819 case LINE_AUTHOR:
6820 parse_author_line(line + STRING_SIZE("author "),
6821 &commit->author, &commit->time);
6822 update_rev_graph(view, graph);
6823 graph = graph->next;
6824 break;
6826 default:
6827 /* Fill in the commit title if it has not already been set. */
6828 if (commit->title[0])
6829 break;
6831 /* Require titles to start with a non-space character at the
6832 * offset used by git log. */
6833 if (strncmp(line, " ", 4))
6834 break;
6835 line += 4;
6836 /* Well, if the title starts with a whitespace character,
6837 * try to be forgiving. Otherwise we end up with no title. */
6838 while (isspace(*line))
6839 line++;
6840 if (*line == '\0')
6841 break;
6842 /* FIXME: More graceful handling of titles; append "..." to
6843 * shortened titles, etc. */
6845 string_expand(commit->title, sizeof(commit->title), line, 1);
6846 view->line[view->lines - 1].dirty = 1;
6849 return TRUE;
6852 static enum request
6853 main_request(struct view *view, enum request request, struct line *line)
6855 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6857 switch (request) {
6858 case REQ_ENTER:
6859 open_view(view, REQ_VIEW_DIFF, flags);
6860 break;
6861 case REQ_REFRESH:
6862 load_refs();
6863 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6864 break;
6865 default:
6866 return request;
6869 return REQ_NONE;
6872 static bool
6873 grep_refs(struct ref_list *list, regex_t *regex)
6875 regmatch_t pmatch;
6876 size_t i;
6878 if (!opt_show_refs || !list)
6879 return FALSE;
6881 for (i = 0; i < list->size; i++) {
6882 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6883 return TRUE;
6886 return FALSE;
6889 static bool
6890 main_grep(struct view *view, struct line *line)
6892 struct commit *commit = line->data;
6893 const char *text[] = {
6894 commit->title,
6895 opt_author ? commit->author : "",
6896 mkdate(&commit->time, opt_date),
6897 NULL
6900 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6903 static void
6904 main_select(struct view *view, struct line *line)
6906 struct commit *commit = line->data;
6908 string_copy_rev(view->ref, commit->id);
6909 string_copy_rev(ref_commit, view->ref);
6912 static struct view_ops main_ops = {
6913 "commit",
6914 main_argv,
6915 NULL,
6916 main_read,
6917 main_draw,
6918 main_request,
6919 main_grep,
6920 main_select,
6925 * Status management
6928 /* Whether or not the curses interface has been initialized. */
6929 static bool cursed = FALSE;
6931 /* Terminal hacks and workarounds. */
6932 static bool use_scroll_redrawwin;
6933 static bool use_scroll_status_wclear;
6935 /* The status window is used for polling keystrokes. */
6936 static WINDOW *status_win;
6938 /* Reading from the prompt? */
6939 static bool input_mode = FALSE;
6941 static bool status_empty = FALSE;
6943 /* Update status and title window. */
6944 static void
6945 report(const char *msg, ...)
6947 struct view *view = display[current_view];
6949 if (input_mode)
6950 return;
6952 if (!view) {
6953 char buf[SIZEOF_STR];
6954 va_list args;
6956 va_start(args, msg);
6957 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6958 buf[sizeof(buf) - 1] = 0;
6959 buf[sizeof(buf) - 2] = '.';
6960 buf[sizeof(buf) - 3] = '.';
6961 buf[sizeof(buf) - 4] = '.';
6963 va_end(args);
6964 die("%s", buf);
6967 if (!status_empty || *msg) {
6968 va_list args;
6970 va_start(args, msg);
6972 wmove(status_win, 0, 0);
6973 if (view->has_scrolled && use_scroll_status_wclear)
6974 wclear(status_win);
6975 if (*msg) {
6976 vwprintw(status_win, msg, args);
6977 status_empty = FALSE;
6978 } else {
6979 status_empty = TRUE;
6981 wclrtoeol(status_win);
6982 wnoutrefresh(status_win);
6984 va_end(args);
6987 update_view_title(view);
6990 static void
6991 init_display(void)
6993 const char *term;
6994 int x, y;
6996 /* Initialize the curses library */
6997 if (isatty(STDIN_FILENO)) {
6998 cursed = !!initscr();
6999 opt_tty = stdin;
7000 } else {
7001 /* Leave stdin and stdout alone when acting as a pager. */
7002 opt_tty = fopen("/dev/tty", "r+");
7003 if (!opt_tty)
7004 die("Failed to open /dev/tty");
7005 cursed = !!newterm(NULL, opt_tty, opt_tty);
7008 if (!cursed)
7009 die("Failed to initialize curses");
7011 nonl(); /* Disable conversion and detect newlines from input. */
7012 cbreak(); /* Take input chars one at a time, no wait for \n */
7013 noecho(); /* Don't echo input */
7014 leaveok(stdscr, FALSE);
7016 if (has_colors())
7017 init_colors();
7019 getmaxyx(stdscr, y, x);
7020 status_win = newwin(1, 0, y - 1, 0);
7021 if (!status_win)
7022 die("Failed to create status window");
7024 /* Enable keyboard mapping */
7025 keypad(status_win, TRUE);
7026 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7028 TABSIZE = opt_tab_size;
7030 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7031 if (term && !strcmp(term, "gnome-terminal")) {
7032 /* In the gnome-terminal-emulator, the message from
7033 * scrolling up one line when impossible followed by
7034 * scrolling down one line causes corruption of the
7035 * status line. This is fixed by calling wclear. */
7036 use_scroll_status_wclear = TRUE;
7037 use_scroll_redrawwin = FALSE;
7039 } else if (term && !strcmp(term, "xrvt-xpm")) {
7040 /* No problems with full optimizations in xrvt-(unicode)
7041 * and aterm. */
7042 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7044 } else {
7045 /* When scrolling in (u)xterm the last line in the
7046 * scrolling direction will update slowly. */
7047 use_scroll_redrawwin = TRUE;
7048 use_scroll_status_wclear = FALSE;
7052 static int
7053 get_input(int prompt_position)
7055 struct view *view;
7056 int i, key, cursor_y, cursor_x;
7058 if (prompt_position)
7059 input_mode = TRUE;
7061 while (TRUE) {
7062 bool loading = FALSE;
7064 foreach_view (view, i) {
7065 update_view(view);
7066 if (view_is_displayed(view) && view->has_scrolled &&
7067 use_scroll_redrawwin)
7068 redrawwin(view->win);
7069 view->has_scrolled = FALSE;
7070 if (view->pipe)
7071 loading = TRUE;
7074 /* Update the cursor position. */
7075 if (prompt_position) {
7076 getbegyx(status_win, cursor_y, cursor_x);
7077 cursor_x = prompt_position;
7078 } else {
7079 view = display[current_view];
7080 getbegyx(view->win, cursor_y, cursor_x);
7081 cursor_x = view->width - 1;
7082 cursor_y += view->lineno - view->offset;
7084 setsyx(cursor_y, cursor_x);
7086 /* Refresh, accept single keystroke of input */
7087 doupdate();
7088 nodelay(status_win, loading);
7089 key = wgetch(status_win);
7091 /* wgetch() with nodelay() enabled returns ERR when
7092 * there's no input. */
7093 if (key == ERR) {
7095 } else if (key == KEY_RESIZE) {
7096 int height, width;
7098 getmaxyx(stdscr, height, width);
7100 wresize(status_win, 1, width);
7101 mvwin(status_win, height - 1, 0);
7102 wnoutrefresh(status_win);
7103 resize_display();
7104 redraw_display(TRUE);
7106 } else {
7107 input_mode = FALSE;
7108 return key;
7113 static char *
7114 prompt_input(const char *prompt, input_handler handler, void *data)
7116 enum input_status status = INPUT_OK;
7117 static char buf[SIZEOF_STR];
7118 size_t pos = 0;
7120 buf[pos] = 0;
7122 while (status == INPUT_OK || status == INPUT_SKIP) {
7123 int key;
7125 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7126 wclrtoeol(status_win);
7128 key = get_input(pos + 1);
7129 switch (key) {
7130 case KEY_RETURN:
7131 case KEY_ENTER:
7132 case '\n':
7133 status = pos ? INPUT_STOP : INPUT_CANCEL;
7134 break;
7136 case KEY_BACKSPACE:
7137 if (pos > 0)
7138 buf[--pos] = 0;
7139 else
7140 status = INPUT_CANCEL;
7141 break;
7143 case KEY_ESC:
7144 status = INPUT_CANCEL;
7145 break;
7147 default:
7148 if (pos >= sizeof(buf)) {
7149 report("Input string too long");
7150 return NULL;
7153 status = handler(data, buf, key);
7154 if (status == INPUT_OK)
7155 buf[pos++] = (char) key;
7159 /* Clear the status window */
7160 status_empty = FALSE;
7161 report("");
7163 if (status == INPUT_CANCEL)
7164 return NULL;
7166 buf[pos++] = 0;
7168 return buf;
7171 static enum input_status
7172 prompt_yesno_handler(void *data, char *buf, int c)
7174 if (c == 'y' || c == 'Y')
7175 return INPUT_STOP;
7176 if (c == 'n' || c == 'N')
7177 return INPUT_CANCEL;
7178 return INPUT_SKIP;
7181 static bool
7182 prompt_yesno(const char *prompt)
7184 char prompt2[SIZEOF_STR];
7186 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7187 return FALSE;
7189 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7192 static enum input_status
7193 read_prompt_handler(void *data, char *buf, int c)
7195 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7198 static char *
7199 read_prompt(const char *prompt)
7201 return prompt_input(prompt, read_prompt_handler, NULL);
7204 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7206 enum input_status status = INPUT_OK;
7207 int size = 0;
7209 while (items[size].text)
7210 size++;
7212 while (status == INPUT_OK) {
7213 const struct menu_item *item = &items[*selected];
7214 int key;
7215 int i;
7217 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7218 prompt, *selected + 1, size);
7219 if (item->hotkey)
7220 wprintw(status_win, "[%c] ", (char) item->hotkey);
7221 wprintw(status_win, "%s", item->text);
7222 wclrtoeol(status_win);
7224 key = get_input(COLS - 1);
7225 switch (key) {
7226 case KEY_RETURN:
7227 case KEY_ENTER:
7228 case '\n':
7229 status = INPUT_STOP;
7230 break;
7232 case KEY_LEFT:
7233 case KEY_UP:
7234 *selected = *selected - 1;
7235 if (*selected < 0)
7236 *selected = size - 1;
7237 break;
7239 case KEY_RIGHT:
7240 case KEY_DOWN:
7241 *selected = (*selected + 1) % size;
7242 break;
7244 case KEY_ESC:
7245 status = INPUT_CANCEL;
7246 break;
7248 default:
7249 for (i = 0; items[i].text; i++)
7250 if (items[i].hotkey == key) {
7251 *selected = i;
7252 status = INPUT_STOP;
7253 break;
7258 /* Clear the status window */
7259 status_empty = FALSE;
7260 report("");
7262 return status != INPUT_CANCEL;
7266 * Repository properties
7269 static struct ref **refs = NULL;
7270 static size_t refs_size = 0;
7271 static struct ref *refs_head = NULL;
7273 static struct ref_list **ref_lists = NULL;
7274 static size_t ref_lists_size = 0;
7276 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7277 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7278 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7280 static int
7281 compare_refs(const void *ref1_, const void *ref2_)
7283 const struct ref *ref1 = *(const struct ref **)ref1_;
7284 const struct ref *ref2 = *(const struct ref **)ref2_;
7286 if (ref1->tag != ref2->tag)
7287 return ref2->tag - ref1->tag;
7288 if (ref1->ltag != ref2->ltag)
7289 return ref2->ltag - ref2->ltag;
7290 if (ref1->head != ref2->head)
7291 return ref2->head - ref1->head;
7292 if (ref1->tracked != ref2->tracked)
7293 return ref2->tracked - ref1->tracked;
7294 if (ref1->remote != ref2->remote)
7295 return ref2->remote - ref1->remote;
7296 return strcmp(ref1->name, ref2->name);
7299 static void
7300 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7302 size_t i;
7304 for (i = 0; i < refs_size; i++)
7305 if (!visitor(data, refs[i]))
7306 break;
7309 static struct ref *
7310 get_ref_head()
7312 return refs_head;
7315 static struct ref_list *
7316 get_ref_list(const char *id)
7318 struct ref_list *list;
7319 size_t i;
7321 for (i = 0; i < ref_lists_size; i++)
7322 if (!strcmp(id, ref_lists[i]->id))
7323 return ref_lists[i];
7325 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7326 return NULL;
7327 list = calloc(1, sizeof(*list));
7328 if (!list)
7329 return NULL;
7331 for (i = 0; i < refs_size; i++) {
7332 if (!strcmp(id, refs[i]->id) &&
7333 realloc_refs_list(&list->refs, list->size, 1))
7334 list->refs[list->size++] = refs[i];
7337 if (!list->refs) {
7338 free(list);
7339 return NULL;
7342 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7343 ref_lists[ref_lists_size++] = list;
7344 return list;
7347 static int
7348 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7350 struct ref *ref = NULL;
7351 bool tag = FALSE;
7352 bool ltag = FALSE;
7353 bool remote = FALSE;
7354 bool tracked = FALSE;
7355 bool head = FALSE;
7356 int from = 0, to = refs_size - 1;
7358 if (!prefixcmp(name, "refs/tags/")) {
7359 if (!suffixcmp(name, namelen, "^{}")) {
7360 namelen -= 3;
7361 name[namelen] = 0;
7362 } else {
7363 ltag = TRUE;
7366 tag = TRUE;
7367 namelen -= STRING_SIZE("refs/tags/");
7368 name += STRING_SIZE("refs/tags/");
7370 } else if (!prefixcmp(name, "refs/remotes/")) {
7371 remote = TRUE;
7372 namelen -= STRING_SIZE("refs/remotes/");
7373 name += STRING_SIZE("refs/remotes/");
7374 tracked = !strcmp(opt_remote, name);
7376 } else if (!prefixcmp(name, "refs/heads/")) {
7377 namelen -= STRING_SIZE("refs/heads/");
7378 name += STRING_SIZE("refs/heads/");
7379 if (!strncmp(opt_head, name, namelen))
7380 return OK;
7382 } else if (!strcmp(name, "HEAD")) {
7383 head = TRUE;
7384 if (*opt_head) {
7385 namelen = strlen(opt_head);
7386 name = opt_head;
7390 /* If we are reloading or it's an annotated tag, replace the
7391 * previous SHA1 with the resolved commit id; relies on the fact
7392 * git-ls-remote lists the commit id of an annotated tag right
7393 * before the commit id it points to. */
7394 while (from <= to) {
7395 size_t pos = (to + from) / 2;
7396 int cmp = strcmp(name, refs[pos]->name);
7398 if (!cmp) {
7399 ref = refs[pos];
7400 break;
7403 if (cmp < 0)
7404 to = pos - 1;
7405 else
7406 from = pos + 1;
7409 if (!ref) {
7410 if (!realloc_refs(&refs, refs_size, 1))
7411 return ERR;
7412 ref = calloc(1, sizeof(*ref) + namelen);
7413 if (!ref)
7414 return ERR;
7415 memmove(refs + from + 1, refs + from,
7416 (refs_size - from) * sizeof(*refs));
7417 refs[from] = ref;
7418 strncpy(ref->name, name, namelen);
7419 refs_size++;
7422 ref->head = head;
7423 ref->tag = tag;
7424 ref->ltag = ltag;
7425 ref->remote = remote;
7426 ref->tracked = tracked;
7427 string_copy_rev(ref->id, id);
7429 if (head)
7430 refs_head = ref;
7431 return OK;
7434 static int
7435 load_refs(void)
7437 const char *head_argv[] = {
7438 "git", "symbolic-ref", "HEAD", NULL
7440 static const char *ls_remote_argv[SIZEOF_ARG] = {
7441 "git", "ls-remote", opt_git_dir, NULL
7443 static bool init = FALSE;
7444 size_t i;
7446 if (!init) {
7447 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7448 die("TIG_LS_REMOTE contains too many arguments");
7449 init = TRUE;
7452 if (!*opt_git_dir)
7453 return OK;
7455 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7456 !prefixcmp(opt_head, "refs/heads/")) {
7457 char *offset = opt_head + STRING_SIZE("refs/heads/");
7459 memmove(opt_head, offset, strlen(offset) + 1);
7462 refs_head = NULL;
7463 for (i = 0; i < refs_size; i++)
7464 refs[i]->id[0] = 0;
7466 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7467 return ERR;
7469 /* Update the ref lists to reflect changes. */
7470 for (i = 0; i < ref_lists_size; i++) {
7471 struct ref_list *list = ref_lists[i];
7472 size_t old, new;
7474 for (old = new = 0; old < list->size; old++)
7475 if (!strcmp(list->id, list->refs[old]->id))
7476 list->refs[new++] = list->refs[old];
7477 list->size = new;
7480 return OK;
7483 static void
7484 set_remote_branch(const char *name, const char *value, size_t valuelen)
7486 if (!strcmp(name, ".remote")) {
7487 string_ncopy(opt_remote, value, valuelen);
7489 } else if (*opt_remote && !strcmp(name, ".merge")) {
7490 size_t from = strlen(opt_remote);
7492 if (!prefixcmp(value, "refs/heads/"))
7493 value += STRING_SIZE("refs/heads/");
7495 if (!string_format_from(opt_remote, &from, "/%s", value))
7496 opt_remote[0] = 0;
7500 static void
7501 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7503 const char *argv[SIZEOF_ARG] = { name, "=" };
7504 int argc = 1 + (cmd == option_set_command);
7505 int error = ERR;
7507 if (!argv_from_string(argv, &argc, value))
7508 config_msg = "Too many option arguments";
7509 else
7510 error = cmd(argc, argv);
7512 if (error == ERR)
7513 warn("Option 'tig.%s': %s", name, config_msg);
7516 static bool
7517 set_environment_variable(const char *name, const char *value)
7519 size_t len = strlen(name) + 1 + strlen(value) + 1;
7520 char *env = malloc(len);
7522 if (env &&
7523 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7524 putenv(env) == 0)
7525 return TRUE;
7526 free(env);
7527 return FALSE;
7530 static void
7531 set_work_tree(const char *value)
7533 char cwd[SIZEOF_STR];
7535 if (!getcwd(cwd, sizeof(cwd)))
7536 die("Failed to get cwd path: %s", strerror(errno));
7537 if (chdir(opt_git_dir) < 0)
7538 die("Failed to chdir(%s): %s", strerror(errno));
7539 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7540 die("Failed to get git path: %s", strerror(errno));
7541 if (chdir(cwd) < 0)
7542 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7543 if (chdir(value) < 0)
7544 die("Failed to chdir(%s): %s", value, strerror(errno));
7545 if (!getcwd(cwd, sizeof(cwd)))
7546 die("Failed to get cwd path: %s", strerror(errno));
7547 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7548 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7549 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7550 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7551 opt_is_inside_work_tree = TRUE;
7554 static int
7555 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7557 if (!strcmp(name, "i18n.commitencoding"))
7558 string_ncopy(opt_encoding, value, valuelen);
7560 else if (!strcmp(name, "core.editor"))
7561 string_ncopy(opt_editor, value, valuelen);
7563 else if (!strcmp(name, "core.worktree"))
7564 set_work_tree(value);
7566 else if (!prefixcmp(name, "tig.color."))
7567 set_repo_config_option(name + 10, value, option_color_command);
7569 else if (!prefixcmp(name, "tig.bind."))
7570 set_repo_config_option(name + 9, value, option_bind_command);
7572 else if (!prefixcmp(name, "tig."))
7573 set_repo_config_option(name + 4, value, option_set_command);
7575 else if (*opt_head && !prefixcmp(name, "branch.") &&
7576 !strncmp(name + 7, opt_head, strlen(opt_head)))
7577 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7579 return OK;
7582 static int
7583 load_git_config(void)
7585 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7587 return io_run_load(config_list_argv, "=", read_repo_config_option);
7590 static int
7591 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7593 if (!opt_git_dir[0]) {
7594 string_ncopy(opt_git_dir, name, namelen);
7596 } else if (opt_is_inside_work_tree == -1) {
7597 /* This can be 3 different values depending on the
7598 * version of git being used. If git-rev-parse does not
7599 * understand --is-inside-work-tree it will simply echo
7600 * the option else either "true" or "false" is printed.
7601 * Default to true for the unknown case. */
7602 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7604 } else if (*name == '.') {
7605 string_ncopy(opt_cdup, name, namelen);
7607 } else {
7608 string_ncopy(opt_prefix, name, namelen);
7611 return OK;
7614 static int
7615 load_repo_info(void)
7617 const char *rev_parse_argv[] = {
7618 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7619 "--show-cdup", "--show-prefix", NULL
7622 return io_run_load(rev_parse_argv, "=", read_repo_info);
7627 * Main
7630 static const char usage[] =
7631 "tig " TIG_VERSION " (" __DATE__ ")\n"
7632 "\n"
7633 "Usage: tig [options] [revs] [--] [paths]\n"
7634 " or: tig show [options] [revs] [--] [paths]\n"
7635 " or: tig blame [rev] path\n"
7636 " or: tig status\n"
7637 " or: tig < [git command output]\n"
7638 "\n"
7639 "Options:\n"
7640 " -v, --version Show version and exit\n"
7641 " -h, --help Show help message and exit";
7643 static void __NORETURN
7644 quit(int sig)
7646 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7647 if (cursed)
7648 endwin();
7649 exit(0);
7652 static void __NORETURN
7653 die(const char *err, ...)
7655 va_list args;
7657 endwin();
7659 va_start(args, err);
7660 fputs("tig: ", stderr);
7661 vfprintf(stderr, err, args);
7662 fputs("\n", stderr);
7663 va_end(args);
7665 exit(1);
7668 static void
7669 warn(const char *msg, ...)
7671 va_list args;
7673 va_start(args, msg);
7674 fputs("tig warning: ", stderr);
7675 vfprintf(stderr, msg, args);
7676 fputs("\n", stderr);
7677 va_end(args);
7680 static const char ***filter_args;
7682 static int
7683 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7685 return argv_append(filter_args, name) ? OK : ERR;
7688 static void
7689 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7691 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7692 const char **all_argv = NULL;
7694 filter_args = args;
7695 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7696 !argv_append_array(&all_argv, argv) ||
7697 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7698 die("Failed to split arguments");
7699 argv_free(all_argv);
7700 free(all_argv);
7703 static void
7704 filter_options(const char *argv[])
7706 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7707 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7708 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7711 static enum request
7712 parse_options(int argc, const char *argv[])
7714 enum request request = REQ_VIEW_MAIN;
7715 const char *subcommand;
7716 bool seen_dashdash = FALSE;
7717 const char **filter_argv = NULL;
7718 int i;
7720 if (!isatty(STDIN_FILENO)) {
7721 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7722 return REQ_VIEW_PAGER;
7725 if (argc <= 1)
7726 return REQ_VIEW_MAIN;
7728 subcommand = argv[1];
7729 if (!strcmp(subcommand, "status")) {
7730 if (argc > 2)
7731 warn("ignoring arguments after `%s'", subcommand);
7732 return REQ_VIEW_STATUS;
7734 } else if (!strcmp(subcommand, "blame")) {
7735 if (argc <= 2 || argc > 4)
7736 die("invalid number of options to blame\n\n%s", usage);
7738 i = 2;
7739 if (argc == 4) {
7740 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7741 i++;
7744 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7745 return REQ_VIEW_BLAME;
7747 } else if (!strcmp(subcommand, "show")) {
7748 request = REQ_VIEW_DIFF;
7750 } else {
7751 subcommand = NULL;
7754 for (i = 1 + !!subcommand; i < argc; i++) {
7755 const char *opt = argv[i];
7757 if (seen_dashdash) {
7758 argv_append(&opt_file_args, opt);
7759 continue;
7761 } else if (!strcmp(opt, "--")) {
7762 seen_dashdash = TRUE;
7763 continue;
7765 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7766 printf("tig version %s\n", TIG_VERSION);
7767 quit(0);
7769 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7770 printf("%s\n", usage);
7771 quit(0);
7773 } else if (!strcmp(opt, "--all")) {
7774 argv_append(&opt_rev_args, opt);
7775 continue;
7778 if (!argv_append(&filter_argv, opt))
7779 die("command too long");
7782 if (filter_argv)
7783 filter_options(filter_argv);
7785 return request;
7789 main(int argc, const char *argv[])
7791 const char *codeset = "UTF-8";
7792 enum request request = parse_options(argc, argv);
7793 struct view *view;
7794 size_t i;
7796 signal(SIGINT, quit);
7797 signal(SIGPIPE, SIG_IGN);
7799 if (setlocale(LC_ALL, "")) {
7800 codeset = nl_langinfo(CODESET);
7803 if (load_repo_info() == ERR)
7804 die("Failed to load repo info.");
7806 if (load_options() == ERR)
7807 die("Failed to load user config.");
7809 if (load_git_config() == ERR)
7810 die("Failed to load repo config.");
7812 /* Require a git repository unless when running in pager mode. */
7813 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7814 die("Not a git repository");
7816 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7817 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7818 if (opt_iconv_in == ICONV_NONE)
7819 die("Failed to initialize character set conversion");
7822 if (codeset && strcmp(codeset, "UTF-8")) {
7823 opt_iconv_out = iconv_open(codeset, "UTF-8");
7824 if (opt_iconv_out == ICONV_NONE)
7825 die("Failed to initialize character set conversion");
7828 if (load_refs() == ERR)
7829 die("Failed to load refs.");
7831 foreach_view (view, i) {
7832 if (getenv(view->cmd_env))
7833 warn("Use of the %s environment variable is deprecated,"
7834 " use options or TIG_DIFF_ARGS instead",
7835 view->cmd_env);
7836 if (!argv_from_env(view->ops->argv, view->cmd_env))
7837 die("Too many arguments in the `%s` environment variable",
7838 view->cmd_env);
7841 init_display();
7843 while (view_driver(display[current_view], request)) {
7844 int key = get_input(0);
7846 view = display[current_view];
7847 request = get_keybinding(view->keymap, key);
7849 /* Some low-level request handling. This keeps access to
7850 * status_win restricted. */
7851 switch (request) {
7852 case REQ_NONE:
7853 report("Unknown key, press %s for help",
7854 get_key(view->keymap, REQ_VIEW_HELP));
7855 break;
7856 case REQ_PROMPT:
7858 char *cmd = read_prompt(":");
7860 if (cmd && isdigit(*cmd)) {
7861 int lineno = view->lineno + 1;
7863 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7864 select_view_line(view, lineno - 1);
7865 report("");
7866 } else {
7867 report("Unable to parse '%s' as a line number", cmd);
7870 } else if (cmd) {
7871 struct view *next = VIEW(REQ_VIEW_PAGER);
7872 const char *argv[SIZEOF_ARG] = { "git" };
7873 int argc = 1;
7875 /* When running random commands, initially show the
7876 * command in the title. However, it maybe later be
7877 * overwritten if a commit line is selected. */
7878 string_ncopy(next->ref, cmd, strlen(cmd));
7880 if (!argv_from_string(argv, &argc, cmd)) {
7881 report("Too many arguments");
7882 } else if (!prepare_update(next, argv, NULL)) {
7883 report("Failed to format command");
7884 } else {
7885 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7889 request = REQ_NONE;
7890 break;
7892 case REQ_SEARCH:
7893 case REQ_SEARCH_BACK:
7895 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7896 char *search = read_prompt(prompt);
7898 if (search)
7899 string_ncopy(opt_search, search, strlen(search));
7900 else if (*opt_search)
7901 request = request == REQ_SEARCH ?
7902 REQ_FIND_NEXT :
7903 REQ_FIND_PREV;
7904 else
7905 request = REQ_NONE;
7906 break;
7908 default:
7909 break;
7913 quit(0);
7915 return 0;