Improve parent blame to detect renames by using the previous information
[tig.git] / tig.c
blobc2417bfee168eb4b52c6287ee01bcc524a9bc635
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 void
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;
233 static char *
234 chomp_string(char *name)
236 int namelen;
238 while (isspace(*name))
239 name++;
241 namelen = strlen(name) - 1;
242 while (namelen > 0 && isspace(name[namelen]))
243 name[namelen--] = 0;
245 return name;
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
251 va_list args;
252 size_t pos = bufpos ? *bufpos : 0;
254 va_start(args, fmt);
255 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256 va_end(args);
258 if (bufpos)
259 *bufpos = pos;
261 return pos >= bufsize ? FALSE : TRUE;
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
273 size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i = 0; i < len; i++) {
279 if (toupper(str1[i]) == toupper(str2[i]))
280 continue;
282 if (string_enum_sep(str1[i]) &&
283 string_enum_sep(str2[i]))
284 continue;
286 return str1[i] - str2[i];
289 return 0;
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296 const char *name;
297 int namelen;
298 int value;
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
306 static char buf[SIZEOF_STR];
307 int bufpos;
309 for (bufpos = 0; bufpos <= namelen; bufpos++) {
310 buf[bufpos] = tolower(name[bufpos]);
311 if (buf[bufpos] == '_')
312 buf[bufpos] = '-';
315 buf[bufpos] = 0;
316 return buf;
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
324 size_t namelen = strlen(name);
325 int i;
327 for (i = 0; i < map_size; i++)
328 if (enum_equals(map[i], name, namelen)) {
329 *value = map[i].value;
330 return TRUE;
333 return FALSE;
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
345 size_t len = slen >= 0 ? slen : strlen(str);
346 size_t suffixlen = strlen(suffix);
348 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
353 * Unicode / UTF-8 handling
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
363 if (c >= 0x1100 &&
364 (c <= 0x115f /* Hangul Jamo */
365 || c == 0x2329
366 || c == 0x232a
367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
368 /* CJK ... Yi */
369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
373 || (c >= 0xffe0 && c <= 0xffe6)
374 || (c >= 0x20000 && c <= 0x2fffd)
375 || (c >= 0x30000 && c <= 0x3fffd)))
376 return 2;
378 if (c == '\t')
379 return tab_size;
381 return 1;
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387 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,
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 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,
394 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,
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
400 int c = *(unsigned char *) string;
402 return utf8_bytes[c];
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
409 unsigned long unicode;
411 switch (length) {
412 case 1:
413 unicode = string[0];
414 break;
415 case 2:
416 unicode = (string[0] & 0x1f) << 6;
417 unicode += (string[1] & 0x3f);
418 break;
419 case 3:
420 unicode = (string[0] & 0x0f) << 12;
421 unicode += ((string[1] & 0x3f) << 6);
422 unicode += (string[2] & 0x3f);
423 break;
424 case 4:
425 unicode = (string[0] & 0x0f) << 18;
426 unicode += ((string[1] & 0x3f) << 12);
427 unicode += ((string[2] & 0x3f) << 6);
428 unicode += (string[3] & 0x3f);
429 break;
430 case 5:
431 unicode = (string[0] & 0x0f) << 24;
432 unicode += ((string[1] & 0x3f) << 18);
433 unicode += ((string[2] & 0x3f) << 12);
434 unicode += ((string[3] & 0x3f) << 6);
435 unicode += (string[4] & 0x3f);
436 break;
437 case 6:
438 unicode = (string[0] & 0x01) << 30;
439 unicode += ((string[1] & 0x3f) << 24);
440 unicode += ((string[2] & 0x3f) << 18);
441 unicode += ((string[3] & 0x3f) << 12);
442 unicode += ((string[4] & 0x3f) << 6);
443 unicode += (string[5] & 0x3f);
444 break;
445 default:
446 return 0;
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode > 0xffff ? 0 : unicode;
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
459 * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
463 const char *string = *start;
464 const char *end = strchr(string, '\0');
465 unsigned char last_bytes = 0;
466 size_t last_ucwidth = 0;
468 *width = 0;
469 *trimmed = 0;
471 while (string < end) {
472 unsigned char bytes = utf8_char_length(string, end);
473 size_t ucwidth;
474 unsigned long unicode;
476 if (string + bytes > end)
477 break;
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode = utf8_to_unicode(string, bytes);
483 /* FIXME: Graceful handling of invalid Unicode character. */
484 if (!unicode)
485 break;
487 ucwidth = unicode_width(unicode, tab_size);
488 if (skip > 0) {
489 skip -= ucwidth <= skip ? ucwidth : skip;
490 *start += bytes;
492 *width += ucwidth;
493 if (*width > max_width) {
494 *trimmed = 1;
495 *width -= ucwidth;
496 if (reserve && *width == max_width) {
497 string -= last_bytes;
498 *width -= last_ucwidth;
500 break;
503 string += bytes;
504 last_bytes = ucwidth ? bytes : 0;
505 last_ucwidth = ucwidth;
508 return string - *start;
512 #define DATE_INFO \
513 DATE_(NO), \
514 DATE_(DEFAULT), \
515 DATE_(LOCAL), \
516 DATE_(RELATIVE), \
517 DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521 DATE_INFO
522 #undef DATE_
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527 DATE_INFO
528 #undef DATE_
531 struct time {
532 time_t sec;
533 int tz;
536 static inline int timecmp(const struct time *t1, const struct time *t2)
538 return t1->sec - t2->sec;
541 static const char *
542 mkdate(const struct time *time, enum date date)
544 static char buf[DATE_COLS + 1];
545 static const struct enum_map reldate[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
553 struct tm tm;
555 if (!date || !time || !time->sec)
556 return "";
558 if (date == DATE_RELATIVE) {
559 struct timeval now;
560 time_t date = time->sec + time->tz;
561 time_t seconds;
562 int i;
564 gettimeofday(&now, NULL);
565 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567 if (seconds >= reldate[i].value)
568 continue;
570 seconds /= reldate[i].namelen;
571 if (!string_format(buf, "%ld %s%s %s",
572 seconds, reldate[i].name,
573 seconds > 1 ? "s" : "",
574 now.tv_sec >= date ? "ago" : "ahead"))
575 break;
576 return buf;
580 if (date == DATE_LOCAL) {
581 time_t date = time->sec + time->tz;
582 localtime_r(&date, &tm);
584 else {
585 gmtime_r(&time->sec, &tm);
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
609 static const char *
610 get_author_initials(const char *author)
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
638 initials[i++] = 0;
641 return initials;
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
663 static bool
664 argv_from_env(const char **argv, const char *name)
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
674 static void
675 argv_free(const char *argv[])
677 int argc;
679 if (!argv)
680 return;
681 for (argc = 0; argv[argc]; argc++)
682 free((void *) argv[argc]);
683 argv[0] = NULL;
686 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
688 static bool
689 argv_append(const char ***argv, const char *arg)
691 int argc = 0;
693 while (*argv && (*argv)[argc])
694 argc++;
696 if (!argv_realloc(argv, argc, 2))
697 return FALSE;
699 (*argv)[argc++] = strdup(arg);
700 (*argv)[argc] = NULL;
701 return TRUE;
704 static bool
705 argv_append_array(const char ***dst_argv, const char *src_argv[])
707 int i;
709 for (i = 0; src_argv && src_argv[i]; i++)
710 if (!argv_append(dst_argv, src_argv[i]))
711 return FALSE;
712 return TRUE;
715 static bool
716 argv_copy(const char ***dst, const char *src[])
718 int argc;
720 for (argc = 0; src[argc]; argc++)
721 if (!argv_append(dst, src[argc]))
722 return FALSE;
723 return TRUE;
728 * Executing external commands.
731 enum io_type {
732 IO_FD, /* File descriptor based IO. */
733 IO_BG, /* Execute command in the background. */
734 IO_FG, /* Execute command with same std{in,out,err}. */
735 IO_RD, /* Read only fork+exec IO. */
736 IO_WR, /* Write only fork+exec IO. */
737 IO_AP, /* Append fork+exec output to file. */
740 struct io {
741 int pipe; /* Pipe end for reading or writing. */
742 pid_t pid; /* PID of spawned process. */
743 int error; /* Error status. */
744 char *buf; /* Read buffer. */
745 size_t bufalloc; /* Allocated buffer size. */
746 size_t bufsize; /* Buffer content size. */
747 char *bufpos; /* Current buffer position. */
748 unsigned int eof:1; /* Has end of file been reached. */
751 static void
752 io_init(struct io *io)
754 memset(io, 0, sizeof(*io));
755 io->pipe = -1;
758 static bool
759 io_open(struct io *io, const char *fmt, ...)
761 char name[SIZEOF_STR] = "";
762 bool fits;
763 va_list args;
765 io_init(io);
767 va_start(args, fmt);
768 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
769 va_end(args);
771 if (!fits) {
772 io->error = ENAMETOOLONG;
773 return FALSE;
775 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
776 if (io->pipe == -1)
777 io->error = errno;
778 return io->pipe != -1;
781 static bool
782 io_kill(struct io *io)
784 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
787 static bool
788 io_done(struct io *io)
790 pid_t pid = io->pid;
792 if (io->pipe != -1)
793 close(io->pipe);
794 free(io->buf);
795 io_init(io);
797 while (pid > 0) {
798 int status;
799 pid_t waiting = waitpid(pid, &status, 0);
801 if (waiting < 0) {
802 if (errno == EINTR)
803 continue;
804 io->error = errno;
805 return FALSE;
808 return waiting == pid &&
809 !WIFSIGNALED(status) &&
810 WIFEXITED(status) &&
811 !WEXITSTATUS(status);
814 return TRUE;
817 static bool
818 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
820 int pipefds[2] = { -1, -1 };
821 va_list args;
823 io_init(io);
825 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
826 io->error = errno;
827 return FALSE;
828 } else if (type == IO_AP) {
829 va_start(args, argv);
830 pipefds[1] = va_arg(args, int);
831 va_end(args);
834 if ((io->pid = fork())) {
835 if (io->pid == -1)
836 io->error = errno;
837 if (pipefds[!(type == IO_WR)] != -1)
838 close(pipefds[!(type == IO_WR)]);
839 if (io->pid != -1) {
840 io->pipe = pipefds[!!(type == IO_WR)];
841 return TRUE;
844 } else {
845 if (type != IO_FG) {
846 int devnull = open("/dev/null", O_RDWR);
847 int readfd = type == IO_WR ? pipefds[0] : devnull;
848 int writefd = (type == IO_RD || type == IO_AP)
849 ? pipefds[1] : devnull;
851 dup2(readfd, STDIN_FILENO);
852 dup2(writefd, STDOUT_FILENO);
853 dup2(devnull, STDERR_FILENO);
855 close(devnull);
856 if (pipefds[0] != -1)
857 close(pipefds[0]);
858 if (pipefds[1] != -1)
859 close(pipefds[1]);
862 if (dir && *dir && chdir(dir) == -1)
863 exit(errno);
865 execvp(argv[0], (char *const*) argv);
866 exit(errno);
869 if (pipefds[!!(type == IO_WR)] != -1)
870 close(pipefds[!!(type == IO_WR)]);
871 return FALSE;
874 static bool
875 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
877 struct io io;
879 return io_run(&io, type, dir, argv, fd) && io_done(&io);
882 static bool
883 io_run_bg(const char **argv)
885 return io_complete(IO_BG, argv, NULL, -1);
888 static bool
889 io_run_fg(const char **argv, const char *dir)
891 return io_complete(IO_FG, argv, dir, -1);
894 static bool
895 io_run_append(const char **argv, int fd)
897 return io_complete(IO_AP, argv, NULL, fd);
900 static bool
901 io_eof(struct io *io)
903 return io->eof;
906 static int
907 io_error(struct io *io)
909 return io->error;
912 static char *
913 io_strerror(struct io *io)
915 return strerror(io->error);
918 static bool
919 io_can_read(struct io *io)
921 struct timeval tv = { 0, 500 };
922 fd_set fds;
924 FD_ZERO(&fds);
925 FD_SET(io->pipe, &fds);
927 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
930 static ssize_t
931 io_read(struct io *io, void *buf, size_t bufsize)
933 do {
934 ssize_t readsize = read(io->pipe, buf, bufsize);
936 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
937 continue;
938 else if (readsize == -1)
939 io->error = errno;
940 else if (readsize == 0)
941 io->eof = 1;
942 return readsize;
943 } while (1);
946 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
948 static char *
949 io_get(struct io *io, int c, bool can_read)
951 char *eol;
952 ssize_t readsize;
954 while (TRUE) {
955 if (io->bufsize > 0) {
956 eol = memchr(io->bufpos, c, io->bufsize);
957 if (eol) {
958 char *line = io->bufpos;
960 *eol = 0;
961 io->bufpos = eol + 1;
962 io->bufsize -= io->bufpos - line;
963 return line;
967 if (io_eof(io)) {
968 if (io->bufsize) {
969 io->bufpos[io->bufsize] = 0;
970 io->bufsize = 0;
971 return io->bufpos;
973 return NULL;
976 if (!can_read)
977 return NULL;
979 if (io->bufsize > 0 && io->bufpos > io->buf)
980 memmove(io->buf, io->bufpos, io->bufsize);
982 if (io->bufalloc == io->bufsize) {
983 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
984 return NULL;
985 io->bufalloc += BUFSIZ;
988 io->bufpos = io->buf;
989 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
990 if (io_error(io))
991 return NULL;
992 io->bufsize += readsize;
996 static bool
997 io_write(struct io *io, const void *buf, size_t bufsize)
999 size_t written = 0;
1001 while (!io_error(io) && written < bufsize) {
1002 ssize_t size;
1004 size = write(io->pipe, buf + written, bufsize - written);
1005 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1006 continue;
1007 else if (size == -1)
1008 io->error = errno;
1009 else
1010 written += size;
1013 return written == bufsize;
1016 static bool
1017 io_read_buf(struct io *io, char buf[], size_t bufsize)
1019 char *result = io_get(io, '\n', TRUE);
1021 if (result) {
1022 result = chomp_string(result);
1023 string_ncopy_do(buf, bufsize, result, strlen(result));
1026 return io_done(io) && result;
1029 static bool
1030 io_run_buf(const char **argv, char buf[], size_t bufsize)
1032 struct io io;
1034 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1037 static int
1038 io_load(struct io *io, const char *separators,
1039 int (*read_property)(char *, size_t, char *, size_t))
1041 char *name;
1042 int state = OK;
1044 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1045 char *value;
1046 size_t namelen;
1047 size_t valuelen;
1049 name = chomp_string(name);
1050 namelen = strcspn(name, separators);
1052 if (name[namelen]) {
1053 name[namelen] = 0;
1054 value = chomp_string(name + namelen + 1);
1055 valuelen = strlen(value);
1057 } else {
1058 value = "";
1059 valuelen = 0;
1062 state = read_property(name, namelen, value, valuelen);
1065 if (state != ERR && io_error(io))
1066 state = ERR;
1067 io_done(io);
1069 return state;
1072 static int
1073 io_run_load(const char **argv, const char *separators,
1074 int (*read_property)(char *, size_t, char *, size_t))
1076 struct io io;
1078 if (!io_run(&io, IO_RD, NULL, argv))
1079 return ERR;
1080 return io_load(&io, separators, read_property);
1085 * User requests
1088 #define REQ_INFO \
1089 /* XXX: Keep the view request first and in sync with views[]. */ \
1090 REQ_GROUP("View switching") \
1091 REQ_(VIEW_MAIN, "Show main view"), \
1092 REQ_(VIEW_DIFF, "Show diff view"), \
1093 REQ_(VIEW_LOG, "Show log view"), \
1094 REQ_(VIEW_TREE, "Show tree view"), \
1095 REQ_(VIEW_BLOB, "Show blob view"), \
1096 REQ_(VIEW_BLAME, "Show blame view"), \
1097 REQ_(VIEW_BRANCH, "Show branch view"), \
1098 REQ_(VIEW_HELP, "Show help page"), \
1099 REQ_(VIEW_PAGER, "Show pager view"), \
1100 REQ_(VIEW_STATUS, "Show status view"), \
1101 REQ_(VIEW_STAGE, "Show stage view"), \
1103 REQ_GROUP("View manipulation") \
1104 REQ_(ENTER, "Enter current line and scroll"), \
1105 REQ_(NEXT, "Move to next"), \
1106 REQ_(PREVIOUS, "Move to previous"), \
1107 REQ_(PARENT, "Move to parent"), \
1108 REQ_(VIEW_NEXT, "Move focus to next view"), \
1109 REQ_(REFRESH, "Reload and refresh"), \
1110 REQ_(MAXIMIZE, "Maximize the current view"), \
1111 REQ_(VIEW_CLOSE, "Close the current view"), \
1112 REQ_(QUIT, "Close all views and quit"), \
1114 REQ_GROUP("View specific requests") \
1115 REQ_(STATUS_UPDATE, "Update file status"), \
1116 REQ_(STATUS_REVERT, "Revert file changes"), \
1117 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1118 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1120 REQ_GROUP("Cursor navigation") \
1121 REQ_(MOVE_UP, "Move cursor one line up"), \
1122 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1123 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1124 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1125 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1126 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1128 REQ_GROUP("Scrolling") \
1129 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1130 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1131 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1132 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1133 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1134 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1136 REQ_GROUP("Searching") \
1137 REQ_(SEARCH, "Search the view"), \
1138 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1139 REQ_(FIND_NEXT, "Find next search match"), \
1140 REQ_(FIND_PREV, "Find previous search match"), \
1142 REQ_GROUP("Option manipulation") \
1143 REQ_(OPTIONS, "Open option menu"), \
1144 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1145 REQ_(TOGGLE_DATE, "Toggle date display"), \
1146 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1147 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1148 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1149 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1150 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1151 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153 REQ_GROUP("Misc") \
1154 REQ_(PROMPT, "Bring up the prompt"), \
1155 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1156 REQ_(SHOW_VERSION, "Show version information"), \
1157 REQ_(STOP_LOADING, "Stop all loading views"), \
1158 REQ_(EDIT, "Open in editor"), \
1159 REQ_(NONE, "Do nothing")
1162 /* User action requests. */
1163 enum request {
1164 #define REQ_GROUP(help)
1165 #define REQ_(req, help) REQ_##req
1167 /* Offset all requests to avoid conflicts with ncurses getch values. */
1168 REQ_UNKNOWN = KEY_MAX + 1,
1169 REQ_OFFSET,
1170 REQ_INFO
1172 #undef REQ_GROUP
1173 #undef REQ_
1176 struct request_info {
1177 enum request request;
1178 const char *name;
1179 int namelen;
1180 const char *help;
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186 REQ_INFO
1187 #undef REQ_GROUP
1188 #undef REQ_
1191 static enum request
1192 get_request(const char *name)
1194 int namelen = strlen(name);
1195 int i;
1197 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198 if (enum_equals(req_info[i], name, namelen))
1199 return req_info[i].request;
1201 return REQ_UNKNOWN;
1206 * Options
1209 /* Option and state variables. */
1210 static enum date opt_date = DATE_DEFAULT;
1211 static enum author opt_author = AUTHOR_DEFAULT;
1212 static bool opt_line_number = FALSE;
1213 static bool opt_line_graphics = TRUE;
1214 static bool opt_rev_graph = FALSE;
1215 static bool opt_show_refs = TRUE;
1216 static int opt_num_interval = 5;
1217 static double opt_hscroll = 0.50;
1218 static double opt_scale_split_view = 2.0 / 3.0;
1219 static int opt_tab_size = 8;
1220 static int opt_author_cols = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR] = "";
1222 static char opt_file[SIZEOF_STR] = "";
1223 static char opt_ref[SIZEOF_REF] = "";
1224 static char opt_head[SIZEOF_REF] = "";
1225 static char opt_remote[SIZEOF_REF] = "";
1226 static char opt_encoding[20] = "UTF-8";
1227 static iconv_t opt_iconv_in = ICONV_NONE;
1228 static iconv_t opt_iconv_out = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR] = "";
1230 static char opt_cdup[SIZEOF_STR] = "";
1231 static char opt_prefix[SIZEOF_STR] = "";
1232 static char opt_git_dir[SIZEOF_STR] = "";
1233 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR] = "";
1235 static FILE *opt_tty = NULL;
1236 static const char **opt_diff_args = NULL;
1237 static const char **opt_rev_args = NULL;
1238 static const char **opt_file_args = NULL;
1240 #define is_initial_commit() (!get_ref_head())
1241 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1245 * Line-oriented content detection.
1248 #define LINE_INFO \
1249 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1264 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1265 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1266 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1270 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1271 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1274 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1275 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1280 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1281 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1283 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1285 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1286 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1287 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1288 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1293 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1295 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1297 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1298 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1299 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1300 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1301 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1303 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1306 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1307 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1309 enum line_type {
1310 #define LINE(type, line, fg, bg, attr) \
1311 LINE_##type
1312 LINE_INFO,
1313 LINE_NONE
1314 #undef LINE
1317 struct line_info {
1318 const char *name; /* Option name. */
1319 int namelen; /* Size of option name. */
1320 const char *line; /* The start of line to match. */
1321 int linelen; /* Size of string to match. */
1322 int fg, bg, attr; /* Color and text attributes for the lines. */
1325 static struct line_info line_info[] = {
1326 #define LINE(type, line, fg, bg, attr) \
1327 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1328 LINE_INFO
1329 #undef LINE
1332 static enum line_type
1333 get_line_type(const char *line)
1335 int linelen = strlen(line);
1336 enum line_type type;
1338 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1339 /* Case insensitive search matches Signed-off-by lines better. */
1340 if (linelen >= line_info[type].linelen &&
1341 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1342 return type;
1344 return LINE_DEFAULT;
1347 static inline int
1348 get_line_attr(enum line_type type)
1350 assert(type < ARRAY_SIZE(line_info));
1351 return COLOR_PAIR(type) | line_info[type].attr;
1354 static struct line_info *
1355 get_line_info(const char *name)
1357 size_t namelen = strlen(name);
1358 enum line_type type;
1360 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1361 if (enum_equals(line_info[type], name, namelen))
1362 return &line_info[type];
1364 return NULL;
1367 static void
1368 init_colors(void)
1370 int default_bg = line_info[LINE_DEFAULT].bg;
1371 int default_fg = line_info[LINE_DEFAULT].fg;
1372 enum line_type type;
1374 start_color();
1376 if (assume_default_colors(default_fg, default_bg) == ERR) {
1377 default_bg = COLOR_BLACK;
1378 default_fg = COLOR_WHITE;
1381 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1382 struct line_info *info = &line_info[type];
1383 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1384 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1386 init_pair(type, fg, bg);
1390 struct line {
1391 enum line_type type;
1393 /* State flags */
1394 unsigned int selected:1;
1395 unsigned int dirty:1;
1396 unsigned int cleareol:1;
1397 unsigned int other:16;
1399 void *data; /* User data */
1404 * Keys
1407 struct keybinding {
1408 int alias;
1409 enum request request;
1412 static struct keybinding default_keybindings[] = {
1413 /* View switching */
1414 { 'm', REQ_VIEW_MAIN },
1415 { 'd', REQ_VIEW_DIFF },
1416 { 'l', REQ_VIEW_LOG },
1417 { 't', REQ_VIEW_TREE },
1418 { 'f', REQ_VIEW_BLOB },
1419 { 'B', REQ_VIEW_BLAME },
1420 { 'H', REQ_VIEW_BRANCH },
1421 { 'p', REQ_VIEW_PAGER },
1422 { 'h', REQ_VIEW_HELP },
1423 { 'S', REQ_VIEW_STATUS },
1424 { 'c', REQ_VIEW_STAGE },
1426 /* View manipulation */
1427 { 'q', REQ_VIEW_CLOSE },
1428 { KEY_TAB, REQ_VIEW_NEXT },
1429 { KEY_RETURN, REQ_ENTER },
1430 { KEY_UP, REQ_PREVIOUS },
1431 { KEY_DOWN, REQ_NEXT },
1432 { 'R', REQ_REFRESH },
1433 { KEY_F(5), REQ_REFRESH },
1434 { 'O', REQ_MAXIMIZE },
1436 /* Cursor navigation */
1437 { 'k', REQ_MOVE_UP },
1438 { 'j', REQ_MOVE_DOWN },
1439 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1440 { KEY_END, REQ_MOVE_LAST_LINE },
1441 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1442 { ' ', REQ_MOVE_PAGE_DOWN },
1443 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1444 { 'b', REQ_MOVE_PAGE_UP },
1445 { '-', REQ_MOVE_PAGE_UP },
1447 /* Scrolling */
1448 { KEY_LEFT, REQ_SCROLL_LEFT },
1449 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1450 { KEY_IC, REQ_SCROLL_LINE_UP },
1451 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1452 { 'w', REQ_SCROLL_PAGE_UP },
1453 { 's', REQ_SCROLL_PAGE_DOWN },
1455 /* Searching */
1456 { '/', REQ_SEARCH },
1457 { '?', REQ_SEARCH_BACK },
1458 { 'n', REQ_FIND_NEXT },
1459 { 'N', REQ_FIND_PREV },
1461 /* Misc */
1462 { 'Q', REQ_QUIT },
1463 { 'z', REQ_STOP_LOADING },
1464 { 'v', REQ_SHOW_VERSION },
1465 { 'r', REQ_SCREEN_REDRAW },
1466 { 'o', REQ_OPTIONS },
1467 { '.', REQ_TOGGLE_LINENO },
1468 { 'D', REQ_TOGGLE_DATE },
1469 { 'A', REQ_TOGGLE_AUTHOR },
1470 { 'g', REQ_TOGGLE_REV_GRAPH },
1471 { 'F', REQ_TOGGLE_REFS },
1472 { 'I', REQ_TOGGLE_SORT_ORDER },
1473 { 'i', REQ_TOGGLE_SORT_FIELD },
1474 { ':', REQ_PROMPT },
1475 { 'u', REQ_STATUS_UPDATE },
1476 { '!', REQ_STATUS_REVERT },
1477 { 'M', REQ_STATUS_MERGE },
1478 { '@', REQ_STAGE_NEXT },
1479 { ',', REQ_PARENT },
1480 { 'e', REQ_EDIT },
1483 #define KEYMAP_INFO \
1484 KEYMAP_(GENERIC), \
1485 KEYMAP_(MAIN), \
1486 KEYMAP_(DIFF), \
1487 KEYMAP_(LOG), \
1488 KEYMAP_(TREE), \
1489 KEYMAP_(BLOB), \
1490 KEYMAP_(BLAME), \
1491 KEYMAP_(BRANCH), \
1492 KEYMAP_(PAGER), \
1493 KEYMAP_(HELP), \
1494 KEYMAP_(STATUS), \
1495 KEYMAP_(STAGE)
1497 enum keymap {
1498 #define KEYMAP_(name) KEYMAP_##name
1499 KEYMAP_INFO
1500 #undef KEYMAP_
1503 static const struct enum_map keymap_table[] = {
1504 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1505 KEYMAP_INFO
1506 #undef KEYMAP_
1509 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1511 struct keybinding_table {
1512 struct keybinding *data;
1513 size_t size;
1516 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1518 static void
1519 add_keybinding(enum keymap keymap, enum request request, int key)
1521 struct keybinding_table *table = &keybindings[keymap];
1522 size_t i;
1524 for (i = 0; i < keybindings[keymap].size; i++) {
1525 if (keybindings[keymap].data[i].alias == key) {
1526 keybindings[keymap].data[i].request = request;
1527 return;
1531 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1532 if (!table->data)
1533 die("Failed to allocate keybinding");
1534 table->data[table->size].alias = key;
1535 table->data[table->size++].request = request;
1537 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1538 int i;
1540 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541 if (default_keybindings[i].alias == key)
1542 default_keybindings[i].request = REQ_NONE;
1546 /* Looks for a key binding first in the given map, then in the generic map, and
1547 * lastly in the default keybindings. */
1548 static enum request
1549 get_keybinding(enum keymap keymap, int key)
1551 size_t i;
1553 for (i = 0; i < keybindings[keymap].size; i++)
1554 if (keybindings[keymap].data[i].alias == key)
1555 return keybindings[keymap].data[i].request;
1557 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1558 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1559 return keybindings[KEYMAP_GENERIC].data[i].request;
1561 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562 if (default_keybindings[i].alias == key)
1563 return default_keybindings[i].request;
1565 return (enum request) key;
1569 struct key {
1570 const char *name;
1571 int value;
1574 static const struct key key_table[] = {
1575 { "Enter", KEY_RETURN },
1576 { "Space", ' ' },
1577 { "Backspace", KEY_BACKSPACE },
1578 { "Tab", KEY_TAB },
1579 { "Escape", KEY_ESC },
1580 { "Left", KEY_LEFT },
1581 { "Right", KEY_RIGHT },
1582 { "Up", KEY_UP },
1583 { "Down", KEY_DOWN },
1584 { "Insert", KEY_IC },
1585 { "Delete", KEY_DC },
1586 { "Hash", '#' },
1587 { "Home", KEY_HOME },
1588 { "End", KEY_END },
1589 { "PageUp", KEY_PPAGE },
1590 { "PageDown", KEY_NPAGE },
1591 { "F1", KEY_F(1) },
1592 { "F2", KEY_F(2) },
1593 { "F3", KEY_F(3) },
1594 { "F4", KEY_F(4) },
1595 { "F5", KEY_F(5) },
1596 { "F6", KEY_F(6) },
1597 { "F7", KEY_F(7) },
1598 { "F8", KEY_F(8) },
1599 { "F9", KEY_F(9) },
1600 { "F10", KEY_F(10) },
1601 { "F11", KEY_F(11) },
1602 { "F12", KEY_F(12) },
1605 static int
1606 get_key_value(const char *name)
1608 int i;
1610 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1611 if (!strcasecmp(key_table[i].name, name))
1612 return key_table[i].value;
1614 if (strlen(name) == 1 && isprint(*name))
1615 return (int) *name;
1617 return ERR;
1620 static const char *
1621 get_key_name(int key_value)
1623 static char key_char[] = "'X'";
1624 const char *seq = NULL;
1625 int key;
1627 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1628 if (key_table[key].value == key_value)
1629 seq = key_table[key].name;
1631 if (seq == NULL &&
1632 key_value < 127 &&
1633 isprint(key_value)) {
1634 key_char[1] = (char) key_value;
1635 seq = key_char;
1638 return seq ? seq : "(no key)";
1641 static bool
1642 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1644 const char *sep = *pos > 0 ? ", " : "";
1645 const char *keyname = get_key_name(keybinding->alias);
1647 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1650 static bool
1651 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1652 enum keymap keymap, bool all)
1654 int i;
1656 for (i = 0; i < keybindings[keymap].size; i++) {
1657 if (keybindings[keymap].data[i].request == request) {
1658 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1659 return FALSE;
1660 if (!all)
1661 break;
1665 return TRUE;
1668 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1670 static const char *
1671 get_keys(enum keymap keymap, enum request request, bool all)
1673 static char buf[BUFSIZ];
1674 size_t pos = 0;
1675 int i;
1677 buf[pos] = 0;
1679 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1680 return "Too many keybindings!";
1681 if (pos > 0 && !all)
1682 return buf;
1684 if (keymap != KEYMAP_GENERIC) {
1685 /* Only the generic keymap includes the default keybindings when
1686 * listing all keys. */
1687 if (all)
1688 return buf;
1690 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1691 return "Too many keybindings!";
1692 if (pos)
1693 return buf;
1696 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1697 if (default_keybindings[i].request == request) {
1698 if (!append_key(buf, &pos, &default_keybindings[i]))
1699 return "Too many keybindings!";
1700 if (!all)
1701 return buf;
1705 return buf;
1708 struct run_request {
1709 enum keymap keymap;
1710 int key;
1711 const char **argv;
1714 static struct run_request *run_request;
1715 static size_t run_requests;
1717 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1719 static enum request
1720 add_run_request(enum keymap keymap, int key, const char **argv)
1722 struct run_request *req;
1724 if (!realloc_run_requests(&run_request, run_requests, 1))
1725 return REQ_NONE;
1727 req = &run_request[run_requests];
1728 req->keymap = keymap;
1729 req->key = key;
1730 req->argv = NULL;
1732 if (!argv_copy(&req->argv, argv))
1733 return REQ_NONE;
1735 return REQ_NONE + ++run_requests;
1738 static struct run_request *
1739 get_run_request(enum request request)
1741 if (request <= REQ_NONE)
1742 return NULL;
1743 return &run_request[request - REQ_NONE - 1];
1746 static void
1747 add_builtin_run_requests(void)
1749 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1750 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1751 const char *commit[] = { "git", "commit", NULL };
1752 const char *gc[] = { "git", "gc", NULL };
1753 struct run_request reqs[] = {
1754 { KEYMAP_MAIN, 'C', cherry_pick },
1755 { KEYMAP_STATUS, 'C', commit },
1756 { KEYMAP_BRANCH, 'C', checkout },
1757 { KEYMAP_GENERIC, 'G', gc },
1759 int i;
1761 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1762 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1764 if (req != reqs[i].key)
1765 continue;
1766 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1767 if (req != REQ_NONE)
1768 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1773 * User config file handling.
1776 static int config_lineno;
1777 static bool config_errors;
1778 static const char *config_msg;
1780 static const struct enum_map color_map[] = {
1781 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1782 COLOR_MAP(DEFAULT),
1783 COLOR_MAP(BLACK),
1784 COLOR_MAP(BLUE),
1785 COLOR_MAP(CYAN),
1786 COLOR_MAP(GREEN),
1787 COLOR_MAP(MAGENTA),
1788 COLOR_MAP(RED),
1789 COLOR_MAP(WHITE),
1790 COLOR_MAP(YELLOW),
1793 static const struct enum_map attr_map[] = {
1794 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1795 ATTR_MAP(NORMAL),
1796 ATTR_MAP(BLINK),
1797 ATTR_MAP(BOLD),
1798 ATTR_MAP(DIM),
1799 ATTR_MAP(REVERSE),
1800 ATTR_MAP(STANDOUT),
1801 ATTR_MAP(UNDERLINE),
1804 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1806 static int parse_step(double *opt, const char *arg)
1808 *opt = atoi(arg);
1809 if (!strchr(arg, '%'))
1810 return OK;
1812 /* "Shift down" so 100% and 1 does not conflict. */
1813 *opt = (*opt - 1) / 100;
1814 if (*opt >= 1.0) {
1815 *opt = 0.99;
1816 config_msg = "Step value larger than 100%";
1817 return ERR;
1819 if (*opt < 0.0) {
1820 *opt = 1;
1821 config_msg = "Invalid step value";
1822 return ERR;
1824 return OK;
1827 static int
1828 parse_int(int *opt, const char *arg, int min, int max)
1830 int value = atoi(arg);
1832 if (min <= value && value <= max) {
1833 *opt = value;
1834 return OK;
1837 config_msg = "Integer value out of bound";
1838 return ERR;
1841 static bool
1842 set_color(int *color, const char *name)
1844 if (map_enum(color, color_map, name))
1845 return TRUE;
1846 if (!prefixcmp(name, "color"))
1847 return parse_int(color, name + 5, 0, 255) == OK;
1848 return FALSE;
1851 /* Wants: object fgcolor bgcolor [attribute] */
1852 static int
1853 option_color_command(int argc, const char *argv[])
1855 struct line_info *info;
1857 if (argc < 3) {
1858 config_msg = "Wrong number of arguments given to color command";
1859 return ERR;
1862 info = get_line_info(argv[0]);
1863 if (!info) {
1864 static const struct enum_map obsolete[] = {
1865 ENUM_MAP("main-delim", LINE_DELIMITER),
1866 ENUM_MAP("main-date", LINE_DATE),
1867 ENUM_MAP("main-author", LINE_AUTHOR),
1869 int index;
1871 if (!map_enum(&index, obsolete, argv[0])) {
1872 config_msg = "Unknown color name";
1873 return ERR;
1875 info = &line_info[index];
1878 if (!set_color(&info->fg, argv[1]) ||
1879 !set_color(&info->bg, argv[2])) {
1880 config_msg = "Unknown color";
1881 return ERR;
1884 info->attr = 0;
1885 while (argc-- > 3) {
1886 int attr;
1888 if (!set_attribute(&attr, argv[argc])) {
1889 config_msg = "Unknown attribute";
1890 return ERR;
1892 info->attr |= attr;
1895 return OK;
1898 static int parse_bool(bool *opt, const char *arg)
1900 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1901 ? TRUE : FALSE;
1902 return OK;
1905 static int parse_enum_do(unsigned int *opt, const char *arg,
1906 const struct enum_map *map, size_t map_size)
1908 bool is_true;
1910 assert(map_size > 1);
1912 if (map_enum_do(map, map_size, (int *) opt, arg))
1913 return OK;
1915 if (parse_bool(&is_true, arg) != OK)
1916 return ERR;
1918 *opt = is_true ? map[1].value : map[0].value;
1919 return OK;
1922 #define parse_enum(opt, arg, map) \
1923 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1925 static int
1926 parse_string(char *opt, const char *arg, size_t optsize)
1928 int arglen = strlen(arg);
1930 switch (arg[0]) {
1931 case '\"':
1932 case '\'':
1933 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1934 config_msg = "Unmatched quotation";
1935 return ERR;
1937 arg += 1; arglen -= 2;
1938 default:
1939 string_ncopy_do(opt, optsize, arg, arglen);
1940 return OK;
1944 /* Wants: name = value */
1945 static int
1946 option_set_command(int argc, const char *argv[])
1948 if (argc != 3) {
1949 config_msg = "Wrong number of arguments given to set command";
1950 return ERR;
1953 if (strcmp(argv[1], "=")) {
1954 config_msg = "No value assigned";
1955 return ERR;
1958 if (!strcmp(argv[0], "show-author"))
1959 return parse_enum(&opt_author, argv[2], author_map);
1961 if (!strcmp(argv[0], "show-date"))
1962 return parse_enum(&opt_date, argv[2], date_map);
1964 if (!strcmp(argv[0], "show-rev-graph"))
1965 return parse_bool(&opt_rev_graph, argv[2]);
1967 if (!strcmp(argv[0], "show-refs"))
1968 return parse_bool(&opt_show_refs, argv[2]);
1970 if (!strcmp(argv[0], "show-line-numbers"))
1971 return parse_bool(&opt_line_number, argv[2]);
1973 if (!strcmp(argv[0], "line-graphics"))
1974 return parse_bool(&opt_line_graphics, argv[2]);
1976 if (!strcmp(argv[0], "line-number-interval"))
1977 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1979 if (!strcmp(argv[0], "author-width"))
1980 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1982 if (!strcmp(argv[0], "horizontal-scroll"))
1983 return parse_step(&opt_hscroll, argv[2]);
1985 if (!strcmp(argv[0], "split-view-height"))
1986 return parse_step(&opt_scale_split_view, argv[2]);
1988 if (!strcmp(argv[0], "tab-size"))
1989 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1991 if (!strcmp(argv[0], "commit-encoding"))
1992 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1994 config_msg = "Unknown variable name";
1995 return ERR;
1998 /* Wants: mode request key */
1999 static int
2000 option_bind_command(int argc, const char *argv[])
2002 enum request request;
2003 int keymap = -1;
2004 int key;
2006 if (argc < 3) {
2007 config_msg = "Wrong number of arguments given to bind command";
2008 return ERR;
2011 if (!set_keymap(&keymap, argv[0])) {
2012 config_msg = "Unknown key map";
2013 return ERR;
2016 key = get_key_value(argv[1]);
2017 if (key == ERR) {
2018 config_msg = "Unknown key";
2019 return ERR;
2022 request = get_request(argv[2]);
2023 if (request == REQ_UNKNOWN) {
2024 static const struct enum_map obsolete[] = {
2025 ENUM_MAP("cherry-pick", REQ_NONE),
2026 ENUM_MAP("screen-resize", REQ_NONE),
2027 ENUM_MAP("tree-parent", REQ_PARENT),
2029 int alias;
2031 if (map_enum(&alias, obsolete, argv[2])) {
2032 if (alias != REQ_NONE)
2033 add_keybinding(keymap, alias, key);
2034 config_msg = "Obsolete request name";
2035 return ERR;
2038 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2039 request = add_run_request(keymap, key, argv + 2);
2040 if (request == REQ_UNKNOWN) {
2041 config_msg = "Unknown request name";
2042 return ERR;
2045 add_keybinding(keymap, request, key);
2047 return OK;
2050 static int
2051 set_option(const char *opt, char *value)
2053 const char *argv[SIZEOF_ARG];
2054 int argc = 0;
2056 if (!argv_from_string(argv, &argc, value)) {
2057 config_msg = "Too many option arguments";
2058 return ERR;
2061 if (!strcmp(opt, "color"))
2062 return option_color_command(argc, argv);
2064 if (!strcmp(opt, "set"))
2065 return option_set_command(argc, argv);
2067 if (!strcmp(opt, "bind"))
2068 return option_bind_command(argc, argv);
2070 config_msg = "Unknown option command";
2071 return ERR;
2074 static int
2075 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2077 int status = OK;
2079 config_lineno++;
2080 config_msg = "Internal error";
2082 /* Check for comment markers, since read_properties() will
2083 * only ensure opt and value are split at first " \t". */
2084 optlen = strcspn(opt, "#");
2085 if (optlen == 0)
2086 return OK;
2088 if (opt[optlen] != 0) {
2089 config_msg = "No option value";
2090 status = ERR;
2092 } else {
2093 /* Look for comment endings in the value. */
2094 size_t len = strcspn(value, "#");
2096 if (len < valuelen) {
2097 valuelen = len;
2098 value[valuelen] = 0;
2101 status = set_option(opt, value);
2104 if (status == ERR) {
2105 warn("Error on line %d, near '%.*s': %s",
2106 config_lineno, (int) optlen, opt, config_msg);
2107 config_errors = TRUE;
2110 /* Always keep going if errors are encountered. */
2111 return OK;
2114 static void
2115 load_option_file(const char *path)
2117 struct io io;
2119 /* It's OK that the file doesn't exist. */
2120 if (!io_open(&io, "%s", path))
2121 return;
2123 config_lineno = 0;
2124 config_errors = FALSE;
2126 if (io_load(&io, " \t", read_option) == ERR ||
2127 config_errors == TRUE)
2128 warn("Errors while loading %s.", path);
2131 static int
2132 load_options(void)
2134 const char *home = getenv("HOME");
2135 const char *tigrc_user = getenv("TIGRC_USER");
2136 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2137 char buf[SIZEOF_STR];
2139 if (!tigrc_system)
2140 tigrc_system = SYSCONFDIR "/tigrc";
2141 load_option_file(tigrc_system);
2143 if (!tigrc_user) {
2144 if (!home || !string_format(buf, "%s/.tigrc", home))
2145 return ERR;
2146 tigrc_user = buf;
2148 load_option_file(tigrc_user);
2150 /* Add _after_ loading config files to avoid adding run requests
2151 * that conflict with keybindings. */
2152 add_builtin_run_requests();
2154 return OK;
2159 * The viewer
2162 struct view;
2163 struct view_ops;
2165 /* The display array of active views and the index of the current view. */
2166 static struct view *display[2];
2167 static unsigned int current_view;
2169 #define foreach_displayed_view(view, i) \
2170 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2172 #define displayed_views() (display[1] != NULL ? 2 : 1)
2174 /* Current head and commit ID */
2175 static char ref_blob[SIZEOF_REF] = "";
2176 static char ref_commit[SIZEOF_REF] = "HEAD";
2177 static char ref_head[SIZEOF_REF] = "HEAD";
2178 static char ref_branch[SIZEOF_REF] = "";
2180 enum view_type {
2181 VIEW_MAIN,
2182 VIEW_DIFF,
2183 VIEW_LOG,
2184 VIEW_TREE,
2185 VIEW_BLOB,
2186 VIEW_BLAME,
2187 VIEW_BRANCH,
2188 VIEW_HELP,
2189 VIEW_PAGER,
2190 VIEW_STATUS,
2191 VIEW_STAGE,
2194 struct view {
2195 enum view_type type; /* View type */
2196 const char *name; /* View name */
2197 const char *cmd_env; /* Command line set via environment */
2198 const char *id; /* Points to either of ref_{head,commit,blob} */
2200 struct view_ops *ops; /* View operations */
2202 enum keymap keymap; /* What keymap does this view have */
2203 bool git_dir; /* Whether the view requires a git directory. */
2205 char ref[SIZEOF_REF]; /* Hovered commit reference */
2206 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2208 int height, width; /* The width and height of the main window */
2209 WINDOW *win; /* The main window */
2210 WINDOW *title; /* The title window living below the main window */
2212 /* Navigation */
2213 unsigned long offset; /* Offset of the window top */
2214 unsigned long yoffset; /* Offset from the window side. */
2215 unsigned long lineno; /* Current line number */
2216 unsigned long p_offset; /* Previous offset of the window top */
2217 unsigned long p_yoffset;/* Previous offset from the window side */
2218 unsigned long p_lineno; /* Previous current line number */
2219 bool p_restore; /* Should the previous position be restored. */
2221 /* Searching */
2222 char grep[SIZEOF_STR]; /* Search string */
2223 regex_t *regex; /* Pre-compiled regexp */
2225 /* If non-NULL, points to the view that opened this view. If this view
2226 * is closed tig will switch back to the parent view. */
2227 struct view *parent;
2228 struct view *prev;
2230 /* Buffering */
2231 size_t lines; /* Total number of lines */
2232 struct line *line; /* Line index */
2233 unsigned int digits; /* Number of digits in the lines member. */
2235 /* Drawing */
2236 struct line *curline; /* Line currently being drawn. */
2237 enum line_type curtype; /* Attribute currently used for drawing. */
2238 unsigned long col; /* Column when drawing. */
2239 bool has_scrolled; /* View was scrolled. */
2241 /* Loading */
2242 const char **argv; /* Shell command arguments. */
2243 const char *dir; /* Directory from which to execute. */
2244 struct io io;
2245 struct io *pipe;
2246 time_t start_time;
2247 time_t update_secs;
2250 struct view_ops {
2251 /* What type of content being displayed. Used in the title bar. */
2252 const char *type;
2253 /* Default command arguments. */
2254 const char **argv;
2255 /* Open and reads in all view content. */
2256 bool (*open)(struct view *view);
2257 /* Read one line; updates view->line. */
2258 bool (*read)(struct view *view, char *data);
2259 /* Draw one line; @lineno must be < view->height. */
2260 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2261 /* Depending on view handle a special requests. */
2262 enum request (*request)(struct view *view, enum request request, struct line *line);
2263 /* Search for regexp in a line. */
2264 bool (*grep)(struct view *view, struct line *line);
2265 /* Select line */
2266 void (*select)(struct view *view, struct line *line);
2267 /* Prepare view for loading */
2268 bool (*prepare)(struct view *view);
2271 static struct view_ops blame_ops;
2272 static struct view_ops blob_ops;
2273 static struct view_ops diff_ops;
2274 static struct view_ops help_ops;
2275 static struct view_ops log_ops;
2276 static struct view_ops main_ops;
2277 static struct view_ops pager_ops;
2278 static struct view_ops stage_ops;
2279 static struct view_ops status_ops;
2280 static struct view_ops tree_ops;
2281 static struct view_ops branch_ops;
2283 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2284 { type, name, #env, ref, ops, map, git }
2286 #define VIEW_(id, name, ops, git, ref) \
2287 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2289 static struct view views[] = {
2290 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2291 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2292 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2293 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2294 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2295 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2296 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2297 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2298 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2299 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2300 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2303 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2305 #define foreach_view(view, i) \
2306 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2308 #define view_is_displayed(view) \
2309 (view == display[0] || view == display[1])
2311 static enum request
2312 view_request(struct view *view, enum request request)
2314 if (!view || !view->lines)
2315 return request;
2316 return view->ops->request(view, request, &view->line[view->lineno]);
2321 * View drawing.
2324 static inline void
2325 set_view_attr(struct view *view, enum line_type type)
2327 if (!view->curline->selected && view->curtype != type) {
2328 (void) wattrset(view->win, get_line_attr(type));
2329 wchgat(view->win, -1, 0, type, NULL);
2330 view->curtype = type;
2334 static int
2335 draw_chars(struct view *view, enum line_type type, const char *string,
2336 int max_len, bool use_tilde)
2338 static char out_buffer[BUFSIZ * 2];
2339 int len = 0;
2340 int col = 0;
2341 int trimmed = FALSE;
2342 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2344 if (max_len <= 0)
2345 return 0;
2347 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2349 set_view_attr(view, type);
2350 if (len > 0) {
2351 if (opt_iconv_out != ICONV_NONE) {
2352 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2353 size_t inlen = len + 1;
2355 char *outbuf = out_buffer;
2356 size_t outlen = sizeof(out_buffer);
2358 size_t ret;
2360 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2361 if (ret != (size_t) -1) {
2362 string = out_buffer;
2363 len = sizeof(out_buffer) - outlen;
2367 waddnstr(view->win, string, len);
2369 if (trimmed && use_tilde) {
2370 set_view_attr(view, LINE_DELIMITER);
2371 waddch(view->win, '~');
2372 col++;
2375 return col;
2378 static int
2379 draw_space(struct view *view, enum line_type type, int max, int spaces)
2381 static char space[] = " ";
2382 int col = 0;
2384 spaces = MIN(max, spaces);
2386 while (spaces > 0) {
2387 int len = MIN(spaces, sizeof(space) - 1);
2389 col += draw_chars(view, type, space, len, FALSE);
2390 spaces -= len;
2393 return col;
2396 static bool
2397 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2399 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2400 return view->width + view->yoffset <= view->col;
2403 static bool
2404 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2406 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2407 int max = view->width + view->yoffset - view->col;
2408 int i;
2410 if (max < size)
2411 size = max;
2413 set_view_attr(view, type);
2414 /* Using waddch() instead of waddnstr() ensures that
2415 * they'll be rendered correctly for the cursor line. */
2416 for (i = skip; i < size; i++)
2417 waddch(view->win, graphic[i]);
2419 view->col += size;
2420 if (size < max && skip <= size)
2421 waddch(view->win, ' ');
2422 view->col++;
2424 return view->width + view->yoffset <= view->col;
2427 static bool
2428 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2430 int max = MIN(view->width + view->yoffset - view->col, len);
2431 int col;
2433 if (text)
2434 col = draw_chars(view, type, text, max - 1, trim);
2435 else
2436 col = draw_space(view, type, max - 1, max - 1);
2438 view->col += col;
2439 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2440 return view->width + view->yoffset <= view->col;
2443 static bool
2444 draw_date(struct view *view, struct time *time)
2446 const char *date = mkdate(time, opt_date);
2447 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2449 return draw_field(view, LINE_DATE, date, cols, FALSE);
2452 static bool
2453 draw_author(struct view *view, const char *author)
2455 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2456 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2458 if (abbreviate && author)
2459 author = get_author_initials(author);
2461 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2464 static bool
2465 draw_mode(struct view *view, mode_t mode)
2467 const char *str;
2469 if (S_ISDIR(mode))
2470 str = "drwxr-xr-x";
2471 else if (S_ISLNK(mode))
2472 str = "lrwxrwxrwx";
2473 else if (S_ISGITLINK(mode))
2474 str = "m---------";
2475 else if (S_ISREG(mode) && mode & S_IXUSR)
2476 str = "-rwxr-xr-x";
2477 else if (S_ISREG(mode))
2478 str = "-rw-r--r--";
2479 else
2480 str = "----------";
2482 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2485 static bool
2486 draw_lineno(struct view *view, unsigned int lineno)
2488 char number[10];
2489 int digits3 = view->digits < 3 ? 3 : view->digits;
2490 int max = MIN(view->width + view->yoffset - view->col, digits3);
2491 char *text = NULL;
2492 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2494 lineno += view->offset + 1;
2495 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2496 static char fmt[] = "%1ld";
2498 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2499 if (string_format(number, fmt, lineno))
2500 text = number;
2502 if (text)
2503 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2504 else
2505 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2506 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2509 static bool
2510 draw_view_line(struct view *view, unsigned int lineno)
2512 struct line *line;
2513 bool selected = (view->offset + lineno == view->lineno);
2515 assert(view_is_displayed(view));
2517 if (view->offset + lineno >= view->lines)
2518 return FALSE;
2520 line = &view->line[view->offset + lineno];
2522 wmove(view->win, lineno, 0);
2523 if (line->cleareol)
2524 wclrtoeol(view->win);
2525 view->col = 0;
2526 view->curline = line;
2527 view->curtype = LINE_NONE;
2528 line->selected = FALSE;
2529 line->dirty = line->cleareol = 0;
2531 if (selected) {
2532 set_view_attr(view, LINE_CURSOR);
2533 line->selected = TRUE;
2534 view->ops->select(view, line);
2537 return view->ops->draw(view, line, lineno);
2540 static void
2541 redraw_view_dirty(struct view *view)
2543 bool dirty = FALSE;
2544 int lineno;
2546 for (lineno = 0; lineno < view->height; lineno++) {
2547 if (view->offset + lineno >= view->lines)
2548 break;
2549 if (!view->line[view->offset + lineno].dirty)
2550 continue;
2551 dirty = TRUE;
2552 if (!draw_view_line(view, lineno))
2553 break;
2556 if (!dirty)
2557 return;
2558 wnoutrefresh(view->win);
2561 static void
2562 redraw_view_from(struct view *view, int lineno)
2564 assert(0 <= lineno && lineno < view->height);
2566 for (; lineno < view->height; lineno++) {
2567 if (!draw_view_line(view, lineno))
2568 break;
2571 wnoutrefresh(view->win);
2574 static void
2575 redraw_view(struct view *view)
2577 werase(view->win);
2578 redraw_view_from(view, 0);
2582 static void
2583 update_view_title(struct view *view)
2585 char buf[SIZEOF_STR];
2586 char state[SIZEOF_STR];
2587 size_t bufpos = 0, statelen = 0;
2589 assert(view_is_displayed(view));
2591 if (view->type != VIEW_STATUS && view->lines) {
2592 unsigned int view_lines = view->offset + view->height;
2593 unsigned int lines = view->lines
2594 ? MIN(view_lines, view->lines) * 100 / view->lines
2595 : 0;
2597 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2598 view->ops->type,
2599 view->lineno + 1,
2600 view->lines,
2601 lines);
2605 if (view->pipe) {
2606 time_t secs = time(NULL) - view->start_time;
2608 /* Three git seconds are a long time ... */
2609 if (secs > 2)
2610 string_format_from(state, &statelen, " loading %lds", secs);
2613 string_format_from(buf, &bufpos, "[%s]", view->name);
2614 if (*view->ref && bufpos < view->width) {
2615 size_t refsize = strlen(view->ref);
2616 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2618 if (minsize < view->width)
2619 refsize = view->width - minsize + 7;
2620 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2623 if (statelen && bufpos < view->width) {
2624 string_format_from(buf, &bufpos, "%s", state);
2627 if (view == display[current_view])
2628 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2629 else
2630 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2632 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2633 wclrtoeol(view->title);
2634 wnoutrefresh(view->title);
2637 static int
2638 apply_step(double step, int value)
2640 if (step >= 1)
2641 return (int) step;
2642 value *= step + 0.01;
2643 return value ? value : 1;
2646 static void
2647 resize_display(void)
2649 int offset, i;
2650 struct view *base = display[0];
2651 struct view *view = display[1] ? display[1] : display[0];
2653 /* Setup window dimensions */
2655 getmaxyx(stdscr, base->height, base->width);
2657 /* Make room for the status window. */
2658 base->height -= 1;
2660 if (view != base) {
2661 /* Horizontal split. */
2662 view->width = base->width;
2663 view->height = apply_step(opt_scale_split_view, base->height);
2664 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2665 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2666 base->height -= view->height;
2668 /* Make room for the title bar. */
2669 view->height -= 1;
2672 /* Make room for the title bar. */
2673 base->height -= 1;
2675 offset = 0;
2677 foreach_displayed_view (view, i) {
2678 if (!view->win) {
2679 view->win = newwin(view->height, 0, offset, 0);
2680 if (!view->win)
2681 die("Failed to create %s view", view->name);
2683 scrollok(view->win, FALSE);
2685 view->title = newwin(1, 0, offset + view->height, 0);
2686 if (!view->title)
2687 die("Failed to create title window");
2689 } else {
2690 wresize(view->win, view->height, view->width);
2691 mvwin(view->win, offset, 0);
2692 mvwin(view->title, offset + view->height, 0);
2695 offset += view->height + 1;
2699 static void
2700 redraw_display(bool clear)
2702 struct view *view;
2703 int i;
2705 foreach_displayed_view (view, i) {
2706 if (clear)
2707 wclear(view->win);
2708 redraw_view(view);
2709 update_view_title(view);
2715 * Option management
2718 static void
2719 toggle_enum_option_do(unsigned int *opt, const char *help,
2720 const struct enum_map *map, size_t size)
2722 *opt = (*opt + 1) % size;
2723 redraw_display(FALSE);
2724 report("Displaying %s %s", enum_name(map[*opt]), help);
2727 #define toggle_enum_option(opt, help, map) \
2728 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2730 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2731 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2733 static void
2734 toggle_view_option(bool *option, const char *help)
2736 *option = !*option;
2737 redraw_display(FALSE);
2738 report("%sabling %s", *option ? "En" : "Dis", help);
2741 static void
2742 open_option_menu(void)
2744 const struct menu_item menu[] = {
2745 { '.', "line numbers", &opt_line_number },
2746 { 'D', "date display", &opt_date },
2747 { 'A', "author display", &opt_author },
2748 { 'g', "revision graph display", &opt_rev_graph },
2749 { 'F', "reference display", &opt_show_refs },
2750 { 0 }
2752 int selected = 0;
2754 if (prompt_menu("Toggle option", menu, &selected)) {
2755 if (menu[selected].data == &opt_date)
2756 toggle_date();
2757 else if (menu[selected].data == &opt_author)
2758 toggle_author();
2759 else
2760 toggle_view_option(menu[selected].data, menu[selected].text);
2764 static void
2765 maximize_view(struct view *view)
2767 memset(display, 0, sizeof(display));
2768 current_view = 0;
2769 display[current_view] = view;
2770 resize_display();
2771 redraw_display(FALSE);
2772 report("");
2777 * Navigation
2780 static bool
2781 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2783 if (lineno >= view->lines)
2784 lineno = view->lines > 0 ? view->lines - 1 : 0;
2786 if (offset > lineno || offset + view->height <= lineno) {
2787 unsigned long half = view->height / 2;
2789 if (lineno > half)
2790 offset = lineno - half;
2791 else
2792 offset = 0;
2795 if (offset != view->offset || lineno != view->lineno) {
2796 view->offset = offset;
2797 view->lineno = lineno;
2798 return TRUE;
2801 return FALSE;
2804 /* Scrolling backend */
2805 static void
2806 do_scroll_view(struct view *view, int lines)
2808 bool redraw_current_line = FALSE;
2810 /* The rendering expects the new offset. */
2811 view->offset += lines;
2813 assert(0 <= view->offset && view->offset < view->lines);
2814 assert(lines);
2816 /* Move current line into the view. */
2817 if (view->lineno < view->offset) {
2818 view->lineno = view->offset;
2819 redraw_current_line = TRUE;
2820 } else if (view->lineno >= view->offset + view->height) {
2821 view->lineno = view->offset + view->height - 1;
2822 redraw_current_line = TRUE;
2825 assert(view->offset <= view->lineno && view->lineno < view->lines);
2827 /* Redraw the whole screen if scrolling is pointless. */
2828 if (view->height < ABS(lines)) {
2829 redraw_view(view);
2831 } else {
2832 int line = lines > 0 ? view->height - lines : 0;
2833 int end = line + ABS(lines);
2835 scrollok(view->win, TRUE);
2836 wscrl(view->win, lines);
2837 scrollok(view->win, FALSE);
2839 while (line < end && draw_view_line(view, line))
2840 line++;
2842 if (redraw_current_line)
2843 draw_view_line(view, view->lineno - view->offset);
2844 wnoutrefresh(view->win);
2847 view->has_scrolled = TRUE;
2848 report("");
2851 /* Scroll frontend */
2852 static void
2853 scroll_view(struct view *view, enum request request)
2855 int lines = 1;
2857 assert(view_is_displayed(view));
2859 switch (request) {
2860 case REQ_SCROLL_LEFT:
2861 if (view->yoffset == 0) {
2862 report("Cannot scroll beyond the first column");
2863 return;
2865 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2866 view->yoffset = 0;
2867 else
2868 view->yoffset -= apply_step(opt_hscroll, view->width);
2869 redraw_view_from(view, 0);
2870 report("");
2871 return;
2872 case REQ_SCROLL_RIGHT:
2873 view->yoffset += apply_step(opt_hscroll, view->width);
2874 redraw_view(view);
2875 report("");
2876 return;
2877 case REQ_SCROLL_PAGE_DOWN:
2878 lines = view->height;
2879 case REQ_SCROLL_LINE_DOWN:
2880 if (view->offset + lines > view->lines)
2881 lines = view->lines - view->offset;
2883 if (lines == 0 || view->offset + view->height >= view->lines) {
2884 report("Cannot scroll beyond the last line");
2885 return;
2887 break;
2889 case REQ_SCROLL_PAGE_UP:
2890 lines = view->height;
2891 case REQ_SCROLL_LINE_UP:
2892 if (lines > view->offset)
2893 lines = view->offset;
2895 if (lines == 0) {
2896 report("Cannot scroll beyond the first line");
2897 return;
2900 lines = -lines;
2901 break;
2903 default:
2904 die("request %d not handled in switch", request);
2907 do_scroll_view(view, lines);
2910 /* Cursor moving */
2911 static void
2912 move_view(struct view *view, enum request request)
2914 int scroll_steps = 0;
2915 int steps;
2917 switch (request) {
2918 case REQ_MOVE_FIRST_LINE:
2919 steps = -view->lineno;
2920 break;
2922 case REQ_MOVE_LAST_LINE:
2923 steps = view->lines - view->lineno - 1;
2924 break;
2926 case REQ_MOVE_PAGE_UP:
2927 steps = view->height > view->lineno
2928 ? -view->lineno : -view->height;
2929 break;
2931 case REQ_MOVE_PAGE_DOWN:
2932 steps = view->lineno + view->height >= view->lines
2933 ? view->lines - view->lineno - 1 : view->height;
2934 break;
2936 case REQ_MOVE_UP:
2937 steps = -1;
2938 break;
2940 case REQ_MOVE_DOWN:
2941 steps = 1;
2942 break;
2944 default:
2945 die("request %d not handled in switch", request);
2948 if (steps <= 0 && view->lineno == 0) {
2949 report("Cannot move beyond the first line");
2950 return;
2952 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2953 report("Cannot move beyond the last line");
2954 return;
2957 /* Move the current line */
2958 view->lineno += steps;
2959 assert(0 <= view->lineno && view->lineno < view->lines);
2961 /* Check whether the view needs to be scrolled */
2962 if (view->lineno < view->offset ||
2963 view->lineno >= view->offset + view->height) {
2964 scroll_steps = steps;
2965 if (steps < 0 && -steps > view->offset) {
2966 scroll_steps = -view->offset;
2968 } else if (steps > 0) {
2969 if (view->lineno == view->lines - 1 &&
2970 view->lines > view->height) {
2971 scroll_steps = view->lines - view->offset - 1;
2972 if (scroll_steps >= view->height)
2973 scroll_steps -= view->height - 1;
2978 if (!view_is_displayed(view)) {
2979 view->offset += scroll_steps;
2980 assert(0 <= view->offset && view->offset < view->lines);
2981 view->ops->select(view, &view->line[view->lineno]);
2982 return;
2985 /* Repaint the old "current" line if we be scrolling */
2986 if (ABS(steps) < view->height)
2987 draw_view_line(view, view->lineno - steps - view->offset);
2989 if (scroll_steps) {
2990 do_scroll_view(view, scroll_steps);
2991 return;
2994 /* Draw the current line */
2995 draw_view_line(view, view->lineno - view->offset);
2997 wnoutrefresh(view->win);
2998 report("");
3003 * Searching
3006 static void search_view(struct view *view, enum request request);
3008 static bool
3009 grep_text(struct view *view, const char *text[])
3011 regmatch_t pmatch;
3012 size_t i;
3014 for (i = 0; text[i]; i++)
3015 if (*text[i] &&
3016 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3017 return TRUE;
3018 return FALSE;
3021 static void
3022 select_view_line(struct view *view, unsigned long lineno)
3024 unsigned long old_lineno = view->lineno;
3025 unsigned long old_offset = view->offset;
3027 if (goto_view_line(view, view->offset, lineno)) {
3028 if (view_is_displayed(view)) {
3029 if (old_offset != view->offset) {
3030 redraw_view(view);
3031 } else {
3032 draw_view_line(view, old_lineno - view->offset);
3033 draw_view_line(view, view->lineno - view->offset);
3034 wnoutrefresh(view->win);
3036 } else {
3037 view->ops->select(view, &view->line[view->lineno]);
3042 static void
3043 find_next(struct view *view, enum request request)
3045 unsigned long lineno = view->lineno;
3046 int direction;
3048 if (!*view->grep) {
3049 if (!*opt_search)
3050 report("No previous search");
3051 else
3052 search_view(view, request);
3053 return;
3056 switch (request) {
3057 case REQ_SEARCH:
3058 case REQ_FIND_NEXT:
3059 direction = 1;
3060 break;
3062 case REQ_SEARCH_BACK:
3063 case REQ_FIND_PREV:
3064 direction = -1;
3065 break;
3067 default:
3068 return;
3071 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3072 lineno += direction;
3074 /* Note, lineno is unsigned long so will wrap around in which case it
3075 * will become bigger than view->lines. */
3076 for (; lineno < view->lines; lineno += direction) {
3077 if (view->ops->grep(view, &view->line[lineno])) {
3078 select_view_line(view, lineno);
3079 report("Line %ld matches '%s'", lineno + 1, view->grep);
3080 return;
3084 report("No match found for '%s'", view->grep);
3087 static void
3088 search_view(struct view *view, enum request request)
3090 int regex_err;
3092 if (view->regex) {
3093 regfree(view->regex);
3094 *view->grep = 0;
3095 } else {
3096 view->regex = calloc(1, sizeof(*view->regex));
3097 if (!view->regex)
3098 return;
3101 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3102 if (regex_err != 0) {
3103 char buf[SIZEOF_STR] = "unknown error";
3105 regerror(regex_err, view->regex, buf, sizeof(buf));
3106 report("Search failed: %s", buf);
3107 return;
3110 string_copy(view->grep, opt_search);
3112 find_next(view, request);
3116 * Incremental updating
3119 static void
3120 reset_view(struct view *view)
3122 int i;
3124 for (i = 0; i < view->lines; i++)
3125 free(view->line[i].data);
3126 free(view->line);
3128 view->p_offset = view->offset;
3129 view->p_yoffset = view->yoffset;
3130 view->p_lineno = view->lineno;
3132 view->line = NULL;
3133 view->offset = 0;
3134 view->yoffset = 0;
3135 view->lines = 0;
3136 view->lineno = 0;
3137 view->vid[0] = 0;
3138 view->update_secs = 0;
3141 static const char *
3142 format_arg(const char *name)
3144 static struct {
3145 const char *name;
3146 size_t namelen;
3147 const char *value;
3148 const char *value_if_empty;
3149 } vars[] = {
3150 #define FORMAT_VAR(name, value, value_if_empty) \
3151 { name, STRING_SIZE(name), value, value_if_empty }
3152 FORMAT_VAR("%(directory)", opt_path, ""),
3153 FORMAT_VAR("%(file)", opt_file, ""),
3154 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3155 FORMAT_VAR("%(head)", ref_head, ""),
3156 FORMAT_VAR("%(commit)", ref_commit, ""),
3157 FORMAT_VAR("%(blob)", ref_blob, ""),
3158 FORMAT_VAR("%(branch)", ref_branch, ""),
3160 int i;
3162 for (i = 0; i < ARRAY_SIZE(vars); i++)
3163 if (!strncmp(name, vars[i].name, vars[i].namelen))
3164 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3166 report("Unknown replacement: `%s`", name);
3167 return NULL;
3170 static bool
3171 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3173 char buf[SIZEOF_STR];
3174 int argc;
3176 argv_free(*dst_argv);
3178 for (argc = 0; src_argv[argc]; argc++) {
3179 const char *arg = src_argv[argc];
3180 size_t bufpos = 0;
3182 if (!strcmp(arg, "%(file-args)")) {
3183 if (!argv_append_array(dst_argv, opt_file_args))
3184 break;
3185 continue;
3187 } else if (!strcmp(arg, "%(diff-args)")) {
3188 if (!argv_append_array(dst_argv, opt_diff_args))
3189 break;
3190 continue;
3192 } else if (!strcmp(arg, "%(rev-args)")) {
3193 if (!argv_append_array(dst_argv, opt_rev_args))
3194 break;
3195 continue;
3198 while (arg) {
3199 char *next = strstr(arg, "%(");
3200 int len = next - arg;
3201 const char *value;
3203 if (!next || !replace) {
3204 len = strlen(arg);
3205 value = "";
3207 } else {
3208 value = format_arg(next);
3210 if (!value) {
3211 return FALSE;
3215 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3216 return FALSE;
3218 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3221 if (!argv_append(dst_argv, buf))
3222 break;
3225 return src_argv[argc] == NULL;
3228 static bool
3229 restore_view_position(struct view *view)
3231 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3232 return FALSE;
3234 /* Changing the view position cancels the restoring. */
3235 /* FIXME: Changing back to the first line is not detected. */
3236 if (view->offset != 0 || view->lineno != 0) {
3237 view->p_restore = FALSE;
3238 return FALSE;
3241 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3242 view_is_displayed(view))
3243 werase(view->win);
3245 view->yoffset = view->p_yoffset;
3246 view->p_restore = FALSE;
3248 return TRUE;
3251 static void
3252 end_update(struct view *view, bool force)
3254 if (!view->pipe)
3255 return;
3256 while (!view->ops->read(view, NULL))
3257 if (!force)
3258 return;
3259 if (force)
3260 io_kill(view->pipe);
3261 io_done(view->pipe);
3262 view->pipe = NULL;
3265 static void
3266 setup_update(struct view *view, const char *vid)
3268 reset_view(view);
3269 string_copy_rev(view->vid, vid);
3270 view->pipe = &view->io;
3271 view->start_time = time(NULL);
3274 static bool
3275 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3277 view->dir = dir;
3278 return format_argv(&view->argv, argv, replace);
3281 static bool
3282 prepare_update(struct view *view, const char *argv[], const char *dir)
3284 if (view->pipe)
3285 end_update(view, TRUE);
3286 return prepare_io(view, dir, argv, FALSE);
3289 static bool
3290 start_update(struct view *view, const char **argv, const char *dir)
3292 if (view->pipe)
3293 io_done(view->pipe);
3294 return prepare_io(view, dir, argv, FALSE) &&
3295 io_run(&view->io, IO_RD, dir, view->argv);
3298 static bool
3299 prepare_update_file(struct view *view, const char *name)
3301 if (view->pipe)
3302 end_update(view, TRUE);
3303 argv_free(view->argv);
3304 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3307 static bool
3308 begin_update(struct view *view, bool refresh)
3310 if (view->pipe)
3311 end_update(view, TRUE);
3313 if (!refresh) {
3314 if (view->ops->prepare) {
3315 if (!view->ops->prepare(view))
3316 return FALSE;
3317 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3318 return FALSE;
3321 /* Put the current ref_* value to the view title ref
3322 * member. This is needed by the blob view. Most other
3323 * views sets it automatically after loading because the
3324 * first line is a commit line. */
3325 string_copy_rev(view->ref, view->id);
3328 if (view->argv && view->argv[0] &&
3329 !io_run(&view->io, IO_RD, view->dir, view->argv))
3330 return FALSE;
3332 setup_update(view, view->id);
3334 return TRUE;
3337 static bool
3338 update_view(struct view *view)
3340 char out_buffer[BUFSIZ * 2];
3341 char *line;
3342 /* Clear the view and redraw everything since the tree sorting
3343 * might have rearranged things. */
3344 bool redraw = view->lines == 0;
3345 bool can_read = TRUE;
3347 if (!view->pipe)
3348 return TRUE;
3350 if (!io_can_read(view->pipe)) {
3351 if (view->lines == 0 && view_is_displayed(view)) {
3352 time_t secs = time(NULL) - view->start_time;
3354 if (secs > 1 && secs > view->update_secs) {
3355 if (view->update_secs == 0)
3356 redraw_view(view);
3357 update_view_title(view);
3358 view->update_secs = secs;
3361 return TRUE;
3364 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3365 if (opt_iconv_in != ICONV_NONE) {
3366 ICONV_CONST char *inbuf = line;
3367 size_t inlen = strlen(line) + 1;
3369 char *outbuf = out_buffer;
3370 size_t outlen = sizeof(out_buffer);
3372 size_t ret;
3374 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3375 if (ret != (size_t) -1)
3376 line = out_buffer;
3379 if (!view->ops->read(view, line)) {
3380 report("Allocation failure");
3381 end_update(view, TRUE);
3382 return FALSE;
3387 unsigned long lines = view->lines;
3388 int digits;
3390 for (digits = 0; lines; digits++)
3391 lines /= 10;
3393 /* Keep the displayed view in sync with line number scaling. */
3394 if (digits != view->digits) {
3395 view->digits = digits;
3396 if (opt_line_number || view->type == VIEW_BLAME)
3397 redraw = TRUE;
3401 if (io_error(view->pipe)) {
3402 report("Failed to read: %s", io_strerror(view->pipe));
3403 end_update(view, TRUE);
3405 } else if (io_eof(view->pipe)) {
3406 if (view_is_displayed(view))
3407 report("");
3408 end_update(view, FALSE);
3411 if (restore_view_position(view))
3412 redraw = TRUE;
3414 if (!view_is_displayed(view))
3415 return TRUE;
3417 if (redraw)
3418 redraw_view_from(view, 0);
3419 else
3420 redraw_view_dirty(view);
3422 /* Update the title _after_ the redraw so that if the redraw picks up a
3423 * commit reference in view->ref it'll be available here. */
3424 update_view_title(view);
3425 return TRUE;
3428 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3430 static struct line *
3431 add_line_data(struct view *view, void *data, enum line_type type)
3433 struct line *line;
3435 if (!realloc_lines(&view->line, view->lines, 1))
3436 return NULL;
3438 line = &view->line[view->lines++];
3439 memset(line, 0, sizeof(*line));
3440 line->type = type;
3441 line->data = data;
3442 line->dirty = 1;
3444 return line;
3447 static struct line *
3448 add_line_text(struct view *view, const char *text, enum line_type type)
3450 char *data = text ? strdup(text) : NULL;
3452 return data ? add_line_data(view, data, type) : NULL;
3455 static struct line *
3456 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3458 char buf[SIZEOF_STR];
3459 va_list args;
3461 va_start(args, fmt);
3462 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3463 buf[0] = 0;
3464 va_end(args);
3466 return buf[0] ? add_line_text(view, buf, type) : NULL;
3470 * View opening
3473 enum open_flags {
3474 OPEN_DEFAULT = 0, /* Use default view switching. */
3475 OPEN_SPLIT = 1, /* Split current view. */
3476 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3477 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3478 OPEN_PREPARED = 32, /* Open already prepared command. */
3481 static void
3482 open_view(struct view *prev, enum request request, enum open_flags flags)
3484 bool split = !!(flags & OPEN_SPLIT);
3485 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3486 bool nomaximize = !!(flags & OPEN_REFRESH);
3487 struct view *view = VIEW(request);
3488 int nviews = displayed_views();
3489 struct view *base_view = display[0];
3491 if (view == prev && nviews == 1 && !reload) {
3492 report("Already in %s view", view->name);
3493 return;
3496 if (view->git_dir && !opt_git_dir[0]) {
3497 report("The %s view is disabled in pager view", view->name);
3498 return;
3501 if (split) {
3502 display[1] = view;
3503 current_view = 1;
3504 view->parent = prev;
3505 } else if (!nomaximize) {
3506 /* Maximize the current view. */
3507 memset(display, 0, sizeof(display));
3508 current_view = 0;
3509 display[current_view] = view;
3512 /* No prev signals that this is the first loaded view. */
3513 if (prev && view != prev) {
3514 view->prev = prev;
3517 /* Resize the view when switching between split- and full-screen,
3518 * or when switching between two different full-screen views. */
3519 if (nviews != displayed_views() ||
3520 (nviews == 1 && base_view != display[0]))
3521 resize_display();
3523 if (view->ops->open) {
3524 if (view->pipe)
3525 end_update(view, TRUE);
3526 if (!view->ops->open(view)) {
3527 report("Failed to load %s view", view->name);
3528 return;
3530 restore_view_position(view);
3532 } else if ((reload || strcmp(view->vid, view->id)) &&
3533 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3534 report("Failed to load %s view", view->name);
3535 return;
3538 if (split && prev->lineno - prev->offset >= prev->height) {
3539 /* Take the title line into account. */
3540 int lines = prev->lineno - prev->offset - prev->height + 1;
3542 /* Scroll the view that was split if the current line is
3543 * outside the new limited view. */
3544 do_scroll_view(prev, lines);
3547 if (prev && view != prev && split && view_is_displayed(prev)) {
3548 /* "Blur" the previous view. */
3549 update_view_title(prev);
3552 if (view->pipe && view->lines == 0) {
3553 /* Clear the old view and let the incremental updating refill
3554 * the screen. */
3555 werase(view->win);
3556 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3557 report("");
3558 } else if (view_is_displayed(view)) {
3559 redraw_view(view);
3560 report("");
3564 static void
3565 open_external_viewer(const char *argv[], const char *dir)
3567 def_prog_mode(); /* save current tty modes */
3568 endwin(); /* restore original tty modes */
3569 io_run_fg(argv, dir);
3570 fprintf(stderr, "Press Enter to continue");
3571 getc(opt_tty);
3572 reset_prog_mode();
3573 redraw_display(TRUE);
3576 static void
3577 open_mergetool(const char *file)
3579 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3581 open_external_viewer(mergetool_argv, opt_cdup);
3584 static void
3585 open_editor(const char *file)
3587 const char *editor_argv[] = { "vi", file, NULL };
3588 const char *editor;
3590 editor = getenv("GIT_EDITOR");
3591 if (!editor && *opt_editor)
3592 editor = opt_editor;
3593 if (!editor)
3594 editor = getenv("VISUAL");
3595 if (!editor)
3596 editor = getenv("EDITOR");
3597 if (!editor)
3598 editor = "vi";
3600 editor_argv[0] = editor;
3601 open_external_viewer(editor_argv, opt_cdup);
3604 static void
3605 open_run_request(enum request request)
3607 struct run_request *req = get_run_request(request);
3608 const char **argv = NULL;
3610 if (!req) {
3611 report("Unknown run request");
3612 return;
3615 if (format_argv(&argv, req->argv, TRUE))
3616 open_external_viewer(argv, NULL);
3617 if (argv)
3618 argv_free(argv);
3619 free(argv);
3623 * User request switch noodle
3626 static int
3627 view_driver(struct view *view, enum request request)
3629 int i;
3631 if (request == REQ_NONE)
3632 return TRUE;
3634 if (request > REQ_NONE) {
3635 open_run_request(request);
3636 view_request(view, REQ_REFRESH);
3637 return TRUE;
3640 request = view_request(view, request);
3641 if (request == REQ_NONE)
3642 return TRUE;
3644 switch (request) {
3645 case REQ_MOVE_UP:
3646 case REQ_MOVE_DOWN:
3647 case REQ_MOVE_PAGE_UP:
3648 case REQ_MOVE_PAGE_DOWN:
3649 case REQ_MOVE_FIRST_LINE:
3650 case REQ_MOVE_LAST_LINE:
3651 move_view(view, request);
3652 break;
3654 case REQ_SCROLL_LEFT:
3655 case REQ_SCROLL_RIGHT:
3656 case REQ_SCROLL_LINE_DOWN:
3657 case REQ_SCROLL_LINE_UP:
3658 case REQ_SCROLL_PAGE_DOWN:
3659 case REQ_SCROLL_PAGE_UP:
3660 scroll_view(view, request);
3661 break;
3663 case REQ_VIEW_BLAME:
3664 if (!opt_file[0]) {
3665 report("No file chosen, press %s to open tree view",
3666 get_key(view->keymap, REQ_VIEW_TREE));
3667 break;
3669 open_view(view, request, OPEN_DEFAULT);
3670 break;
3672 case REQ_VIEW_BLOB:
3673 if (!ref_blob[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_PAGER:
3682 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3683 report("No pager content, press %s to run command from prompt",
3684 get_key(view->keymap, REQ_PROMPT));
3685 break;
3687 open_view(view, request, OPEN_DEFAULT);
3688 break;
3690 case REQ_VIEW_STAGE:
3691 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3692 report("No stage content, press %s to open the status view and choose file",
3693 get_key(view->keymap, REQ_VIEW_STATUS));
3694 break;
3696 open_view(view, request, OPEN_DEFAULT);
3697 break;
3699 case REQ_VIEW_STATUS:
3700 if (opt_is_inside_work_tree == FALSE) {
3701 report("The status view requires a working tree");
3702 break;
3704 open_view(view, request, OPEN_DEFAULT);
3705 break;
3707 case REQ_VIEW_MAIN:
3708 case REQ_VIEW_DIFF:
3709 case REQ_VIEW_LOG:
3710 case REQ_VIEW_TREE:
3711 case REQ_VIEW_HELP:
3712 case REQ_VIEW_BRANCH:
3713 open_view(view, request, OPEN_DEFAULT);
3714 break;
3716 case REQ_NEXT:
3717 case REQ_PREVIOUS:
3718 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3720 if (view->parent) {
3721 int line;
3723 view = view->parent;
3724 line = view->lineno;
3725 move_view(view, request);
3726 if (view_is_displayed(view))
3727 update_view_title(view);
3728 if (line != view->lineno)
3729 view_request(view, REQ_ENTER);
3730 } else {
3731 move_view(view, request);
3733 break;
3735 case REQ_VIEW_NEXT:
3737 int nviews = displayed_views();
3738 int next_view = (current_view + 1) % nviews;
3740 if (next_view == current_view) {
3741 report("Only one view is displayed");
3742 break;
3745 current_view = next_view;
3746 /* Blur out the title of the previous view. */
3747 update_view_title(view);
3748 report("");
3749 break;
3751 case REQ_REFRESH:
3752 report("Refreshing is not yet supported for the %s view", view->name);
3753 break;
3755 case REQ_MAXIMIZE:
3756 if (displayed_views() == 2)
3757 maximize_view(view);
3758 break;
3760 case REQ_OPTIONS:
3761 open_option_menu();
3762 break;
3764 case REQ_TOGGLE_LINENO:
3765 toggle_view_option(&opt_line_number, "line numbers");
3766 break;
3768 case REQ_TOGGLE_DATE:
3769 toggle_date();
3770 break;
3772 case REQ_TOGGLE_AUTHOR:
3773 toggle_author();
3774 break;
3776 case REQ_TOGGLE_REV_GRAPH:
3777 toggle_view_option(&opt_rev_graph, "revision graph display");
3778 break;
3780 case REQ_TOGGLE_REFS:
3781 toggle_view_option(&opt_show_refs, "reference display");
3782 break;
3784 case REQ_TOGGLE_SORT_FIELD:
3785 case REQ_TOGGLE_SORT_ORDER:
3786 report("Sorting is not yet supported for the %s view", view->name);
3787 break;
3789 case REQ_SEARCH:
3790 case REQ_SEARCH_BACK:
3791 search_view(view, request);
3792 break;
3794 case REQ_FIND_NEXT:
3795 case REQ_FIND_PREV:
3796 find_next(view, request);
3797 break;
3799 case REQ_STOP_LOADING:
3800 foreach_view(view, i) {
3801 if (view->pipe)
3802 report("Stopped loading the %s view", view->name),
3803 end_update(view, TRUE);
3805 break;
3807 case REQ_SHOW_VERSION:
3808 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3809 return TRUE;
3811 case REQ_SCREEN_REDRAW:
3812 redraw_display(TRUE);
3813 break;
3815 case REQ_EDIT:
3816 report("Nothing to edit");
3817 break;
3819 case REQ_ENTER:
3820 report("Nothing to enter");
3821 break;
3823 case REQ_VIEW_CLOSE:
3824 /* XXX: Mark closed views by letting view->prev point to the
3825 * view itself. Parents to closed view should never be
3826 * followed. */
3827 if (view->prev && view->prev != view) {
3828 maximize_view(view->prev);
3829 view->prev = view;
3830 break;
3832 /* Fall-through */
3833 case REQ_QUIT:
3834 return FALSE;
3836 default:
3837 report("Unknown key, press %s for help",
3838 get_key(view->keymap, REQ_VIEW_HELP));
3839 return TRUE;
3842 return TRUE;
3847 * View backend utilities
3850 enum sort_field {
3851 ORDERBY_NAME,
3852 ORDERBY_DATE,
3853 ORDERBY_AUTHOR,
3856 struct sort_state {
3857 const enum sort_field *fields;
3858 size_t size, current;
3859 bool reverse;
3862 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3863 #define get_sort_field(state) ((state).fields[(state).current])
3864 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3866 static void
3867 sort_view(struct view *view, enum request request, struct sort_state *state,
3868 int (*compare)(const void *, const void *))
3870 switch (request) {
3871 case REQ_TOGGLE_SORT_FIELD:
3872 state->current = (state->current + 1) % state->size;
3873 break;
3875 case REQ_TOGGLE_SORT_ORDER:
3876 state->reverse = !state->reverse;
3877 break;
3878 default:
3879 die("Not a sort request");
3882 qsort(view->line, view->lines, sizeof(*view->line), compare);
3883 redraw_view(view);
3886 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3888 /* Small author cache to reduce memory consumption. It uses binary
3889 * search to lookup or find place to position new entries. No entries
3890 * are ever freed. */
3891 static const char *
3892 get_author(const char *name)
3894 static const char **authors;
3895 static size_t authors_size;
3896 int from = 0, to = authors_size - 1;
3898 while (from <= to) {
3899 size_t pos = (to + from) / 2;
3900 int cmp = strcmp(name, authors[pos]);
3902 if (!cmp)
3903 return authors[pos];
3905 if (cmp < 0)
3906 to = pos - 1;
3907 else
3908 from = pos + 1;
3911 if (!realloc_authors(&authors, authors_size, 1))
3912 return NULL;
3913 name = strdup(name);
3914 if (!name)
3915 return NULL;
3917 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3918 authors[from] = name;
3919 authors_size++;
3921 return name;
3924 static void
3925 parse_timesec(struct time *time, const char *sec)
3927 time->sec = (time_t) atol(sec);
3930 static void
3931 parse_timezone(struct time *time, const char *zone)
3933 long tz;
3935 tz = ('0' - zone[1]) * 60 * 60 * 10;
3936 tz += ('0' - zone[2]) * 60 * 60;
3937 tz += ('0' - zone[3]) * 60 * 10;
3938 tz += ('0' - zone[4]) * 60;
3940 if (zone[0] == '-')
3941 tz = -tz;
3943 time->tz = tz;
3944 time->sec -= tz;
3947 /* Parse author lines where the name may be empty:
3948 * author <email@address.tld> 1138474660 +0100
3950 static void
3951 parse_author_line(char *ident, const char **author, struct time *time)
3953 char *nameend = strchr(ident, '<');
3954 char *emailend = strchr(ident, '>');
3956 if (nameend && emailend)
3957 *nameend = *emailend = 0;
3958 ident = chomp_string(ident);
3959 if (!*ident) {
3960 if (nameend)
3961 ident = chomp_string(nameend + 1);
3962 if (!*ident)
3963 ident = "Unknown";
3966 *author = get_author(ident);
3968 /* Parse epoch and timezone */
3969 if (emailend && emailend[1] == ' ') {
3970 char *secs = emailend + 2;
3971 char *zone = strchr(secs, ' ');
3973 parse_timesec(time, secs);
3975 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3976 parse_timezone(time, zone + 1);
3981 * Pager backend
3984 static bool
3985 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3987 char text[SIZEOF_STR];
3989 if (opt_line_number && draw_lineno(view, lineno))
3990 return TRUE;
3992 string_expand(text, sizeof(text), line->data, opt_tab_size);
3993 draw_text(view, line->type, text, TRUE);
3994 return TRUE;
3997 static bool
3998 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4000 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4001 char ref[SIZEOF_STR];
4003 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4004 return TRUE;
4006 /* This is the only fatal call, since it can "corrupt" the buffer. */
4007 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4008 return FALSE;
4010 return TRUE;
4013 static void
4014 add_pager_refs(struct view *view, struct line *line)
4016 char buf[SIZEOF_STR];
4017 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4018 struct ref_list *list;
4019 size_t bufpos = 0, i;
4020 const char *sep = "Refs: ";
4021 bool is_tag = FALSE;
4023 assert(line->type == LINE_COMMIT);
4025 list = get_ref_list(commit_id);
4026 if (!list) {
4027 if (view->type == VIEW_DIFF)
4028 goto try_add_describe_ref;
4029 return;
4032 for (i = 0; i < list->size; i++) {
4033 struct ref *ref = list->refs[i];
4034 const char *fmt = ref->tag ? "%s[%s]" :
4035 ref->remote ? "%s<%s>" : "%s%s";
4037 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4038 return;
4039 sep = ", ";
4040 if (ref->tag)
4041 is_tag = TRUE;
4044 if (!is_tag && view->type == VIEW_DIFF) {
4045 try_add_describe_ref:
4046 /* Add <tag>-g<commit_id> "fake" reference. */
4047 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4048 return;
4051 if (bufpos == 0)
4052 return;
4054 add_line_text(view, buf, LINE_PP_REFS);
4057 static bool
4058 pager_read(struct view *view, char *data)
4060 struct line *line;
4062 if (!data)
4063 return TRUE;
4065 line = add_line_text(view, data, get_line_type(data));
4066 if (!line)
4067 return FALSE;
4069 if (line->type == LINE_COMMIT &&
4070 (view->type == VIEW_DIFF ||
4071 view->type == VIEW_LOG))
4072 add_pager_refs(view, line);
4074 return TRUE;
4077 static enum request
4078 pager_request(struct view *view, enum request request, struct line *line)
4080 int split = 0;
4082 if (request != REQ_ENTER)
4083 return request;
4085 if (line->type == LINE_COMMIT &&
4086 (view->type == VIEW_LOG ||
4087 view->type == VIEW_PAGER)) {
4088 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4089 split = 1;
4092 /* Always scroll the view even if it was split. That way
4093 * you can use Enter to scroll through the log view and
4094 * split open each commit diff. */
4095 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4097 /* FIXME: A minor workaround. Scrolling the view will call report("")
4098 * but if we are scrolling a non-current view this won't properly
4099 * update the view title. */
4100 if (split)
4101 update_view_title(view);
4103 return REQ_NONE;
4106 static bool
4107 pager_grep(struct view *view, struct line *line)
4109 const char *text[] = { line->data, NULL };
4111 return grep_text(view, text);
4114 static void
4115 pager_select(struct view *view, struct line *line)
4117 if (line->type == LINE_COMMIT) {
4118 char *text = (char *)line->data + STRING_SIZE("commit ");
4120 if (view->type != VIEW_PAGER)
4121 string_copy_rev(view->ref, text);
4122 string_copy_rev(ref_commit, text);
4126 static struct view_ops pager_ops = {
4127 "line",
4128 NULL,
4129 NULL,
4130 pager_read,
4131 pager_draw,
4132 pager_request,
4133 pager_grep,
4134 pager_select,
4137 static const char *log_argv[SIZEOF_ARG] = {
4138 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4141 static enum request
4142 log_request(struct view *view, enum request request, struct line *line)
4144 switch (request) {
4145 case REQ_REFRESH:
4146 load_refs();
4147 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4148 return REQ_NONE;
4149 default:
4150 return pager_request(view, request, line);
4154 static struct view_ops log_ops = {
4155 "line",
4156 log_argv,
4157 NULL,
4158 pager_read,
4159 pager_draw,
4160 log_request,
4161 pager_grep,
4162 pager_select,
4165 static const char *diff_argv[SIZEOF_ARG] = {
4166 "git", "show", "--pretty=fuller", "--no-color", "--root",
4167 "--patch-with-stat", "--find-copies-harder", "-C",
4168 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4171 static struct view_ops diff_ops = {
4172 "line",
4173 diff_argv,
4174 NULL,
4175 pager_read,
4176 pager_draw,
4177 pager_request,
4178 pager_grep,
4179 pager_select,
4183 * Help backend
4186 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4188 static bool
4189 help_open_keymap_title(struct view *view, enum keymap keymap)
4191 struct line *line;
4193 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4194 help_keymap_hidden[keymap] ? '+' : '-',
4195 enum_name(keymap_table[keymap]));
4196 if (line)
4197 line->other = keymap;
4199 return help_keymap_hidden[keymap];
4202 static void
4203 help_open_keymap(struct view *view, enum keymap keymap)
4205 const char *group = NULL;
4206 char buf[SIZEOF_STR];
4207 size_t bufpos;
4208 bool add_title = TRUE;
4209 int i;
4211 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4212 const char *key = NULL;
4214 if (req_info[i].request == REQ_NONE)
4215 continue;
4217 if (!req_info[i].request) {
4218 group = req_info[i].help;
4219 continue;
4222 key = get_keys(keymap, req_info[i].request, TRUE);
4223 if (!key || !*key)
4224 continue;
4226 if (add_title && help_open_keymap_title(view, keymap))
4227 return;
4228 add_title = FALSE;
4230 if (group) {
4231 add_line_text(view, group, LINE_HELP_GROUP);
4232 group = NULL;
4235 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4236 enum_name(req_info[i]), req_info[i].help);
4239 group = "External commands:";
4241 for (i = 0; i < run_requests; i++) {
4242 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4243 const char *key;
4244 int argc;
4246 if (!req || req->keymap != keymap)
4247 continue;
4249 key = get_key_name(req->key);
4250 if (!*key)
4251 key = "(no key defined)";
4253 if (add_title && help_open_keymap_title(view, keymap))
4254 return;
4255 if (group) {
4256 add_line_text(view, group, LINE_HELP_GROUP);
4257 group = NULL;
4260 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4261 if (!string_format_from(buf, &bufpos, "%s%s",
4262 argc ? " " : "", req->argv[argc]))
4263 return;
4265 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4269 static bool
4270 help_open(struct view *view)
4272 enum keymap keymap;
4274 reset_view(view);
4275 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4276 add_line_text(view, "", LINE_DEFAULT);
4278 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4279 help_open_keymap(view, keymap);
4281 return TRUE;
4284 static enum request
4285 help_request(struct view *view, enum request request, struct line *line)
4287 switch (request) {
4288 case REQ_ENTER:
4289 if (line->type == LINE_HELP_KEYMAP) {
4290 help_keymap_hidden[line->other] =
4291 !help_keymap_hidden[line->other];
4292 view->p_restore = TRUE;
4293 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4296 return REQ_NONE;
4297 default:
4298 return pager_request(view, request, line);
4302 static struct view_ops help_ops = {
4303 "line",
4304 NULL,
4305 help_open,
4306 NULL,
4307 pager_draw,
4308 help_request,
4309 pager_grep,
4310 pager_select,
4315 * Tree backend
4318 struct tree_stack_entry {
4319 struct tree_stack_entry *prev; /* Entry below this in the stack */
4320 unsigned long lineno; /* Line number to restore */
4321 char *name; /* Position of name in opt_path */
4324 /* The top of the path stack. */
4325 static struct tree_stack_entry *tree_stack = NULL;
4326 unsigned long tree_lineno = 0;
4328 static void
4329 pop_tree_stack_entry(void)
4331 struct tree_stack_entry *entry = tree_stack;
4333 tree_lineno = entry->lineno;
4334 entry->name[0] = 0;
4335 tree_stack = entry->prev;
4336 free(entry);
4339 static void
4340 push_tree_stack_entry(const char *name, unsigned long lineno)
4342 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4343 size_t pathlen = strlen(opt_path);
4345 if (!entry)
4346 return;
4348 entry->prev = tree_stack;
4349 entry->name = opt_path + pathlen;
4350 tree_stack = entry;
4352 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4353 pop_tree_stack_entry();
4354 return;
4357 /* Move the current line to the first tree entry. */
4358 tree_lineno = 1;
4359 entry->lineno = lineno;
4362 /* Parse output from git-ls-tree(1):
4364 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4367 #define SIZEOF_TREE_ATTR \
4368 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4370 #define SIZEOF_TREE_MODE \
4371 STRING_SIZE("100644 ")
4373 #define TREE_ID_OFFSET \
4374 STRING_SIZE("100644 blob ")
4376 struct tree_entry {
4377 char id[SIZEOF_REV];
4378 mode_t mode;
4379 struct time time; /* Date from the author ident. */
4380 const char *author; /* Author of the commit. */
4381 char name[1];
4384 static const char *
4385 tree_path(const struct line *line)
4387 return ((struct tree_entry *) line->data)->name;
4390 static int
4391 tree_compare_entry(const struct line *line1, const struct line *line2)
4393 if (line1->type != line2->type)
4394 return line1->type == LINE_TREE_DIR ? -1 : 1;
4395 return strcmp(tree_path(line1), tree_path(line2));
4398 static const enum sort_field tree_sort_fields[] = {
4399 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4401 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4403 static int
4404 tree_compare(const void *l1, const void *l2)
4406 const struct line *line1 = (const struct line *) l1;
4407 const struct line *line2 = (const struct line *) l2;
4408 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4409 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4411 if (line1->type == LINE_TREE_HEAD)
4412 return -1;
4413 if (line2->type == LINE_TREE_HEAD)
4414 return 1;
4416 switch (get_sort_field(tree_sort_state)) {
4417 case ORDERBY_DATE:
4418 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4420 case ORDERBY_AUTHOR:
4421 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4423 case ORDERBY_NAME:
4424 default:
4425 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4430 static struct line *
4431 tree_entry(struct view *view, enum line_type type, const char *path,
4432 const char *mode, const char *id)
4434 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4435 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4437 if (!entry || !line) {
4438 free(entry);
4439 return NULL;
4442 strncpy(entry->name, path, strlen(path));
4443 if (mode)
4444 entry->mode = strtoul(mode, NULL, 8);
4445 if (id)
4446 string_copy_rev(entry->id, id);
4448 return line;
4451 static bool
4452 tree_read_date(struct view *view, char *text, bool *read_date)
4454 static const char *author_name;
4455 static struct time author_time;
4457 if (!text && *read_date) {
4458 *read_date = FALSE;
4459 return TRUE;
4461 } else if (!text) {
4462 char *path = *opt_path ? opt_path : ".";
4463 /* Find next entry to process */
4464 const char *log_file[] = {
4465 "git", "log", "--no-color", "--pretty=raw",
4466 "--cc", "--raw", view->id, "--", path, NULL
4469 if (!view->lines) {
4470 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4471 report("Tree is empty");
4472 return TRUE;
4475 if (!start_update(view, log_file, opt_cdup)) {
4476 report("Failed to load tree data");
4477 return TRUE;
4480 *read_date = TRUE;
4481 return FALSE;
4483 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4484 parse_author_line(text + STRING_SIZE("author "),
4485 &author_name, &author_time);
4487 } else if (*text == ':') {
4488 char *pos;
4489 size_t annotated = 1;
4490 size_t i;
4492 pos = strchr(text, '\t');
4493 if (!pos)
4494 return TRUE;
4495 text = pos + 1;
4496 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4497 text += strlen(opt_path);
4498 pos = strchr(text, '/');
4499 if (pos)
4500 *pos = 0;
4502 for (i = 1; i < view->lines; i++) {
4503 struct line *line = &view->line[i];
4504 struct tree_entry *entry = line->data;
4506 annotated += !!entry->author;
4507 if (entry->author || strcmp(entry->name, text))
4508 continue;
4510 entry->author = author_name;
4511 entry->time = author_time;
4512 line->dirty = 1;
4513 break;
4516 if (annotated == view->lines)
4517 io_kill(view->pipe);
4519 return TRUE;
4522 static bool
4523 tree_read(struct view *view, char *text)
4525 static bool read_date = FALSE;
4526 struct tree_entry *data;
4527 struct line *entry, *line;
4528 enum line_type type;
4529 size_t textlen = text ? strlen(text) : 0;
4530 char *path = text + SIZEOF_TREE_ATTR;
4532 if (read_date || !text)
4533 return tree_read_date(view, text, &read_date);
4535 if (textlen <= SIZEOF_TREE_ATTR)
4536 return FALSE;
4537 if (view->lines == 0 &&
4538 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4539 return FALSE;
4541 /* Strip the path part ... */
4542 if (*opt_path) {
4543 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4544 size_t striplen = strlen(opt_path);
4546 if (pathlen > striplen)
4547 memmove(path, path + striplen,
4548 pathlen - striplen + 1);
4550 /* Insert "link" to parent directory. */
4551 if (view->lines == 1 &&
4552 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4553 return FALSE;
4556 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4557 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4558 if (!entry)
4559 return FALSE;
4560 data = entry->data;
4562 /* Skip "Directory ..." and ".." line. */
4563 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4564 if (tree_compare_entry(line, entry) <= 0)
4565 continue;
4567 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4569 line->data = data;
4570 line->type = type;
4571 for (; line <= entry; line++)
4572 line->dirty = line->cleareol = 1;
4573 return TRUE;
4576 if (tree_lineno > view->lineno) {
4577 view->lineno = tree_lineno;
4578 tree_lineno = 0;
4581 return TRUE;
4584 static bool
4585 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4587 struct tree_entry *entry = line->data;
4589 if (line->type == LINE_TREE_HEAD) {
4590 if (draw_text(view, line->type, "Directory path /", TRUE))
4591 return TRUE;
4592 } else {
4593 if (draw_mode(view, entry->mode))
4594 return TRUE;
4596 if (opt_author && draw_author(view, entry->author))
4597 return TRUE;
4599 if (opt_date && draw_date(view, &entry->time))
4600 return TRUE;
4602 if (draw_text(view, line->type, entry->name, TRUE))
4603 return TRUE;
4604 return TRUE;
4607 static void
4608 open_blob_editor(const char *id)
4610 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4611 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4612 int fd = mkstemp(file);
4614 if (fd == -1)
4615 report("Failed to create temporary file");
4616 else if (!io_run_append(blob_argv, fd))
4617 report("Failed to save blob data to file");
4618 else
4619 open_editor(file);
4620 if (fd != -1)
4621 unlink(file);
4624 static enum request
4625 tree_request(struct view *view, enum request request, struct line *line)
4627 enum open_flags flags;
4628 struct tree_entry *entry = line->data;
4630 switch (request) {
4631 case REQ_VIEW_BLAME:
4632 if (line->type != LINE_TREE_FILE) {
4633 report("Blame only supported for files");
4634 return REQ_NONE;
4637 string_copy(opt_ref, view->vid);
4638 return request;
4640 case REQ_EDIT:
4641 if (line->type != LINE_TREE_FILE) {
4642 report("Edit only supported for files");
4643 } else if (!is_head_commit(view->vid)) {
4644 open_blob_editor(entry->id);
4645 } else {
4646 open_editor(opt_file);
4648 return REQ_NONE;
4650 case REQ_TOGGLE_SORT_FIELD:
4651 case REQ_TOGGLE_SORT_ORDER:
4652 sort_view(view, request, &tree_sort_state, tree_compare);
4653 return REQ_NONE;
4655 case REQ_PARENT:
4656 if (!*opt_path) {
4657 /* quit view if at top of tree */
4658 return REQ_VIEW_CLOSE;
4660 /* fake 'cd ..' */
4661 line = &view->line[1];
4662 break;
4664 case REQ_ENTER:
4665 break;
4667 default:
4668 return request;
4671 /* Cleanup the stack if the tree view is at a different tree. */
4672 while (!*opt_path && tree_stack)
4673 pop_tree_stack_entry();
4675 switch (line->type) {
4676 case LINE_TREE_DIR:
4677 /* Depending on whether it is a subdirectory or parent link
4678 * mangle the path buffer. */
4679 if (line == &view->line[1] && *opt_path) {
4680 pop_tree_stack_entry();
4682 } else {
4683 const char *basename = tree_path(line);
4685 push_tree_stack_entry(basename, view->lineno);
4688 /* Trees and subtrees share the same ID, so they are not not
4689 * unique like blobs. */
4690 flags = OPEN_RELOAD;
4691 request = REQ_VIEW_TREE;
4692 break;
4694 case LINE_TREE_FILE:
4695 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4696 request = REQ_VIEW_BLOB;
4697 break;
4699 default:
4700 return REQ_NONE;
4703 open_view(view, request, flags);
4704 if (request == REQ_VIEW_TREE)
4705 view->lineno = tree_lineno;
4707 return REQ_NONE;
4710 static bool
4711 tree_grep(struct view *view, struct line *line)
4713 struct tree_entry *entry = line->data;
4714 const char *text[] = {
4715 entry->name,
4716 opt_author ? entry->author : "",
4717 mkdate(&entry->time, opt_date),
4718 NULL
4721 return grep_text(view, text);
4724 static void
4725 tree_select(struct view *view, struct line *line)
4727 struct tree_entry *entry = line->data;
4729 if (line->type == LINE_TREE_FILE) {
4730 string_copy_rev(ref_blob, entry->id);
4731 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4733 } else if (line->type != LINE_TREE_DIR) {
4734 return;
4737 string_copy_rev(view->ref, entry->id);
4740 static bool
4741 tree_prepare(struct view *view)
4743 if (view->lines == 0 && opt_prefix[0]) {
4744 char *pos = opt_prefix;
4746 while (pos && *pos) {
4747 char *end = strchr(pos, '/');
4749 if (end)
4750 *end = 0;
4751 push_tree_stack_entry(pos, 0);
4752 pos = end;
4753 if (end) {
4754 *end = '/';
4755 pos++;
4759 } else if (strcmp(view->vid, view->id)) {
4760 opt_path[0] = 0;
4763 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4766 static const char *tree_argv[SIZEOF_ARG] = {
4767 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4770 static struct view_ops tree_ops = {
4771 "file",
4772 tree_argv,
4773 NULL,
4774 tree_read,
4775 tree_draw,
4776 tree_request,
4777 tree_grep,
4778 tree_select,
4779 tree_prepare,
4782 static bool
4783 blob_read(struct view *view, char *line)
4785 if (!line)
4786 return TRUE;
4787 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4790 static enum request
4791 blob_request(struct view *view, enum request request, struct line *line)
4793 switch (request) {
4794 case REQ_EDIT:
4795 open_blob_editor(view->vid);
4796 return REQ_NONE;
4797 default:
4798 return pager_request(view, request, line);
4802 static const char *blob_argv[SIZEOF_ARG] = {
4803 "git", "cat-file", "blob", "%(blob)", NULL
4806 static struct view_ops blob_ops = {
4807 "line",
4808 blob_argv,
4809 NULL,
4810 blob_read,
4811 pager_draw,
4812 blob_request,
4813 pager_grep,
4814 pager_select,
4818 * Blame backend
4820 * Loading the blame view is a two phase job:
4822 * 1. File content is read either using opt_file from the
4823 * filesystem or using git-cat-file.
4824 * 2. Then blame information is incrementally added by
4825 * reading output from git-blame.
4828 struct blame_commit {
4829 char id[SIZEOF_REV]; /* SHA1 ID. */
4830 char title[128]; /* First line of the commit message. */
4831 const char *author; /* Author of the commit. */
4832 struct time time; /* Date from the author ident. */
4833 char filename[128]; /* Name of file. */
4834 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4835 char parent_filename[128]; /* Parent/previous name of file. */
4838 struct blame {
4839 struct blame_commit *commit;
4840 unsigned long lineno;
4841 char text[1];
4844 static bool
4845 blame_open(struct view *view)
4847 char path[SIZEOF_STR];
4848 size_t i;
4850 if (!view->prev && *opt_prefix) {
4851 string_copy(path, opt_file);
4852 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4853 return FALSE;
4856 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4857 const char *blame_cat_file_argv[] = {
4858 "git", "cat-file", "blob", path, NULL
4861 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4862 !start_update(view, blame_cat_file_argv, opt_cdup))
4863 return FALSE;
4866 /* First pass: remove multiple references to the same commit. */
4867 for (i = 0; i < view->lines; i++) {
4868 struct blame *blame = view->line[i].data;
4870 if (blame->commit && blame->commit->id[0])
4871 blame->commit->id[0] = 0;
4872 else
4873 blame->commit = NULL;
4876 /* Second pass: free existing references. */
4877 for (i = 0; i < view->lines; i++) {
4878 struct blame *blame = view->line[i].data;
4880 if (blame->commit)
4881 free(blame->commit);
4884 setup_update(view, opt_file);
4885 string_format(view->ref, "%s ...", opt_file);
4887 return TRUE;
4890 static struct blame_commit *
4891 get_blame_commit(struct view *view, const char *id)
4893 size_t i;
4895 for (i = 0; i < view->lines; i++) {
4896 struct blame *blame = view->line[i].data;
4898 if (!blame->commit)
4899 continue;
4901 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4902 return blame->commit;
4906 struct blame_commit *commit = calloc(1, sizeof(*commit));
4908 if (commit)
4909 string_ncopy(commit->id, id, SIZEOF_REV);
4910 return commit;
4914 static bool
4915 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4917 const char *pos = *posref;
4919 *posref = NULL;
4920 pos = strchr(pos + 1, ' ');
4921 if (!pos || !isdigit(pos[1]))
4922 return FALSE;
4923 *number = atoi(pos + 1);
4924 if (*number < min || *number > max)
4925 return FALSE;
4927 *posref = pos;
4928 return TRUE;
4931 static struct blame_commit *
4932 parse_blame_commit(struct view *view, const char *text, int *blamed)
4934 struct blame_commit *commit;
4935 struct blame *blame;
4936 const char *pos = text + SIZEOF_REV - 2;
4937 size_t orig_lineno = 0;
4938 size_t lineno;
4939 size_t group;
4941 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4942 return NULL;
4944 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4945 !parse_number(&pos, &lineno, 1, view->lines) ||
4946 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4947 return NULL;
4949 commit = get_blame_commit(view, text);
4950 if (!commit)
4951 return NULL;
4953 *blamed += group;
4954 while (group--) {
4955 struct line *line = &view->line[lineno + group - 1];
4957 blame = line->data;
4958 blame->commit = commit;
4959 blame->lineno = orig_lineno + group - 1;
4960 line->dirty = 1;
4963 return commit;
4966 static bool
4967 blame_read_file(struct view *view, const char *line, bool *read_file)
4969 if (!line) {
4970 const char *blame_argv[] = {
4971 "git", "blame", "--incremental",
4972 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4975 if (view->lines == 0 && !view->prev)
4976 die("No blame exist for %s", view->vid);
4978 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4979 report("Failed to load blame data");
4980 return TRUE;
4983 *read_file = FALSE;
4984 return FALSE;
4986 } else {
4987 size_t linelen = strlen(line);
4988 struct blame *blame = malloc(sizeof(*blame) + linelen);
4990 if (!blame)
4991 return FALSE;
4993 blame->commit = NULL;
4994 strncpy(blame->text, line, linelen);
4995 blame->text[linelen] = 0;
4996 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5000 static bool
5001 match_blame_header(const char *name, char **line)
5003 size_t namelen = strlen(name);
5004 bool matched = !strncmp(name, *line, namelen);
5006 if (matched)
5007 *line += namelen;
5009 return matched;
5012 static bool
5013 blame_read(struct view *view, char *line)
5015 static struct blame_commit *commit = NULL;
5016 static int blamed = 0;
5017 static bool read_file = TRUE;
5019 if (read_file)
5020 return blame_read_file(view, line, &read_file);
5022 if (!line) {
5023 /* Reset all! */
5024 commit = NULL;
5025 blamed = 0;
5026 read_file = TRUE;
5027 string_format(view->ref, "%s", view->vid);
5028 if (view_is_displayed(view)) {
5029 update_view_title(view);
5030 redraw_view_from(view, 0);
5032 return TRUE;
5035 if (!commit) {
5036 commit = parse_blame_commit(view, line, &blamed);
5037 string_format(view->ref, "%s %2d%%", view->vid,
5038 view->lines ? blamed * 100 / view->lines : 0);
5040 } else if (match_blame_header("author ", &line)) {
5041 commit->author = get_author(line);
5043 } else if (match_blame_header("author-time ", &line)) {
5044 parse_timesec(&commit->time, line);
5046 } else if (match_blame_header("author-tz ", &line)) {
5047 parse_timezone(&commit->time, line);
5049 } else if (match_blame_header("summary ", &line)) {
5050 string_ncopy(commit->title, line, strlen(line));
5052 } else if (match_blame_header("previous ", &line)) {
5053 if (strlen(line) <= SIZEOF_REV)
5054 return FALSE;
5055 string_copy_rev(commit->parent_id, line);
5056 line += SIZEOF_REV;
5057 string_ncopy(commit->parent_filename, line, strlen(line));
5059 } else if (match_blame_header("filename ", &line)) {
5060 string_ncopy(commit->filename, line, strlen(line));
5061 commit = NULL;
5064 return TRUE;
5067 static bool
5068 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5070 struct blame *blame = line->data;
5071 struct time *time = NULL;
5072 const char *id = NULL, *author = NULL;
5073 char text[SIZEOF_STR];
5075 if (blame->commit && *blame->commit->filename) {
5076 id = blame->commit->id;
5077 author = blame->commit->author;
5078 time = &blame->commit->time;
5081 if (opt_date && draw_date(view, time))
5082 return TRUE;
5084 if (opt_author && draw_author(view, author))
5085 return TRUE;
5087 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5088 return TRUE;
5090 if (draw_lineno(view, lineno))
5091 return TRUE;
5093 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5094 draw_text(view, LINE_DEFAULT, text, TRUE);
5095 return TRUE;
5098 static bool
5099 check_blame_commit(struct blame *blame, bool check_null_id)
5101 if (!blame->commit)
5102 report("Commit data not loaded yet");
5103 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5104 report("No commit exist for the selected line");
5105 else
5106 return TRUE;
5107 return FALSE;
5110 static void
5111 setup_blame_parent_line(struct view *view, struct blame *blame)
5113 const char *diff_tree_argv[] = {
5114 "git", "diff-tree", "-U0", blame->commit->id,
5115 "--", blame->commit->filename, NULL
5117 struct io io;
5118 int parent_lineno = -1;
5119 int blamed_lineno = -1;
5120 char *line;
5122 if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5123 return;
5125 while ((line = io_get(&io, '\n', TRUE))) {
5126 if (*line == '@') {
5127 char *pos = strchr(line, '+');
5129 parent_lineno = atoi(line + 4);
5130 if (pos)
5131 blamed_lineno = atoi(pos + 1);
5133 } else if (*line == '+' && parent_lineno != -1) {
5134 if (blame->lineno == blamed_lineno - 1 &&
5135 !strcmp(blame->text, line + 1)) {
5136 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5137 break;
5139 blamed_lineno++;
5143 io_done(&io);
5146 static enum request
5147 blame_request(struct view *view, enum request request, struct line *line)
5149 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5150 struct blame *blame = line->data;
5152 switch (request) {
5153 case REQ_VIEW_BLAME:
5154 if (check_blame_commit(blame, TRUE)) {
5155 string_copy(opt_ref, blame->commit->id);
5156 string_copy(opt_file, blame->commit->filename);
5157 if (blame->lineno)
5158 view->lineno = blame->lineno;
5159 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5161 break;
5163 case REQ_PARENT:
5164 if (!check_blame_commit(blame, TRUE))
5165 break;
5166 if (!*blame->commit->parent_id) {
5167 report("The selected commit has no parents");
5168 } else {
5169 string_copy_rev(opt_ref, blame->commit->parent_id);
5170 string_copy(opt_file, blame->commit->parent_filename);
5171 setup_blame_parent_line(view, blame);
5172 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5174 break;
5176 case REQ_ENTER:
5177 if (!check_blame_commit(blame, FALSE))
5178 break;
5180 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5181 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5182 break;
5184 if (!strcmp(blame->commit->id, NULL_ID)) {
5185 struct view *diff = VIEW(REQ_VIEW_DIFF);
5186 const char *diff_index_argv[] = {
5187 "git", "diff-index", "--root", "--patch-with-stat",
5188 "-C", "-M", "HEAD", "--", view->vid, NULL
5191 if (!*blame->commit->parent_id) {
5192 diff_index_argv[1] = "diff";
5193 diff_index_argv[2] = "--no-color";
5194 diff_index_argv[6] = "--";
5195 diff_index_argv[7] = "/dev/null";
5198 if (!prepare_update(diff, diff_index_argv, NULL)) {
5199 report("Failed to allocate diff command");
5200 break;
5202 flags |= OPEN_PREPARED;
5205 open_view(view, REQ_VIEW_DIFF, flags);
5206 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5207 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5208 break;
5210 default:
5211 return request;
5214 return REQ_NONE;
5217 static bool
5218 blame_grep(struct view *view, struct line *line)
5220 struct blame *blame = line->data;
5221 struct blame_commit *commit = blame->commit;
5222 const char *text[] = {
5223 blame->text,
5224 commit ? commit->title : "",
5225 commit ? commit->id : "",
5226 commit && opt_author ? commit->author : "",
5227 commit ? mkdate(&commit->time, opt_date) : "",
5228 NULL
5231 return grep_text(view, text);
5234 static void
5235 blame_select(struct view *view, struct line *line)
5237 struct blame *blame = line->data;
5238 struct blame_commit *commit = blame->commit;
5240 if (!commit)
5241 return;
5243 if (!strcmp(commit->id, NULL_ID))
5244 string_ncopy(ref_commit, "HEAD", 4);
5245 else
5246 string_copy_rev(ref_commit, commit->id);
5249 static struct view_ops blame_ops = {
5250 "line",
5251 NULL,
5252 blame_open,
5253 blame_read,
5254 blame_draw,
5255 blame_request,
5256 blame_grep,
5257 blame_select,
5261 * Branch backend
5264 struct branch {
5265 const char *author; /* Author of the last commit. */
5266 struct time time; /* Date of the last activity. */
5267 const struct ref *ref; /* Name and commit ID information. */
5270 static const struct ref branch_all;
5272 static const enum sort_field branch_sort_fields[] = {
5273 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5275 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5277 static int
5278 branch_compare(const void *l1, const void *l2)
5280 const struct branch *branch1 = ((const struct line *) l1)->data;
5281 const struct branch *branch2 = ((const struct line *) l2)->data;
5283 switch (get_sort_field(branch_sort_state)) {
5284 case ORDERBY_DATE:
5285 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5287 case ORDERBY_AUTHOR:
5288 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5290 case ORDERBY_NAME:
5291 default:
5292 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5296 static bool
5297 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5299 struct branch *branch = line->data;
5300 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5302 if (opt_date && draw_date(view, &branch->time))
5303 return TRUE;
5305 if (opt_author && draw_author(view, branch->author))
5306 return TRUE;
5308 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5309 return TRUE;
5312 static enum request
5313 branch_request(struct view *view, enum request request, struct line *line)
5315 struct branch *branch = line->data;
5317 switch (request) {
5318 case REQ_REFRESH:
5319 load_refs();
5320 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5321 return REQ_NONE;
5323 case REQ_TOGGLE_SORT_FIELD:
5324 case REQ_TOGGLE_SORT_ORDER:
5325 sort_view(view, request, &branch_sort_state, branch_compare);
5326 return REQ_NONE;
5328 case REQ_ENTER:
5330 const struct ref *ref = branch->ref;
5331 const char *all_branches_argv[] = {
5332 "git", "log", "--no-color", "--pretty=raw", "--parents",
5333 "--topo-order",
5334 ref == &branch_all ? "--all" : ref->name, NULL
5336 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5338 if (!prepare_update(main_view, all_branches_argv, NULL))
5339 report("Failed to load view of all branches");
5340 else
5341 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5342 return REQ_NONE;
5344 default:
5345 return request;
5349 static bool
5350 branch_read(struct view *view, char *line)
5352 static char id[SIZEOF_REV];
5353 struct branch *reference;
5354 size_t i;
5356 if (!line)
5357 return TRUE;
5359 switch (get_line_type(line)) {
5360 case LINE_COMMIT:
5361 string_copy_rev(id, line + STRING_SIZE("commit "));
5362 return TRUE;
5364 case LINE_AUTHOR:
5365 for (i = 0, reference = NULL; i < view->lines; i++) {
5366 struct branch *branch = view->line[i].data;
5368 if (strcmp(branch->ref->id, id))
5369 continue;
5371 view->line[i].dirty = TRUE;
5372 if (reference) {
5373 branch->author = reference->author;
5374 branch->time = reference->time;
5375 continue;
5378 parse_author_line(line + STRING_SIZE("author "),
5379 &branch->author, &branch->time);
5380 reference = branch;
5382 return TRUE;
5384 default:
5385 return TRUE;
5390 static bool
5391 branch_open_visitor(void *data, const struct ref *ref)
5393 struct view *view = data;
5394 struct branch *branch;
5396 if (ref->tag || ref->ltag || ref->remote)
5397 return TRUE;
5399 branch = calloc(1, sizeof(*branch));
5400 if (!branch)
5401 return FALSE;
5403 branch->ref = ref;
5404 return !!add_line_data(view, branch, LINE_DEFAULT);
5407 static bool
5408 branch_open(struct view *view)
5410 const char *branch_log[] = {
5411 "git", "log", "--no-color", "--pretty=raw",
5412 "--simplify-by-decoration", "--all", NULL
5415 if (!start_update(view, branch_log, NULL)) {
5416 report("Failed to load branch data");
5417 return TRUE;
5420 setup_update(view, view->id);
5421 branch_open_visitor(view, &branch_all);
5422 foreach_ref(branch_open_visitor, view);
5423 view->p_restore = TRUE;
5425 return TRUE;
5428 static bool
5429 branch_grep(struct view *view, struct line *line)
5431 struct branch *branch = line->data;
5432 const char *text[] = {
5433 branch->ref->name,
5434 branch->author,
5435 NULL
5438 return grep_text(view, text);
5441 static void
5442 branch_select(struct view *view, struct line *line)
5444 struct branch *branch = line->data;
5446 string_copy_rev(view->ref, branch->ref->id);
5447 string_copy_rev(ref_commit, branch->ref->id);
5448 string_copy_rev(ref_head, branch->ref->id);
5449 string_copy_rev(ref_branch, branch->ref->name);
5452 static struct view_ops branch_ops = {
5453 "branch",
5454 NULL,
5455 branch_open,
5456 branch_read,
5457 branch_draw,
5458 branch_request,
5459 branch_grep,
5460 branch_select,
5464 * Status backend
5467 struct status {
5468 char status;
5469 struct {
5470 mode_t mode;
5471 char rev[SIZEOF_REV];
5472 char name[SIZEOF_STR];
5473 } old;
5474 struct {
5475 mode_t mode;
5476 char rev[SIZEOF_REV];
5477 char name[SIZEOF_STR];
5478 } new;
5481 static char status_onbranch[SIZEOF_STR];
5482 static struct status stage_status;
5483 static enum line_type stage_line_type;
5484 static size_t stage_chunks;
5485 static int *stage_chunk;
5487 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5489 /* This should work even for the "On branch" line. */
5490 static inline bool
5491 status_has_none(struct view *view, struct line *line)
5493 return line < view->line + view->lines && !line[1].data;
5496 /* Get fields from the diff line:
5497 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5499 static inline bool
5500 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5502 const char *old_mode = buf + 1;
5503 const char *new_mode = buf + 8;
5504 const char *old_rev = buf + 15;
5505 const char *new_rev = buf + 56;
5506 const char *status = buf + 97;
5508 if (bufsize < 98 ||
5509 old_mode[-1] != ':' ||
5510 new_mode[-1] != ' ' ||
5511 old_rev[-1] != ' ' ||
5512 new_rev[-1] != ' ' ||
5513 status[-1] != ' ')
5514 return FALSE;
5516 file->status = *status;
5518 string_copy_rev(file->old.rev, old_rev);
5519 string_copy_rev(file->new.rev, new_rev);
5521 file->old.mode = strtoul(old_mode, NULL, 8);
5522 file->new.mode = strtoul(new_mode, NULL, 8);
5524 file->old.name[0] = file->new.name[0] = 0;
5526 return TRUE;
5529 static bool
5530 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5532 struct status *unmerged = NULL;
5533 char *buf;
5534 struct io io;
5536 if (!io_run(&io, IO_RD, opt_cdup, argv))
5537 return FALSE;
5539 add_line_data(view, NULL, type);
5541 while ((buf = io_get(&io, 0, TRUE))) {
5542 struct status *file = unmerged;
5544 if (!file) {
5545 file = calloc(1, sizeof(*file));
5546 if (!file || !add_line_data(view, file, type))
5547 goto error_out;
5550 /* Parse diff info part. */
5551 if (status) {
5552 file->status = status;
5553 if (status == 'A')
5554 string_copy(file->old.rev, NULL_ID);
5556 } else if (!file->status || file == unmerged) {
5557 if (!status_get_diff(file, buf, strlen(buf)))
5558 goto error_out;
5560 buf = io_get(&io, 0, TRUE);
5561 if (!buf)
5562 break;
5564 /* Collapse all modified entries that follow an
5565 * associated unmerged entry. */
5566 if (unmerged == file) {
5567 unmerged->status = 'U';
5568 unmerged = NULL;
5569 } else if (file->status == 'U') {
5570 unmerged = file;
5574 /* Grab the old name for rename/copy. */
5575 if (!*file->old.name &&
5576 (file->status == 'R' || file->status == 'C')) {
5577 string_ncopy(file->old.name, buf, strlen(buf));
5579 buf = io_get(&io, 0, TRUE);
5580 if (!buf)
5581 break;
5584 /* git-ls-files just delivers a NUL separated list of
5585 * file names similar to the second half of the
5586 * git-diff-* output. */
5587 string_ncopy(file->new.name, buf, strlen(buf));
5588 if (!*file->old.name)
5589 string_copy(file->old.name, file->new.name);
5590 file = NULL;
5593 if (io_error(&io)) {
5594 error_out:
5595 io_done(&io);
5596 return FALSE;
5599 if (!view->line[view->lines - 1].data)
5600 add_line_data(view, NULL, LINE_STAT_NONE);
5602 io_done(&io);
5603 return TRUE;
5606 /* Don't show unmerged entries in the staged section. */
5607 static const char *status_diff_index_argv[] = {
5608 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5609 "--cached", "-M", "HEAD", NULL
5612 static const char *status_diff_files_argv[] = {
5613 "git", "diff-files", "-z", NULL
5616 static const char *status_list_other_argv[] = {
5617 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5620 static const char *status_list_no_head_argv[] = {
5621 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5624 static const char *update_index_argv[] = {
5625 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5628 /* Restore the previous line number to stay in the context or select a
5629 * line with something that can be updated. */
5630 static void
5631 status_restore(struct view *view)
5633 if (view->p_lineno >= view->lines)
5634 view->p_lineno = view->lines - 1;
5635 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5636 view->p_lineno++;
5637 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5638 view->p_lineno--;
5640 /* If the above fails, always skip the "On branch" line. */
5641 if (view->p_lineno < view->lines)
5642 view->lineno = view->p_lineno;
5643 else
5644 view->lineno = 1;
5646 if (view->lineno < view->offset)
5647 view->offset = view->lineno;
5648 else if (view->offset + view->height <= view->lineno)
5649 view->offset = view->lineno - view->height + 1;
5651 view->p_restore = FALSE;
5654 static void
5655 status_update_onbranch(void)
5657 static const char *paths[][2] = {
5658 { "rebase-apply/rebasing", "Rebasing" },
5659 { "rebase-apply/applying", "Applying mailbox" },
5660 { "rebase-apply/", "Rebasing mailbox" },
5661 { "rebase-merge/interactive", "Interactive rebase" },
5662 { "rebase-merge/", "Rebase merge" },
5663 { "MERGE_HEAD", "Merging" },
5664 { "BISECT_LOG", "Bisecting" },
5665 { "HEAD", "On branch" },
5667 char buf[SIZEOF_STR];
5668 struct stat stat;
5669 int i;
5671 if (is_initial_commit()) {
5672 string_copy(status_onbranch, "Initial commit");
5673 return;
5676 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5677 char *head = opt_head;
5679 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5680 lstat(buf, &stat) < 0)
5681 continue;
5683 if (!*opt_head) {
5684 struct io io;
5686 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5687 io_read_buf(&io, buf, sizeof(buf))) {
5688 head = buf;
5689 if (!prefixcmp(head, "refs/heads/"))
5690 head += STRING_SIZE("refs/heads/");
5694 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5695 string_copy(status_onbranch, opt_head);
5696 return;
5699 string_copy(status_onbranch, "Not currently on any branch");
5702 /* First parse staged info using git-diff-index(1), then parse unstaged
5703 * info using git-diff-files(1), and finally untracked files using
5704 * git-ls-files(1). */
5705 static bool
5706 status_open(struct view *view)
5708 reset_view(view);
5710 add_line_data(view, NULL, LINE_STAT_HEAD);
5711 status_update_onbranch();
5713 io_run_bg(update_index_argv);
5715 if (is_initial_commit()) {
5716 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5717 return FALSE;
5718 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5719 return FALSE;
5722 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5723 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5724 return FALSE;
5726 /* Restore the exact position or use the specialized restore
5727 * mode? */
5728 if (!view->p_restore)
5729 status_restore(view);
5730 return TRUE;
5733 static bool
5734 status_draw(struct view *view, struct line *line, unsigned int lineno)
5736 struct status *status = line->data;
5737 enum line_type type;
5738 const char *text;
5740 if (!status) {
5741 switch (line->type) {
5742 case LINE_STAT_STAGED:
5743 type = LINE_STAT_SECTION;
5744 text = "Changes to be committed:";
5745 break;
5747 case LINE_STAT_UNSTAGED:
5748 type = LINE_STAT_SECTION;
5749 text = "Changed but not updated:";
5750 break;
5752 case LINE_STAT_UNTRACKED:
5753 type = LINE_STAT_SECTION;
5754 text = "Untracked files:";
5755 break;
5757 case LINE_STAT_NONE:
5758 type = LINE_DEFAULT;
5759 text = " (no files)";
5760 break;
5762 case LINE_STAT_HEAD:
5763 type = LINE_STAT_HEAD;
5764 text = status_onbranch;
5765 break;
5767 default:
5768 return FALSE;
5770 } else {
5771 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5773 buf[0] = status->status;
5774 if (draw_text(view, line->type, buf, TRUE))
5775 return TRUE;
5776 type = LINE_DEFAULT;
5777 text = status->new.name;
5780 draw_text(view, type, text, TRUE);
5781 return TRUE;
5784 static enum request
5785 status_load_error(struct view *view, struct view *stage, const char *path)
5787 if (displayed_views() == 2 || display[current_view] != view)
5788 maximize_view(view);
5789 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5790 return REQ_NONE;
5793 static enum request
5794 status_enter(struct view *view, struct line *line)
5796 struct status *status = line->data;
5797 const char *oldpath = status ? status->old.name : NULL;
5798 /* Diffs for unmerged entries are empty when passing the new
5799 * path, so leave it empty. */
5800 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5801 const char *info;
5802 enum open_flags split;
5803 struct view *stage = VIEW(REQ_VIEW_STAGE);
5805 if (line->type == LINE_STAT_NONE ||
5806 (!status && line[1].type == LINE_STAT_NONE)) {
5807 report("No file to diff");
5808 return REQ_NONE;
5811 switch (line->type) {
5812 case LINE_STAT_STAGED:
5813 if (is_initial_commit()) {
5814 const char *no_head_diff_argv[] = {
5815 "git", "diff", "--no-color", "--patch-with-stat",
5816 "--", "/dev/null", newpath, NULL
5819 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5820 return status_load_error(view, stage, newpath);
5821 } else {
5822 const char *index_show_argv[] = {
5823 "git", "diff-index", "--root", "--patch-with-stat",
5824 "-C", "-M", "--cached", "HEAD", "--",
5825 oldpath, newpath, NULL
5828 if (!prepare_update(stage, index_show_argv, opt_cdup))
5829 return status_load_error(view, stage, newpath);
5832 if (status)
5833 info = "Staged changes to %s";
5834 else
5835 info = "Staged changes";
5836 break;
5838 case LINE_STAT_UNSTAGED:
5840 const char *files_show_argv[] = {
5841 "git", "diff-files", "--root", "--patch-with-stat",
5842 "-C", "-M", "--", oldpath, newpath, NULL
5845 if (!prepare_update(stage, files_show_argv, opt_cdup))
5846 return status_load_error(view, stage, newpath);
5847 if (status)
5848 info = "Unstaged changes to %s";
5849 else
5850 info = "Unstaged changes";
5851 break;
5853 case LINE_STAT_UNTRACKED:
5854 if (!newpath) {
5855 report("No file to show");
5856 return REQ_NONE;
5859 if (!suffixcmp(status->new.name, -1, "/")) {
5860 report("Cannot display a directory");
5861 return REQ_NONE;
5864 if (!prepare_update_file(stage, newpath))
5865 return status_load_error(view, stage, newpath);
5866 info = "Untracked file %s";
5867 break;
5869 case LINE_STAT_HEAD:
5870 return REQ_NONE;
5872 default:
5873 die("line type %d not handled in switch", line->type);
5876 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5877 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5878 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5879 if (status) {
5880 stage_status = *status;
5881 } else {
5882 memset(&stage_status, 0, sizeof(stage_status));
5885 stage_line_type = line->type;
5886 stage_chunks = 0;
5887 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5890 return REQ_NONE;
5893 static bool
5894 status_exists(struct status *status, enum line_type type)
5896 struct view *view = VIEW(REQ_VIEW_STATUS);
5897 unsigned long lineno;
5899 for (lineno = 0; lineno < view->lines; lineno++) {
5900 struct line *line = &view->line[lineno];
5901 struct status *pos = line->data;
5903 if (line->type != type)
5904 continue;
5905 if (!pos && (!status || !status->status) && line[1].data) {
5906 select_view_line(view, lineno);
5907 return TRUE;
5909 if (pos && !strcmp(status->new.name, pos->new.name)) {
5910 select_view_line(view, lineno);
5911 return TRUE;
5915 return FALSE;
5919 static bool
5920 status_update_prepare(struct io *io, enum line_type type)
5922 const char *staged_argv[] = {
5923 "git", "update-index", "-z", "--index-info", NULL
5925 const char *others_argv[] = {
5926 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5929 switch (type) {
5930 case LINE_STAT_STAGED:
5931 return io_run(io, IO_WR, opt_cdup, staged_argv);
5933 case LINE_STAT_UNSTAGED:
5934 case LINE_STAT_UNTRACKED:
5935 return io_run(io, IO_WR, opt_cdup, others_argv);
5937 default:
5938 die("line type %d not handled in switch", type);
5939 return FALSE;
5943 static bool
5944 status_update_write(struct io *io, struct status *status, enum line_type type)
5946 char buf[SIZEOF_STR];
5947 size_t bufsize = 0;
5949 switch (type) {
5950 case LINE_STAT_STAGED:
5951 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5952 status->old.mode,
5953 status->old.rev,
5954 status->old.name, 0))
5955 return FALSE;
5956 break;
5958 case LINE_STAT_UNSTAGED:
5959 case LINE_STAT_UNTRACKED:
5960 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5961 return FALSE;
5962 break;
5964 default:
5965 die("line type %d not handled in switch", type);
5968 return io_write(io, buf, bufsize);
5971 static bool
5972 status_update_file(struct status *status, enum line_type type)
5974 struct io io;
5975 bool result;
5977 if (!status_update_prepare(&io, type))
5978 return FALSE;
5980 result = status_update_write(&io, status, type);
5981 return io_done(&io) && result;
5984 static bool
5985 status_update_files(struct view *view, struct line *line)
5987 char buf[sizeof(view->ref)];
5988 struct io io;
5989 bool result = TRUE;
5990 struct line *pos = view->line + view->lines;
5991 int files = 0;
5992 int file, done;
5993 int cursor_y = -1, cursor_x = -1;
5995 if (!status_update_prepare(&io, line->type))
5996 return FALSE;
5998 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5999 files++;
6001 string_copy(buf, view->ref);
6002 getsyx(cursor_y, cursor_x);
6003 for (file = 0, done = 5; result && file < files; line++, file++) {
6004 int almost_done = file * 100 / files;
6006 if (almost_done > done) {
6007 done = almost_done;
6008 string_format(view->ref, "updating file %u of %u (%d%% done)",
6009 file, files, done);
6010 update_view_title(view);
6011 setsyx(cursor_y, cursor_x);
6012 doupdate();
6014 result = status_update_write(&io, line->data, line->type);
6016 string_copy(view->ref, buf);
6018 return io_done(&io) && result;
6021 static bool
6022 status_update(struct view *view)
6024 struct line *line = &view->line[view->lineno];
6026 assert(view->lines);
6028 if (!line->data) {
6029 /* This should work even for the "On branch" line. */
6030 if (line < view->line + view->lines && !line[1].data) {
6031 report("Nothing to update");
6032 return FALSE;
6035 if (!status_update_files(view, line + 1)) {
6036 report("Failed to update file status");
6037 return FALSE;
6040 } else if (!status_update_file(line->data, line->type)) {
6041 report("Failed to update file status");
6042 return FALSE;
6045 return TRUE;
6048 static bool
6049 status_revert(struct status *status, enum line_type type, bool has_none)
6051 if (!status || type != LINE_STAT_UNSTAGED) {
6052 if (type == LINE_STAT_STAGED) {
6053 report("Cannot revert changes to staged files");
6054 } else if (type == LINE_STAT_UNTRACKED) {
6055 report("Cannot revert changes to untracked files");
6056 } else if (has_none) {
6057 report("Nothing to revert");
6058 } else {
6059 report("Cannot revert changes to multiple files");
6062 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6063 char mode[10] = "100644";
6064 const char *reset_argv[] = {
6065 "git", "update-index", "--cacheinfo", mode,
6066 status->old.rev, status->old.name, NULL
6068 const char *checkout_argv[] = {
6069 "git", "checkout", "--", status->old.name, NULL
6072 if (status->status == 'U') {
6073 string_format(mode, "%5o", status->old.mode);
6075 if (status->old.mode == 0 && status->new.mode == 0) {
6076 reset_argv[2] = "--force-remove";
6077 reset_argv[3] = status->old.name;
6078 reset_argv[4] = NULL;
6081 if (!io_run_fg(reset_argv, opt_cdup))
6082 return FALSE;
6083 if (status->old.mode == 0 && status->new.mode == 0)
6084 return TRUE;
6087 return io_run_fg(checkout_argv, opt_cdup);
6090 return FALSE;
6093 static enum request
6094 status_request(struct view *view, enum request request, struct line *line)
6096 struct status *status = line->data;
6098 switch (request) {
6099 case REQ_STATUS_UPDATE:
6100 if (!status_update(view))
6101 return REQ_NONE;
6102 break;
6104 case REQ_STATUS_REVERT:
6105 if (!status_revert(status, line->type, status_has_none(view, line)))
6106 return REQ_NONE;
6107 break;
6109 case REQ_STATUS_MERGE:
6110 if (!status || status->status != 'U') {
6111 report("Merging only possible for files with unmerged status ('U').");
6112 return REQ_NONE;
6114 open_mergetool(status->new.name);
6115 break;
6117 case REQ_EDIT:
6118 if (!status)
6119 return request;
6120 if (status->status == 'D') {
6121 report("File has been deleted.");
6122 return REQ_NONE;
6125 open_editor(status->new.name);
6126 break;
6128 case REQ_VIEW_BLAME:
6129 if (status)
6130 opt_ref[0] = 0;
6131 return request;
6133 case REQ_ENTER:
6134 /* After returning the status view has been split to
6135 * show the stage view. No further reloading is
6136 * necessary. */
6137 return status_enter(view, line);
6139 case REQ_REFRESH:
6140 /* Simply reload the view. */
6141 break;
6143 default:
6144 return request;
6147 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6149 return REQ_NONE;
6152 static void
6153 status_select(struct view *view, struct line *line)
6155 struct status *status = line->data;
6156 char file[SIZEOF_STR] = "all files";
6157 const char *text;
6158 const char *key;
6160 if (status && !string_format(file, "'%s'", status->new.name))
6161 return;
6163 if (!status && line[1].type == LINE_STAT_NONE)
6164 line++;
6166 switch (line->type) {
6167 case LINE_STAT_STAGED:
6168 text = "Press %s to unstage %s for commit";
6169 break;
6171 case LINE_STAT_UNSTAGED:
6172 text = "Press %s to stage %s for commit";
6173 break;
6175 case LINE_STAT_UNTRACKED:
6176 text = "Press %s to stage %s for addition";
6177 break;
6179 case LINE_STAT_HEAD:
6180 case LINE_STAT_NONE:
6181 text = "Nothing to update";
6182 break;
6184 default:
6185 die("line type %d not handled in switch", line->type);
6188 if (status && status->status == 'U') {
6189 text = "Press %s to resolve conflict in %s";
6190 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6192 } else {
6193 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6196 string_format(view->ref, text, key, file);
6197 if (status)
6198 string_copy(opt_file, status->new.name);
6201 static bool
6202 status_grep(struct view *view, struct line *line)
6204 struct status *status = line->data;
6206 if (status) {
6207 const char buf[2] = { status->status, 0 };
6208 const char *text[] = { status->new.name, buf, NULL };
6210 return grep_text(view, text);
6213 return FALSE;
6216 static struct view_ops status_ops = {
6217 "file",
6218 NULL,
6219 status_open,
6220 NULL,
6221 status_draw,
6222 status_request,
6223 status_grep,
6224 status_select,
6228 static bool
6229 stage_diff_write(struct io *io, struct line *line, struct line *end)
6231 while (line < end) {
6232 if (!io_write(io, line->data, strlen(line->data)) ||
6233 !io_write(io, "\n", 1))
6234 return FALSE;
6235 line++;
6236 if (line->type == LINE_DIFF_CHUNK ||
6237 line->type == LINE_DIFF_HEADER)
6238 break;
6241 return TRUE;
6244 static struct line *
6245 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6247 for (; view->line < line; line--)
6248 if (line->type == type)
6249 return line;
6251 return NULL;
6254 static bool
6255 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6257 const char *apply_argv[SIZEOF_ARG] = {
6258 "git", "apply", "--whitespace=nowarn", NULL
6260 struct line *diff_hdr;
6261 struct io io;
6262 int argc = 3;
6264 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6265 if (!diff_hdr)
6266 return FALSE;
6268 if (!revert)
6269 apply_argv[argc++] = "--cached";
6270 if (revert || stage_line_type == LINE_STAT_STAGED)
6271 apply_argv[argc++] = "-R";
6272 apply_argv[argc++] = "-";
6273 apply_argv[argc++] = NULL;
6274 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6275 return FALSE;
6277 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6278 !stage_diff_write(&io, chunk, view->line + view->lines))
6279 chunk = NULL;
6281 io_done(&io);
6282 io_run_bg(update_index_argv);
6284 return chunk ? TRUE : FALSE;
6287 static bool
6288 stage_update(struct view *view, struct line *line)
6290 struct line *chunk = NULL;
6292 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6293 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6295 if (chunk) {
6296 if (!stage_apply_chunk(view, chunk, FALSE)) {
6297 report("Failed to apply chunk");
6298 return FALSE;
6301 } else if (!stage_status.status) {
6302 view = VIEW(REQ_VIEW_STATUS);
6304 for (line = view->line; line < view->line + view->lines; line++)
6305 if (line->type == stage_line_type)
6306 break;
6308 if (!status_update_files(view, line + 1)) {
6309 report("Failed to update files");
6310 return FALSE;
6313 } else if (!status_update_file(&stage_status, stage_line_type)) {
6314 report("Failed to update file");
6315 return FALSE;
6318 return TRUE;
6321 static bool
6322 stage_revert(struct view *view, struct line *line)
6324 struct line *chunk = NULL;
6326 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6327 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6329 if (chunk) {
6330 if (!prompt_yesno("Are you sure you want to revert changes?"))
6331 return FALSE;
6333 if (!stage_apply_chunk(view, chunk, TRUE)) {
6334 report("Failed to revert chunk");
6335 return FALSE;
6337 return TRUE;
6339 } else {
6340 return status_revert(stage_status.status ? &stage_status : NULL,
6341 stage_line_type, FALSE);
6346 static void
6347 stage_next(struct view *view, struct line *line)
6349 int i;
6351 if (!stage_chunks) {
6352 for (line = view->line; line < view->line + view->lines; line++) {
6353 if (line->type != LINE_DIFF_CHUNK)
6354 continue;
6356 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6357 report("Allocation failure");
6358 return;
6361 stage_chunk[stage_chunks++] = line - view->line;
6365 for (i = 0; i < stage_chunks; i++) {
6366 if (stage_chunk[i] > view->lineno) {
6367 do_scroll_view(view, stage_chunk[i] - view->lineno);
6368 report("Chunk %d of %d", i + 1, stage_chunks);
6369 return;
6373 report("No next chunk found");
6376 static enum request
6377 stage_request(struct view *view, enum request request, struct line *line)
6379 switch (request) {
6380 case REQ_STATUS_UPDATE:
6381 if (!stage_update(view, line))
6382 return REQ_NONE;
6383 break;
6385 case REQ_STATUS_REVERT:
6386 if (!stage_revert(view, line))
6387 return REQ_NONE;
6388 break;
6390 case REQ_STAGE_NEXT:
6391 if (stage_line_type == LINE_STAT_UNTRACKED) {
6392 report("File is untracked; press %s to add",
6393 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6394 return REQ_NONE;
6396 stage_next(view, line);
6397 return REQ_NONE;
6399 case REQ_EDIT:
6400 if (!stage_status.new.name[0])
6401 return request;
6402 if (stage_status.status == 'D') {
6403 report("File has been deleted.");
6404 return REQ_NONE;
6407 open_editor(stage_status.new.name);
6408 break;
6410 case REQ_REFRESH:
6411 /* Reload everything ... */
6412 break;
6414 case REQ_VIEW_BLAME:
6415 if (stage_status.new.name[0]) {
6416 string_copy(opt_file, stage_status.new.name);
6417 opt_ref[0] = 0;
6419 return request;
6421 case REQ_ENTER:
6422 return pager_request(view, request, line);
6424 default:
6425 return request;
6428 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6429 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6431 /* Check whether the staged entry still exists, and close the
6432 * stage view if it doesn't. */
6433 if (!status_exists(&stage_status, stage_line_type)) {
6434 status_restore(VIEW(REQ_VIEW_STATUS));
6435 return REQ_VIEW_CLOSE;
6438 if (stage_line_type == LINE_STAT_UNTRACKED) {
6439 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6440 report("Cannot display a directory");
6441 return REQ_NONE;
6444 if (!prepare_update_file(view, stage_status.new.name)) {
6445 report("Failed to open file: %s", strerror(errno));
6446 return REQ_NONE;
6449 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6451 return REQ_NONE;
6454 static struct view_ops stage_ops = {
6455 "line",
6456 NULL,
6457 NULL,
6458 pager_read,
6459 pager_draw,
6460 stage_request,
6461 pager_grep,
6462 pager_select,
6467 * Revision graph
6470 struct commit {
6471 char id[SIZEOF_REV]; /* SHA1 ID. */
6472 char title[128]; /* First line of the commit message. */
6473 const char *author; /* Author of the commit. */
6474 struct time time; /* Date from the author ident. */
6475 struct ref_list *refs; /* Repository references. */
6476 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6477 size_t graph_size; /* The width of the graph array. */
6478 bool has_parents; /* Rewritten --parents seen. */
6481 /* Size of rev graph with no "padding" columns */
6482 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6484 struct rev_graph {
6485 struct rev_graph *prev, *next, *parents;
6486 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6487 size_t size;
6488 struct commit *commit;
6489 size_t pos;
6490 unsigned int boundary:1;
6493 /* Parents of the commit being visualized. */
6494 static struct rev_graph graph_parents[4];
6496 /* The current stack of revisions on the graph. */
6497 static struct rev_graph graph_stacks[4] = {
6498 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6499 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6500 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6501 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6504 static inline bool
6505 graph_parent_is_merge(struct rev_graph *graph)
6507 return graph->parents->size > 1;
6510 static inline void
6511 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6513 struct commit *commit = graph->commit;
6515 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6516 commit->graph[commit->graph_size++] = symbol;
6519 static void
6520 clear_rev_graph(struct rev_graph *graph)
6522 graph->boundary = 0;
6523 graph->size = graph->pos = 0;
6524 graph->commit = NULL;
6525 memset(graph->parents, 0, sizeof(*graph->parents));
6528 static void
6529 done_rev_graph(struct rev_graph *graph)
6531 if (graph_parent_is_merge(graph) &&
6532 graph->pos < graph->size - 1 &&
6533 graph->next->size == graph->size + graph->parents->size - 1) {
6534 size_t i = graph->pos + graph->parents->size - 1;
6536 graph->commit->graph_size = i * 2;
6537 while (i < graph->next->size - 1) {
6538 append_to_rev_graph(graph, ' ');
6539 append_to_rev_graph(graph, '\\');
6540 i++;
6544 clear_rev_graph(graph);
6547 static void
6548 push_rev_graph(struct rev_graph *graph, const char *parent)
6550 int i;
6552 /* "Collapse" duplicate parents lines.
6554 * FIXME: This needs to also update update the drawn graph but
6555 * for now it just serves as a method for pruning graph lines. */
6556 for (i = 0; i < graph->size; i++)
6557 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6558 return;
6560 if (graph->size < SIZEOF_REVITEMS) {
6561 string_copy_rev(graph->rev[graph->size++], parent);
6565 static chtype
6566 get_rev_graph_symbol(struct rev_graph *graph)
6568 chtype symbol;
6570 if (graph->boundary)
6571 symbol = REVGRAPH_BOUND;
6572 else if (graph->parents->size == 0)
6573 symbol = REVGRAPH_INIT;
6574 else if (graph_parent_is_merge(graph))
6575 symbol = REVGRAPH_MERGE;
6576 else if (graph->pos >= graph->size)
6577 symbol = REVGRAPH_BRANCH;
6578 else
6579 symbol = REVGRAPH_COMMIT;
6581 return symbol;
6584 static void
6585 draw_rev_graph(struct rev_graph *graph)
6587 struct rev_filler {
6588 chtype separator, line;
6590 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6591 static struct rev_filler fillers[] = {
6592 { ' ', '|' },
6593 { '`', '.' },
6594 { '\'', ' ' },
6595 { '/', ' ' },
6597 chtype symbol = get_rev_graph_symbol(graph);
6598 struct rev_filler *filler;
6599 size_t i;
6601 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6602 filler = &fillers[DEFAULT];
6604 for (i = 0; i < graph->pos; i++) {
6605 append_to_rev_graph(graph, filler->line);
6606 if (graph_parent_is_merge(graph->prev) &&
6607 graph->prev->pos == i)
6608 filler = &fillers[RSHARP];
6610 append_to_rev_graph(graph, filler->separator);
6613 /* Place the symbol for this revision. */
6614 append_to_rev_graph(graph, symbol);
6616 if (graph->prev->size > graph->size)
6617 filler = &fillers[RDIAG];
6618 else
6619 filler = &fillers[DEFAULT];
6621 i++;
6623 for (; i < graph->size; i++) {
6624 append_to_rev_graph(graph, filler->separator);
6625 append_to_rev_graph(graph, filler->line);
6626 if (graph_parent_is_merge(graph->prev) &&
6627 i < graph->prev->pos + graph->parents->size)
6628 filler = &fillers[RSHARP];
6629 if (graph->prev->size > graph->size)
6630 filler = &fillers[LDIAG];
6633 if (graph->prev->size > graph->size) {
6634 append_to_rev_graph(graph, filler->separator);
6635 if (filler->line != ' ')
6636 append_to_rev_graph(graph, filler->line);
6640 /* Prepare the next rev graph */
6641 static void
6642 prepare_rev_graph(struct rev_graph *graph)
6644 size_t i;
6646 /* First, traverse all lines of revisions up to the active one. */
6647 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6648 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6649 break;
6651 push_rev_graph(graph->next, graph->rev[graph->pos]);
6654 /* Interleave the new revision parent(s). */
6655 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6656 push_rev_graph(graph->next, graph->parents->rev[i]);
6658 /* Lastly, put any remaining revisions. */
6659 for (i = graph->pos + 1; i < graph->size; i++)
6660 push_rev_graph(graph->next, graph->rev[i]);
6663 static void
6664 update_rev_graph(struct view *view, struct rev_graph *graph)
6666 /* If this is the finalizing update ... */
6667 if (graph->commit)
6668 prepare_rev_graph(graph);
6670 /* Graph visualization needs a one rev look-ahead,
6671 * so the first update doesn't visualize anything. */
6672 if (!graph->prev->commit)
6673 return;
6675 if (view->lines > 2)
6676 view->line[view->lines - 3].dirty = 1;
6677 if (view->lines > 1)
6678 view->line[view->lines - 2].dirty = 1;
6679 draw_rev_graph(graph->prev);
6680 done_rev_graph(graph->prev->prev);
6685 * Main view backend
6688 static const char *main_argv[SIZEOF_ARG] = {
6689 "git", "log", "--no-color", "--pretty=raw", "--parents",
6690 "--topo-order", "%(diff-args)", "%(rev-args)",
6691 "--", "%(file-args)", NULL
6694 static bool
6695 main_draw(struct view *view, struct line *line, unsigned int lineno)
6697 struct commit *commit = line->data;
6699 if (!commit->author)
6700 return FALSE;
6702 if (opt_date && draw_date(view, &commit->time))
6703 return TRUE;
6705 if (opt_author && draw_author(view, commit->author))
6706 return TRUE;
6708 if (opt_rev_graph && commit->graph_size &&
6709 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6710 return TRUE;
6712 if (opt_show_refs && commit->refs) {
6713 size_t i;
6715 for (i = 0; i < commit->refs->size; i++) {
6716 struct ref *ref = commit->refs->refs[i];
6717 enum line_type type;
6719 if (ref->head)
6720 type = LINE_MAIN_HEAD;
6721 else if (ref->ltag)
6722 type = LINE_MAIN_LOCAL_TAG;
6723 else if (ref->tag)
6724 type = LINE_MAIN_TAG;
6725 else if (ref->tracked)
6726 type = LINE_MAIN_TRACKED;
6727 else if (ref->remote)
6728 type = LINE_MAIN_REMOTE;
6729 else
6730 type = LINE_MAIN_REF;
6732 if (draw_text(view, type, "[", TRUE) ||
6733 draw_text(view, type, ref->name, TRUE) ||
6734 draw_text(view, type, "]", TRUE))
6735 return TRUE;
6737 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6738 return TRUE;
6742 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6743 return TRUE;
6746 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6747 static bool
6748 main_read(struct view *view, char *line)
6750 static struct rev_graph *graph = graph_stacks;
6751 enum line_type type;
6752 struct commit *commit;
6754 if (!line) {
6755 int i;
6757 if (!view->lines && !view->prev)
6758 die("No revisions match the given arguments.");
6759 if (view->lines > 0) {
6760 commit = view->line[view->lines - 1].data;
6761 view->line[view->lines - 1].dirty = 1;
6762 if (!commit->author) {
6763 view->lines--;
6764 free(commit);
6765 graph->commit = NULL;
6768 update_rev_graph(view, graph);
6770 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6771 clear_rev_graph(&graph_stacks[i]);
6772 return TRUE;
6775 type = get_line_type(line);
6776 if (type == LINE_COMMIT) {
6777 commit = calloc(1, sizeof(struct commit));
6778 if (!commit)
6779 return FALSE;
6781 line += STRING_SIZE("commit ");
6782 if (*line == '-') {
6783 graph->boundary = 1;
6784 line++;
6787 string_copy_rev(commit->id, line);
6788 commit->refs = get_ref_list(commit->id);
6789 graph->commit = commit;
6790 add_line_data(view, commit, LINE_MAIN_COMMIT);
6792 while ((line = strchr(line, ' '))) {
6793 line++;
6794 push_rev_graph(graph->parents, line);
6795 commit->has_parents = TRUE;
6797 return TRUE;
6800 if (!view->lines)
6801 return TRUE;
6802 commit = view->line[view->lines - 1].data;
6804 switch (type) {
6805 case LINE_PARENT:
6806 if (commit->has_parents)
6807 break;
6808 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6809 break;
6811 case LINE_AUTHOR:
6812 parse_author_line(line + STRING_SIZE("author "),
6813 &commit->author, &commit->time);
6814 update_rev_graph(view, graph);
6815 graph = graph->next;
6816 break;
6818 default:
6819 /* Fill in the commit title if it has not already been set. */
6820 if (commit->title[0])
6821 break;
6823 /* Require titles to start with a non-space character at the
6824 * offset used by git log. */
6825 if (strncmp(line, " ", 4))
6826 break;
6827 line += 4;
6828 /* Well, if the title starts with a whitespace character,
6829 * try to be forgiving. Otherwise we end up with no title. */
6830 while (isspace(*line))
6831 line++;
6832 if (*line == '\0')
6833 break;
6834 /* FIXME: More graceful handling of titles; append "..." to
6835 * shortened titles, etc. */
6837 string_expand(commit->title, sizeof(commit->title), line, 1);
6838 view->line[view->lines - 1].dirty = 1;
6841 return TRUE;
6844 static enum request
6845 main_request(struct view *view, enum request request, struct line *line)
6847 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6849 switch (request) {
6850 case REQ_ENTER:
6851 open_view(view, REQ_VIEW_DIFF, flags);
6852 break;
6853 case REQ_REFRESH:
6854 load_refs();
6855 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6856 break;
6857 default:
6858 return request;
6861 return REQ_NONE;
6864 static bool
6865 grep_refs(struct ref_list *list, regex_t *regex)
6867 regmatch_t pmatch;
6868 size_t i;
6870 if (!opt_show_refs || !list)
6871 return FALSE;
6873 for (i = 0; i < list->size; i++) {
6874 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6875 return TRUE;
6878 return FALSE;
6881 static bool
6882 main_grep(struct view *view, struct line *line)
6884 struct commit *commit = line->data;
6885 const char *text[] = {
6886 commit->title,
6887 opt_author ? commit->author : "",
6888 mkdate(&commit->time, opt_date),
6889 NULL
6892 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6895 static void
6896 main_select(struct view *view, struct line *line)
6898 struct commit *commit = line->data;
6900 string_copy_rev(view->ref, commit->id);
6901 string_copy_rev(ref_commit, view->ref);
6904 static struct view_ops main_ops = {
6905 "commit",
6906 main_argv,
6907 NULL,
6908 main_read,
6909 main_draw,
6910 main_request,
6911 main_grep,
6912 main_select,
6917 * Status management
6920 /* Whether or not the curses interface has been initialized. */
6921 static bool cursed = FALSE;
6923 /* Terminal hacks and workarounds. */
6924 static bool use_scroll_redrawwin;
6925 static bool use_scroll_status_wclear;
6927 /* The status window is used for polling keystrokes. */
6928 static WINDOW *status_win;
6930 /* Reading from the prompt? */
6931 static bool input_mode = FALSE;
6933 static bool status_empty = FALSE;
6935 /* Update status and title window. */
6936 static void
6937 report(const char *msg, ...)
6939 struct view *view = display[current_view];
6941 if (input_mode)
6942 return;
6944 if (!view) {
6945 char buf[SIZEOF_STR];
6946 va_list args;
6948 va_start(args, msg);
6949 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6950 buf[sizeof(buf) - 1] = 0;
6951 buf[sizeof(buf) - 2] = '.';
6952 buf[sizeof(buf) - 3] = '.';
6953 buf[sizeof(buf) - 4] = '.';
6955 va_end(args);
6956 die("%s", buf);
6959 if (!status_empty || *msg) {
6960 va_list args;
6962 va_start(args, msg);
6964 wmove(status_win, 0, 0);
6965 if (view->has_scrolled && use_scroll_status_wclear)
6966 wclear(status_win);
6967 if (*msg) {
6968 vwprintw(status_win, msg, args);
6969 status_empty = FALSE;
6970 } else {
6971 status_empty = TRUE;
6973 wclrtoeol(status_win);
6974 wnoutrefresh(status_win);
6976 va_end(args);
6979 update_view_title(view);
6982 static void
6983 init_display(void)
6985 const char *term;
6986 int x, y;
6988 /* Initialize the curses library */
6989 if (isatty(STDIN_FILENO)) {
6990 cursed = !!initscr();
6991 opt_tty = stdin;
6992 } else {
6993 /* Leave stdin and stdout alone when acting as a pager. */
6994 opt_tty = fopen("/dev/tty", "r+");
6995 if (!opt_tty)
6996 die("Failed to open /dev/tty");
6997 cursed = !!newterm(NULL, opt_tty, opt_tty);
7000 if (!cursed)
7001 die("Failed to initialize curses");
7003 nonl(); /* Disable conversion and detect newlines from input. */
7004 cbreak(); /* Take input chars one at a time, no wait for \n */
7005 noecho(); /* Don't echo input */
7006 leaveok(stdscr, FALSE);
7008 if (has_colors())
7009 init_colors();
7011 getmaxyx(stdscr, y, x);
7012 status_win = newwin(1, 0, y - 1, 0);
7013 if (!status_win)
7014 die("Failed to create status window");
7016 /* Enable keyboard mapping */
7017 keypad(status_win, TRUE);
7018 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7020 TABSIZE = opt_tab_size;
7022 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7023 if (term && !strcmp(term, "gnome-terminal")) {
7024 /* In the gnome-terminal-emulator, the message from
7025 * scrolling up one line when impossible followed by
7026 * scrolling down one line causes corruption of the
7027 * status line. This is fixed by calling wclear. */
7028 use_scroll_status_wclear = TRUE;
7029 use_scroll_redrawwin = FALSE;
7031 } else if (term && !strcmp(term, "xrvt-xpm")) {
7032 /* No problems with full optimizations in xrvt-(unicode)
7033 * and aterm. */
7034 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7036 } else {
7037 /* When scrolling in (u)xterm the last line in the
7038 * scrolling direction will update slowly. */
7039 use_scroll_redrawwin = TRUE;
7040 use_scroll_status_wclear = FALSE;
7044 static int
7045 get_input(int prompt_position)
7047 struct view *view;
7048 int i, key, cursor_y, cursor_x;
7050 if (prompt_position)
7051 input_mode = TRUE;
7053 while (TRUE) {
7054 bool loading = FALSE;
7056 foreach_view (view, i) {
7057 update_view(view);
7058 if (view_is_displayed(view) && view->has_scrolled &&
7059 use_scroll_redrawwin)
7060 redrawwin(view->win);
7061 view->has_scrolled = FALSE;
7062 if (view->pipe)
7063 loading = TRUE;
7066 /* Update the cursor position. */
7067 if (prompt_position) {
7068 getbegyx(status_win, cursor_y, cursor_x);
7069 cursor_x = prompt_position;
7070 } else {
7071 view = display[current_view];
7072 getbegyx(view->win, cursor_y, cursor_x);
7073 cursor_x = view->width - 1;
7074 cursor_y += view->lineno - view->offset;
7076 setsyx(cursor_y, cursor_x);
7078 /* Refresh, accept single keystroke of input */
7079 doupdate();
7080 nodelay(status_win, loading);
7081 key = wgetch(status_win);
7083 /* wgetch() with nodelay() enabled returns ERR when
7084 * there's no input. */
7085 if (key == ERR) {
7087 } else if (key == KEY_RESIZE) {
7088 int height, width;
7090 getmaxyx(stdscr, height, width);
7092 wresize(status_win, 1, width);
7093 mvwin(status_win, height - 1, 0);
7094 wnoutrefresh(status_win);
7095 resize_display();
7096 redraw_display(TRUE);
7098 } else {
7099 input_mode = FALSE;
7100 return key;
7105 static char *
7106 prompt_input(const char *prompt, input_handler handler, void *data)
7108 enum input_status status = INPUT_OK;
7109 static char buf[SIZEOF_STR];
7110 size_t pos = 0;
7112 buf[pos] = 0;
7114 while (status == INPUT_OK || status == INPUT_SKIP) {
7115 int key;
7117 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7118 wclrtoeol(status_win);
7120 key = get_input(pos + 1);
7121 switch (key) {
7122 case KEY_RETURN:
7123 case KEY_ENTER:
7124 case '\n':
7125 status = pos ? INPUT_STOP : INPUT_CANCEL;
7126 break;
7128 case KEY_BACKSPACE:
7129 if (pos > 0)
7130 buf[--pos] = 0;
7131 else
7132 status = INPUT_CANCEL;
7133 break;
7135 case KEY_ESC:
7136 status = INPUT_CANCEL;
7137 break;
7139 default:
7140 if (pos >= sizeof(buf)) {
7141 report("Input string too long");
7142 return NULL;
7145 status = handler(data, buf, key);
7146 if (status == INPUT_OK)
7147 buf[pos++] = (char) key;
7151 /* Clear the status window */
7152 status_empty = FALSE;
7153 report("");
7155 if (status == INPUT_CANCEL)
7156 return NULL;
7158 buf[pos++] = 0;
7160 return buf;
7163 static enum input_status
7164 prompt_yesno_handler(void *data, char *buf, int c)
7166 if (c == 'y' || c == 'Y')
7167 return INPUT_STOP;
7168 if (c == 'n' || c == 'N')
7169 return INPUT_CANCEL;
7170 return INPUT_SKIP;
7173 static bool
7174 prompt_yesno(const char *prompt)
7176 char prompt2[SIZEOF_STR];
7178 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7179 return FALSE;
7181 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7184 static enum input_status
7185 read_prompt_handler(void *data, char *buf, int c)
7187 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7190 static char *
7191 read_prompt(const char *prompt)
7193 return prompt_input(prompt, read_prompt_handler, NULL);
7196 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7198 enum input_status status = INPUT_OK;
7199 int size = 0;
7201 while (items[size].text)
7202 size++;
7204 while (status == INPUT_OK) {
7205 const struct menu_item *item = &items[*selected];
7206 int key;
7207 int i;
7209 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7210 prompt, *selected + 1, size);
7211 if (item->hotkey)
7212 wprintw(status_win, "[%c] ", (char) item->hotkey);
7213 wprintw(status_win, "%s", item->text);
7214 wclrtoeol(status_win);
7216 key = get_input(COLS - 1);
7217 switch (key) {
7218 case KEY_RETURN:
7219 case KEY_ENTER:
7220 case '\n':
7221 status = INPUT_STOP;
7222 break;
7224 case KEY_LEFT:
7225 case KEY_UP:
7226 *selected = *selected - 1;
7227 if (*selected < 0)
7228 *selected = size - 1;
7229 break;
7231 case KEY_RIGHT:
7232 case KEY_DOWN:
7233 *selected = (*selected + 1) % size;
7234 break;
7236 case KEY_ESC:
7237 status = INPUT_CANCEL;
7238 break;
7240 default:
7241 for (i = 0; items[i].text; i++)
7242 if (items[i].hotkey == key) {
7243 *selected = i;
7244 status = INPUT_STOP;
7245 break;
7250 /* Clear the status window */
7251 status_empty = FALSE;
7252 report("");
7254 return status != INPUT_CANCEL;
7258 * Repository properties
7261 static struct ref **refs = NULL;
7262 static size_t refs_size = 0;
7263 static struct ref *refs_head = NULL;
7265 static struct ref_list **ref_lists = NULL;
7266 static size_t ref_lists_size = 0;
7268 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7269 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7270 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7272 static int
7273 compare_refs(const void *ref1_, const void *ref2_)
7275 const struct ref *ref1 = *(const struct ref **)ref1_;
7276 const struct ref *ref2 = *(const struct ref **)ref2_;
7278 if (ref1->tag != ref2->tag)
7279 return ref2->tag - ref1->tag;
7280 if (ref1->ltag != ref2->ltag)
7281 return ref2->ltag - ref2->ltag;
7282 if (ref1->head != ref2->head)
7283 return ref2->head - ref1->head;
7284 if (ref1->tracked != ref2->tracked)
7285 return ref2->tracked - ref1->tracked;
7286 if (ref1->remote != ref2->remote)
7287 return ref2->remote - ref1->remote;
7288 return strcmp(ref1->name, ref2->name);
7291 static void
7292 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7294 size_t i;
7296 for (i = 0; i < refs_size; i++)
7297 if (!visitor(data, refs[i]))
7298 break;
7301 static struct ref *
7302 get_ref_head()
7304 return refs_head;
7307 static struct ref_list *
7308 get_ref_list(const char *id)
7310 struct ref_list *list;
7311 size_t i;
7313 for (i = 0; i < ref_lists_size; i++)
7314 if (!strcmp(id, ref_lists[i]->id))
7315 return ref_lists[i];
7317 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7318 return NULL;
7319 list = calloc(1, sizeof(*list));
7320 if (!list)
7321 return NULL;
7323 for (i = 0; i < refs_size; i++) {
7324 if (!strcmp(id, refs[i]->id) &&
7325 realloc_refs_list(&list->refs, list->size, 1))
7326 list->refs[list->size++] = refs[i];
7329 if (!list->refs) {
7330 free(list);
7331 return NULL;
7334 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7335 ref_lists[ref_lists_size++] = list;
7336 return list;
7339 static int
7340 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7342 struct ref *ref = NULL;
7343 bool tag = FALSE;
7344 bool ltag = FALSE;
7345 bool remote = FALSE;
7346 bool tracked = FALSE;
7347 bool head = FALSE;
7348 int from = 0, to = refs_size - 1;
7350 if (!prefixcmp(name, "refs/tags/")) {
7351 if (!suffixcmp(name, namelen, "^{}")) {
7352 namelen -= 3;
7353 name[namelen] = 0;
7354 } else {
7355 ltag = TRUE;
7358 tag = TRUE;
7359 namelen -= STRING_SIZE("refs/tags/");
7360 name += STRING_SIZE("refs/tags/");
7362 } else if (!prefixcmp(name, "refs/remotes/")) {
7363 remote = TRUE;
7364 namelen -= STRING_SIZE("refs/remotes/");
7365 name += STRING_SIZE("refs/remotes/");
7366 tracked = !strcmp(opt_remote, name);
7368 } else if (!prefixcmp(name, "refs/heads/")) {
7369 namelen -= STRING_SIZE("refs/heads/");
7370 name += STRING_SIZE("refs/heads/");
7371 if (!strncmp(opt_head, name, namelen))
7372 return OK;
7374 } else if (!strcmp(name, "HEAD")) {
7375 head = TRUE;
7376 if (*opt_head) {
7377 namelen = strlen(opt_head);
7378 name = opt_head;
7382 /* If we are reloading or it's an annotated tag, replace the
7383 * previous SHA1 with the resolved commit id; relies on the fact
7384 * git-ls-remote lists the commit id of an annotated tag right
7385 * before the commit id it points to. */
7386 while (from <= to) {
7387 size_t pos = (to + from) / 2;
7388 int cmp = strcmp(name, refs[pos]->name);
7390 if (!cmp) {
7391 ref = refs[pos];
7392 break;
7395 if (cmp < 0)
7396 to = pos - 1;
7397 else
7398 from = pos + 1;
7401 if (!ref) {
7402 if (!realloc_refs(&refs, refs_size, 1))
7403 return ERR;
7404 ref = calloc(1, sizeof(*ref) + namelen);
7405 if (!ref)
7406 return ERR;
7407 memmove(refs + from + 1, refs + from,
7408 (refs_size - from) * sizeof(*refs));
7409 refs[from] = ref;
7410 strncpy(ref->name, name, namelen);
7411 refs_size++;
7414 ref->head = head;
7415 ref->tag = tag;
7416 ref->ltag = ltag;
7417 ref->remote = remote;
7418 ref->tracked = tracked;
7419 string_copy_rev(ref->id, id);
7421 if (head)
7422 refs_head = ref;
7423 return OK;
7426 static int
7427 load_refs(void)
7429 const char *head_argv[] = {
7430 "git", "symbolic-ref", "HEAD", NULL
7432 static const char *ls_remote_argv[SIZEOF_ARG] = {
7433 "git", "ls-remote", opt_git_dir, NULL
7435 static bool init = FALSE;
7436 size_t i;
7438 if (!init) {
7439 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7440 die("TIG_LS_REMOTE contains too many arguments");
7441 init = TRUE;
7444 if (!*opt_git_dir)
7445 return OK;
7447 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7448 !prefixcmp(opt_head, "refs/heads/")) {
7449 char *offset = opt_head + STRING_SIZE("refs/heads/");
7451 memmove(opt_head, offset, strlen(offset) + 1);
7454 refs_head = NULL;
7455 for (i = 0; i < refs_size; i++)
7456 refs[i]->id[0] = 0;
7458 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7459 return ERR;
7461 /* Update the ref lists to reflect changes. */
7462 for (i = 0; i < ref_lists_size; i++) {
7463 struct ref_list *list = ref_lists[i];
7464 size_t old, new;
7466 for (old = new = 0; old < list->size; old++)
7467 if (!strcmp(list->id, list->refs[old]->id))
7468 list->refs[new++] = list->refs[old];
7469 list->size = new;
7472 return OK;
7475 static void
7476 set_remote_branch(const char *name, const char *value, size_t valuelen)
7478 if (!strcmp(name, ".remote")) {
7479 string_ncopy(opt_remote, value, valuelen);
7481 } else if (*opt_remote && !strcmp(name, ".merge")) {
7482 size_t from = strlen(opt_remote);
7484 if (!prefixcmp(value, "refs/heads/"))
7485 value += STRING_SIZE("refs/heads/");
7487 if (!string_format_from(opt_remote, &from, "/%s", value))
7488 opt_remote[0] = 0;
7492 static void
7493 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7495 const char *argv[SIZEOF_ARG] = { name, "=" };
7496 int argc = 1 + (cmd == option_set_command);
7497 int error = ERR;
7499 if (!argv_from_string(argv, &argc, value))
7500 config_msg = "Too many option arguments";
7501 else
7502 error = cmd(argc, argv);
7504 if (error == ERR)
7505 warn("Option 'tig.%s': %s", name, config_msg);
7508 static bool
7509 set_environment_variable(const char *name, const char *value)
7511 size_t len = strlen(name) + 1 + strlen(value) + 1;
7512 char *env = malloc(len);
7514 if (env &&
7515 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7516 putenv(env) == 0)
7517 return TRUE;
7518 free(env);
7519 return FALSE;
7522 static void
7523 set_work_tree(const char *value)
7525 char cwd[SIZEOF_STR];
7527 if (!getcwd(cwd, sizeof(cwd)))
7528 die("Failed to get cwd path: %s", strerror(errno));
7529 if (chdir(opt_git_dir) < 0)
7530 die("Failed to chdir(%s): %s", strerror(errno));
7531 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7532 die("Failed to get git path: %s", strerror(errno));
7533 if (chdir(cwd) < 0)
7534 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7535 if (chdir(value) < 0)
7536 die("Failed to chdir(%s): %s", value, strerror(errno));
7537 if (!getcwd(cwd, sizeof(cwd)))
7538 die("Failed to get cwd path: %s", strerror(errno));
7539 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7540 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7541 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7542 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7543 opt_is_inside_work_tree = TRUE;
7546 static int
7547 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7549 if (!strcmp(name, "i18n.commitencoding"))
7550 string_ncopy(opt_encoding, value, valuelen);
7552 else if (!strcmp(name, "core.editor"))
7553 string_ncopy(opt_editor, value, valuelen);
7555 else if (!strcmp(name, "core.worktree"))
7556 set_work_tree(value);
7558 else if (!prefixcmp(name, "tig.color."))
7559 set_repo_config_option(name + 10, value, option_color_command);
7561 else if (!prefixcmp(name, "tig.bind."))
7562 set_repo_config_option(name + 9, value, option_bind_command);
7564 else if (!prefixcmp(name, "tig."))
7565 set_repo_config_option(name + 4, value, option_set_command);
7567 else if (*opt_head && !prefixcmp(name, "branch.") &&
7568 !strncmp(name + 7, opt_head, strlen(opt_head)))
7569 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7571 return OK;
7574 static int
7575 load_git_config(void)
7577 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7579 return io_run_load(config_list_argv, "=", read_repo_config_option);
7582 static int
7583 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7585 if (!opt_git_dir[0]) {
7586 string_ncopy(opt_git_dir, name, namelen);
7588 } else if (opt_is_inside_work_tree == -1) {
7589 /* This can be 3 different values depending on the
7590 * version of git being used. If git-rev-parse does not
7591 * understand --is-inside-work-tree it will simply echo
7592 * the option else either "true" or "false" is printed.
7593 * Default to true for the unknown case. */
7594 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7596 } else if (*name == '.') {
7597 string_ncopy(opt_cdup, name, namelen);
7599 } else {
7600 string_ncopy(opt_prefix, name, namelen);
7603 return OK;
7606 static int
7607 load_repo_info(void)
7609 const char *rev_parse_argv[] = {
7610 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7611 "--show-cdup", "--show-prefix", NULL
7614 return io_run_load(rev_parse_argv, "=", read_repo_info);
7619 * Main
7622 static const char usage[] =
7623 "tig " TIG_VERSION " (" __DATE__ ")\n"
7624 "\n"
7625 "Usage: tig [options] [revs] [--] [paths]\n"
7626 " or: tig show [options] [revs] [--] [paths]\n"
7627 " or: tig blame [rev] path\n"
7628 " or: tig status\n"
7629 " or: tig < [git command output]\n"
7630 "\n"
7631 "Options:\n"
7632 " -v, --version Show version and exit\n"
7633 " -h, --help Show help message and exit";
7635 static void __NORETURN
7636 quit(int sig)
7638 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7639 if (cursed)
7640 endwin();
7641 exit(0);
7644 static void __NORETURN
7645 die(const char *err, ...)
7647 va_list args;
7649 endwin();
7651 va_start(args, err);
7652 fputs("tig: ", stderr);
7653 vfprintf(stderr, err, args);
7654 fputs("\n", stderr);
7655 va_end(args);
7657 exit(1);
7660 static void
7661 warn(const char *msg, ...)
7663 va_list args;
7665 va_start(args, msg);
7666 fputs("tig warning: ", stderr);
7667 vfprintf(stderr, msg, args);
7668 fputs("\n", stderr);
7669 va_end(args);
7672 static const char ***filter_args;
7674 static int
7675 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7677 return argv_append(filter_args, name) ? OK : ERR;
7680 static void
7681 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7683 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7684 const char **all_argv = NULL;
7686 filter_args = args;
7687 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7688 !argv_append_array(&all_argv, argv) ||
7689 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7690 die("Failed to split arguments");
7691 argv_free(all_argv);
7692 free(all_argv);
7695 static void
7696 filter_options(const char *argv[])
7698 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7699 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7700 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7703 static enum request
7704 parse_options(int argc, const char *argv[])
7706 enum request request = REQ_VIEW_MAIN;
7707 const char *subcommand;
7708 bool seen_dashdash = FALSE;
7709 const char **filter_argv = NULL;
7710 int i;
7712 if (!isatty(STDIN_FILENO)) {
7713 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7714 return REQ_VIEW_PAGER;
7717 if (argc <= 1)
7718 return REQ_VIEW_MAIN;
7720 subcommand = argv[1];
7721 if (!strcmp(subcommand, "status")) {
7722 if (argc > 2)
7723 warn("ignoring arguments after `%s'", subcommand);
7724 return REQ_VIEW_STATUS;
7726 } else if (!strcmp(subcommand, "blame")) {
7727 if (argc <= 2 || argc > 4)
7728 die("invalid number of options to blame\n\n%s", usage);
7730 i = 2;
7731 if (argc == 4) {
7732 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7733 i++;
7736 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7737 return REQ_VIEW_BLAME;
7739 } else if (!strcmp(subcommand, "show")) {
7740 request = REQ_VIEW_DIFF;
7742 } else {
7743 subcommand = NULL;
7746 for (i = 1 + !!subcommand; i < argc; i++) {
7747 const char *opt = argv[i];
7749 if (seen_dashdash) {
7750 argv_append(&opt_file_args, opt);
7751 continue;
7753 } else if (!strcmp(opt, "--")) {
7754 seen_dashdash = TRUE;
7755 continue;
7757 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7758 printf("tig version %s\n", TIG_VERSION);
7759 quit(0);
7761 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7762 printf("%s\n", usage);
7763 quit(0);
7765 } else if (!strcmp(opt, "--all")) {
7766 argv_append(&opt_rev_args, opt);
7767 continue;
7770 if (!argv_append(&filter_argv, opt))
7771 die("command too long");
7774 if (filter_argv)
7775 filter_options(filter_argv);
7777 return request;
7781 main(int argc, const char *argv[])
7783 const char *codeset = "UTF-8";
7784 enum request request = parse_options(argc, argv);
7785 struct view *view;
7786 size_t i;
7788 signal(SIGINT, quit);
7789 signal(SIGPIPE, SIG_IGN);
7791 if (setlocale(LC_ALL, "")) {
7792 codeset = nl_langinfo(CODESET);
7795 if (load_repo_info() == ERR)
7796 die("Failed to load repo info.");
7798 if (load_options() == ERR)
7799 die("Failed to load user config.");
7801 if (load_git_config() == ERR)
7802 die("Failed to load repo config.");
7804 /* Require a git repository unless when running in pager mode. */
7805 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7806 die("Not a git repository");
7808 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7809 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7810 if (opt_iconv_in == ICONV_NONE)
7811 die("Failed to initialize character set conversion");
7814 if (codeset && strcmp(codeset, "UTF-8")) {
7815 opt_iconv_out = iconv_open(codeset, "UTF-8");
7816 if (opt_iconv_out == ICONV_NONE)
7817 die("Failed to initialize character set conversion");
7820 if (load_refs() == ERR)
7821 die("Failed to load refs.");
7823 foreach_view (view, i) {
7824 if (getenv(view->cmd_env))
7825 warn("Use of the %s environment variable is deprecated,"
7826 " use options or TIG_DIFF_ARGS instead",
7827 view->cmd_env);
7828 if (!argv_from_env(view->ops->argv, view->cmd_env))
7829 die("Too many arguments in the `%s` environment variable",
7830 view->cmd_env);
7833 init_display();
7835 while (view_driver(display[current_view], request)) {
7836 int key = get_input(0);
7838 view = display[current_view];
7839 request = get_keybinding(view->keymap, key);
7841 /* Some low-level request handling. This keeps access to
7842 * status_win restricted. */
7843 switch (request) {
7844 case REQ_NONE:
7845 report("Unknown key, press %s for help",
7846 get_key(view->keymap, REQ_VIEW_HELP));
7847 break;
7848 case REQ_PROMPT:
7850 char *cmd = read_prompt(":");
7852 if (cmd && isdigit(*cmd)) {
7853 int lineno = view->lineno + 1;
7855 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7856 select_view_line(view, lineno - 1);
7857 report("");
7858 } else {
7859 report("Unable to parse '%s' as a line number", cmd);
7862 } else if (cmd) {
7863 struct view *next = VIEW(REQ_VIEW_PAGER);
7864 const char *argv[SIZEOF_ARG] = { "git" };
7865 int argc = 1;
7867 /* When running random commands, initially show the
7868 * command in the title. However, it maybe later be
7869 * overwritten if a commit line is selected. */
7870 string_ncopy(next->ref, cmd, strlen(cmd));
7872 if (!argv_from_string(argv, &argc, cmd)) {
7873 report("Too many arguments");
7874 } else if (!prepare_update(next, argv, NULL)) {
7875 report("Failed to format command");
7876 } else {
7877 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7881 request = REQ_NONE;
7882 break;
7884 case REQ_SEARCH:
7885 case REQ_SEARCH_BACK:
7887 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7888 char *search = read_prompt(prompt);
7890 if (search)
7891 string_ncopy(opt_search, search, strlen(search));
7892 else if (*opt_search)
7893 request = request == REQ_SEARCH ?
7894 REQ_FIND_NEXT :
7895 REQ_FIND_PREV;
7896 else
7897 request = REQ_NONE;
7898 break;
7900 default:
7901 break;
7905 quit(0);
7907 return 0;