fd19f5403e8351618b37e2e1a97bcc1af9a6906b
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
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.
7 *
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.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
137 };
139 static struct ref *get_ref_head();
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum input_status {
145 INPUT_OK,
146 INPUT_SKIP,
147 INPUT_STOP,
148 INPUT_CANCEL
149 };
151 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
153 static char *prompt_input(const char *prompt, input_handler handler, void *data);
154 static bool prompt_yesno(const char *prompt);
156 struct menu_item {
157 int hotkey;
158 const char *text;
159 void *data;
160 };
162 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
164 /*
165 * Allocation helpers ... Entering macro hell to never be seen again.
166 */
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 static type * \
170 name(type **mem, size_t size, size_t increase) \
171 { \
172 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
173 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174 type *tmp = *mem; \
175 \
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178 if (tmp) \
179 *mem = tmp; \
180 } \
181 \
182 return tmp; \
183 }
185 /*
186 * String helpers
187 */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191 {
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
197 }
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static size_t
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215 {
216 size_t size, pos;
218 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219 if (src[pos] == '\t') {
220 size_t expanded = tabsize - (size % tabsize);
222 if (expanded + size >= dstlen - 1)
223 expanded = dstlen - size - 1;
224 memcpy(dst + size, " ", expanded);
225 size += expanded;
226 } else {
227 dst[size++] = src[pos];
228 }
229 }
231 dst[size] = 0;
232 return pos;
233 }
235 static char *
236 chomp_string(char *name)
237 {
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
248 }
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252 {
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
264 }
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
274 {
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
289 }
291 return 0;
292 }
294 #define enum_equals(entry, str, len) \
295 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
297 struct enum_map {
298 const char *name;
299 int namelen;
300 int value;
301 };
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 static char *
306 enum_map_name(const char *name, size_t namelen)
307 {
308 static char buf[SIZEOF_STR];
309 int bufpos;
311 for (bufpos = 0; bufpos <= namelen; bufpos++) {
312 buf[bufpos] = tolower(name[bufpos]);
313 if (buf[bufpos] == '_')
314 buf[bufpos] = '-';
315 }
317 buf[bufpos] = 0;
318 return buf;
319 }
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 static bool
324 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
325 {
326 size_t namelen = strlen(name);
327 int i;
329 for (i = 0; i < map_size; i++)
330 if (enum_equals(map[i], name, namelen)) {
331 *value = map[i].value;
332 return TRUE;
333 }
335 return FALSE;
336 }
338 #define map_enum(attr, map, name) \
339 map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342 strncmp(str1, str2, STRING_SIZE(str2))
344 static inline int
345 suffixcmp(const char *str, int slen, const char *suffix)
346 {
347 size_t len = slen >= 0 ? slen : strlen(str);
348 size_t suffixlen = strlen(suffix);
350 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
351 }
354 /*
355 * Unicode / UTF-8 handling
356 *
357 * NOTE: Much of the following code for dealing with Unicode is derived from
358 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
360 */
362 static inline int
363 unicode_width(unsigned long c, int tab_size)
364 {
365 if (c >= 0x1100 &&
366 (c <= 0x115f /* Hangul Jamo */
367 || c == 0x2329
368 || c == 0x232a
369 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
370 /* CJK ... Yi */
371 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
372 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
373 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
374 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
375 || (c >= 0xffe0 && c <= 0xffe6)
376 || (c >= 0x20000 && c <= 0x2fffd)
377 || (c >= 0x30000 && c <= 0x3fffd)))
378 return 2;
380 if (c == '\t')
381 return tab_size;
383 return 1;
384 }
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387 * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes[256] = {
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
396 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
397 };
399 static inline unsigned char
400 utf8_char_length(const char *string, const char *end)
401 {
402 int c = *(unsigned char *) string;
404 return utf8_bytes[c];
405 }
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string, size_t length)
410 {
411 unsigned long unicode;
413 switch (length) {
414 case 1:
415 unicode = string[0];
416 break;
417 case 2:
418 unicode = (string[0] & 0x1f) << 6;
419 unicode += (string[1] & 0x3f);
420 break;
421 case 3:
422 unicode = (string[0] & 0x0f) << 12;
423 unicode += ((string[1] & 0x3f) << 6);
424 unicode += (string[2] & 0x3f);
425 break;
426 case 4:
427 unicode = (string[0] & 0x0f) << 18;
428 unicode += ((string[1] & 0x3f) << 12);
429 unicode += ((string[2] & 0x3f) << 6);
430 unicode += (string[3] & 0x3f);
431 break;
432 case 5:
433 unicode = (string[0] & 0x0f) << 24;
434 unicode += ((string[1] & 0x3f) << 18);
435 unicode += ((string[2] & 0x3f) << 12);
436 unicode += ((string[3] & 0x3f) << 6);
437 unicode += (string[4] & 0x3f);
438 break;
439 case 6:
440 unicode = (string[0] & 0x01) << 30;
441 unicode += ((string[1] & 0x3f) << 24);
442 unicode += ((string[2] & 0x3f) << 18);
443 unicode += ((string[3] & 0x3f) << 12);
444 unicode += ((string[4] & 0x3f) << 6);
445 unicode += (string[5] & 0x3f);
446 break;
447 default:
448 return 0;
449 }
451 /* Invalid characters could return the special 0xfffd value but NUL
452 * should be just as good. */
453 return unicode > 0xffff ? 0 : unicode;
454 }
456 /* Calculates how much of string can be shown within the given maximum width
457 * and sets trimmed parameter to non-zero value if all of string could not be
458 * shown. If the reserve flag is TRUE, it will reserve at least one
459 * trailing character, which can be useful when drawing a delimiter.
460 *
461 * Returns the number of bytes to output from string to satisfy max_width. */
462 static size_t
463 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
464 {
465 const char *string = *start;
466 const char *end = strchr(string, '\0');
467 unsigned char last_bytes = 0;
468 size_t last_ucwidth = 0;
470 *width = 0;
471 *trimmed = 0;
473 while (string < end) {
474 unsigned char bytes = utf8_char_length(string, end);
475 size_t ucwidth;
476 unsigned long unicode;
478 if (string + bytes > end)
479 break;
481 /* Change representation to figure out whether
482 * it is a single- or double-width character. */
484 unicode = utf8_to_unicode(string, bytes);
485 /* FIXME: Graceful handling of invalid Unicode character. */
486 if (!unicode)
487 break;
489 ucwidth = unicode_width(unicode, tab_size);
490 if (skip > 0) {
491 skip -= ucwidth <= skip ? ucwidth : skip;
492 *start += bytes;
493 }
494 *width += ucwidth;
495 if (*width > max_width) {
496 *trimmed = 1;
497 *width -= ucwidth;
498 if (reserve && *width == max_width) {
499 string -= last_bytes;
500 *width -= last_ucwidth;
501 }
502 break;
503 }
505 string += bytes;
506 last_bytes = ucwidth ? bytes : 0;
507 last_ucwidth = ucwidth;
508 }
510 return string - *start;
511 }
514 #define DATE_INFO \
515 DATE_(NO), \
516 DATE_(DEFAULT), \
517 DATE_(LOCAL), \
518 DATE_(RELATIVE), \
519 DATE_(SHORT)
521 enum date {
522 #define DATE_(name) DATE_##name
523 DATE_INFO
524 #undef DATE_
525 };
527 static const struct enum_map date_map[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
529 DATE_INFO
530 #undef DATE_
531 };
533 struct time {
534 time_t sec;
535 int tz;
536 };
538 static inline int timecmp(const struct time *t1, const struct time *t2)
539 {
540 return t1->sec - t2->sec;
541 }
543 static const char *
544 mkdate(const struct time *time, enum date date)
545 {
546 static char buf[DATE_COLS + 1];
547 static const struct enum_map reldate[] = {
548 { "second", 1, 60 * 2 },
549 { "minute", 60, 60 * 60 * 2 },
550 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
551 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
552 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
553 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
554 };
555 struct tm tm;
557 if (!date || !time || !time->sec)
558 return "";
560 if (date == DATE_RELATIVE) {
561 struct timeval now;
562 time_t date = time->sec + time->tz;
563 time_t seconds;
564 int i;
566 gettimeofday(&now, NULL);
567 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
568 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
569 if (seconds >= reldate[i].value)
570 continue;
572 seconds /= reldate[i].namelen;
573 if (!string_format(buf, "%ld %s%s %s",
574 seconds, reldate[i].name,
575 seconds > 1 ? "s" : "",
576 now.tv_sec >= date ? "ago" : "ahead"))
577 break;
578 return buf;
579 }
580 }
582 if (date == DATE_LOCAL) {
583 time_t date = time->sec + time->tz;
584 localtime_r(&date, &tm);
585 }
586 else {
587 gmtime_r(&time->sec, &tm);
588 }
589 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
590 }
593 #define AUTHOR_VALUES \
594 AUTHOR_(NO), \
595 AUTHOR_(FULL), \
596 AUTHOR_(ABBREVIATED)
598 enum author {
599 #define AUTHOR_(name) AUTHOR_##name
600 AUTHOR_VALUES,
601 #undef AUTHOR_
602 AUTHOR_DEFAULT = AUTHOR_FULL
603 };
605 static const struct enum_map author_map[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
607 AUTHOR_VALUES
608 #undef AUTHOR_
609 };
611 static const char *
612 get_author_initials(const char *author)
613 {
614 static char initials[AUTHOR_COLS * 6 + 1];
615 size_t pos = 0;
616 const char *end = strchr(author, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620 memset(initials, 0, sizeof(initials));
621 while (author < end) {
622 unsigned char bytes;
623 size_t i;
625 while (is_initial_sep(*author))
626 author++;
628 bytes = utf8_char_length(author, end);
629 if (bytes < sizeof(initials) - 1 - pos) {
630 while (bytes--) {
631 initials[pos++] = *author++;
632 }
633 }
635 for (i = pos; author < end && !is_initial_sep(*author); author++) {
636 if (i < sizeof(initials) - 1)
637 initials[i++] = *author;
638 }
640 initials[i++] = 0;
641 }
643 return initials;
644 }
647 static bool
648 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649 {
650 int valuelen;
652 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
653 bool advance = cmd[valuelen] != 0;
655 cmd[valuelen] = 0;
656 argv[(*argc)++] = chomp_string(cmd);
657 cmd = chomp_string(cmd + valuelen + advance);
658 }
660 if (*argc < SIZEOF_ARG)
661 argv[*argc] = NULL;
662 return *argc < SIZEOF_ARG;
663 }
665 static bool
666 argv_from_env(const char **argv, const char *name)
667 {
668 char *env = argv ? getenv(name) : NULL;
669 int argc = 0;
671 if (env && *env)
672 env = strdup(env);
673 return !env || argv_from_string(argv, &argc, env);
674 }
676 static void
677 argv_free(const char *argv[])
678 {
679 int argc;
681 if (!argv)
682 return;
683 for (argc = 0; argv[argc]; argc++)
684 free((void *) argv[argc]);
685 argv[0] = NULL;
686 }
688 static size_t
689 argv_size(const char **argv)
690 {
691 int argc = 0;
693 while (argv && argv[argc])
694 argc++;
696 return argc;
697 }
699 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
701 static bool
702 argv_append(const char ***argv, const char *arg)
703 {
704 size_t argc = argv_size(*argv);
706 if (!argv_realloc(argv, argc, 2))
707 return FALSE;
709 (*argv)[argc++] = strdup(arg);
710 (*argv)[argc] = NULL;
711 return TRUE;
712 }
714 static bool
715 argv_append_array(const char ***dst_argv, const char *src_argv[])
716 {
717 int i;
719 for (i = 0; src_argv && src_argv[i]; i++)
720 if (!argv_append(dst_argv, src_argv[i]))
721 return FALSE;
722 return TRUE;
723 }
725 static bool
726 argv_copy(const char ***dst, const char *src[])
727 {
728 int argc;
730 for (argc = 0; src[argc]; argc++)
731 if (!argv_append(dst, src[argc]))
732 return FALSE;
733 return TRUE;
734 }
737 /*
738 * Executing external commands.
739 */
741 enum io_type {
742 IO_FD, /* File descriptor based IO. */
743 IO_BG, /* Execute command in the background. */
744 IO_FG, /* Execute command with same std{in,out,err}. */
745 IO_RD, /* Read only fork+exec IO. */
746 IO_WR, /* Write only fork+exec IO. */
747 IO_AP, /* Append fork+exec output to file. */
748 };
750 struct io {
751 int pipe; /* Pipe end for reading or writing. */
752 pid_t pid; /* PID of spawned process. */
753 int error; /* Error status. */
754 char *buf; /* Read buffer. */
755 size_t bufalloc; /* Allocated buffer size. */
756 size_t bufsize; /* Buffer content size. */
757 char *bufpos; /* Current buffer position. */
758 unsigned int eof:1; /* Has end of file been reached. */
759 };
761 static void
762 io_init(struct io *io)
763 {
764 memset(io, 0, sizeof(*io));
765 io->pipe = -1;
766 }
768 static bool
769 io_open(struct io *io, const char *fmt, ...)
770 {
771 char name[SIZEOF_STR] = "";
772 bool fits;
773 va_list args;
775 io_init(io);
777 va_start(args, fmt);
778 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
779 va_end(args);
781 if (!fits) {
782 io->error = ENAMETOOLONG;
783 return FALSE;
784 }
785 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
786 if (io->pipe == -1)
787 io->error = errno;
788 return io->pipe != -1;
789 }
791 static bool
792 io_kill(struct io *io)
793 {
794 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
795 }
797 static bool
798 io_done(struct io *io)
799 {
800 pid_t pid = io->pid;
802 if (io->pipe != -1)
803 close(io->pipe);
804 free(io->buf);
805 io_init(io);
807 while (pid > 0) {
808 int status;
809 pid_t waiting = waitpid(pid, &status, 0);
811 if (waiting < 0) {
812 if (errno == EINTR)
813 continue;
814 io->error = errno;
815 return FALSE;
816 }
818 return waiting == pid &&
819 !WIFSIGNALED(status) &&
820 WIFEXITED(status) &&
821 !WEXITSTATUS(status);
822 }
824 return TRUE;
825 }
827 static bool
828 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
829 {
830 int pipefds[2] = { -1, -1 };
831 va_list args;
833 io_init(io);
835 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
836 io->error = errno;
837 return FALSE;
838 } else if (type == IO_AP) {
839 va_start(args, argv);
840 pipefds[1] = va_arg(args, int);
841 va_end(args);
842 }
844 if ((io->pid = fork())) {
845 if (io->pid == -1)
846 io->error = errno;
847 if (pipefds[!(type == IO_WR)] != -1)
848 close(pipefds[!(type == IO_WR)]);
849 if (io->pid != -1) {
850 io->pipe = pipefds[!!(type == IO_WR)];
851 return TRUE;
852 }
854 } else {
855 if (type != IO_FG) {
856 int devnull = open("/dev/null", O_RDWR);
857 int readfd = type == IO_WR ? pipefds[0] : devnull;
858 int writefd = (type == IO_RD || type == IO_AP)
859 ? pipefds[1] : devnull;
861 dup2(readfd, STDIN_FILENO);
862 dup2(writefd, STDOUT_FILENO);
863 dup2(devnull, STDERR_FILENO);
865 close(devnull);
866 if (pipefds[0] != -1)
867 close(pipefds[0]);
868 if (pipefds[1] != -1)
869 close(pipefds[1]);
870 }
872 if (dir && *dir && chdir(dir) == -1)
873 exit(errno);
875 execvp(argv[0], (char *const*) argv);
876 exit(errno);
877 }
879 if (pipefds[!!(type == IO_WR)] != -1)
880 close(pipefds[!!(type == IO_WR)]);
881 return FALSE;
882 }
884 static bool
885 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
886 {
887 struct io io;
889 return io_run(&io, type, dir, argv, fd) && io_done(&io);
890 }
892 static bool
893 io_run_bg(const char **argv)
894 {
895 return io_complete(IO_BG, argv, NULL, -1);
896 }
898 static bool
899 io_run_fg(const char **argv, const char *dir)
900 {
901 return io_complete(IO_FG, argv, dir, -1);
902 }
904 static bool
905 io_run_append(const char **argv, int fd)
906 {
907 return io_complete(IO_AP, argv, NULL, fd);
908 }
910 static bool
911 io_eof(struct io *io)
912 {
913 return io->eof;
914 }
916 static int
917 io_error(struct io *io)
918 {
919 return io->error;
920 }
922 static char *
923 io_strerror(struct io *io)
924 {
925 return strerror(io->error);
926 }
928 static bool
929 io_can_read(struct io *io)
930 {
931 struct timeval tv = { 0, 500 };
932 fd_set fds;
934 FD_ZERO(&fds);
935 FD_SET(io->pipe, &fds);
937 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
938 }
940 static ssize_t
941 io_read(struct io *io, void *buf, size_t bufsize)
942 {
943 do {
944 ssize_t readsize = read(io->pipe, buf, bufsize);
946 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
947 continue;
948 else if (readsize == -1)
949 io->error = errno;
950 else if (readsize == 0)
951 io->eof = 1;
952 return readsize;
953 } while (1);
954 }
956 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
958 static char *
959 io_get(struct io *io, int c, bool can_read)
960 {
961 char *eol;
962 ssize_t readsize;
964 while (TRUE) {
965 if (io->bufsize > 0) {
966 eol = memchr(io->bufpos, c, io->bufsize);
967 if (eol) {
968 char *line = io->bufpos;
970 *eol = 0;
971 io->bufpos = eol + 1;
972 io->bufsize -= io->bufpos - line;
973 return line;
974 }
975 }
977 if (io_eof(io)) {
978 if (io->bufsize) {
979 io->bufpos[io->bufsize] = 0;
980 io->bufsize = 0;
981 return io->bufpos;
982 }
983 return NULL;
984 }
986 if (!can_read)
987 return NULL;
989 if (io->bufsize > 0 && io->bufpos > io->buf)
990 memmove(io->buf, io->bufpos, io->bufsize);
992 if (io->bufalloc == io->bufsize) {
993 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
994 return NULL;
995 io->bufalloc += BUFSIZ;
996 }
998 io->bufpos = io->buf;
999 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1000 if (io_error(io))
1001 return NULL;
1002 io->bufsize += readsize;
1003 }
1004 }
1006 static bool
1007 io_write(struct io *io, const void *buf, size_t bufsize)
1008 {
1009 size_t written = 0;
1011 while (!io_error(io) && written < bufsize) {
1012 ssize_t size;
1014 size = write(io->pipe, buf + written, bufsize - written);
1015 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1016 continue;
1017 else if (size == -1)
1018 io->error = errno;
1019 else
1020 written += size;
1021 }
1023 return written == bufsize;
1024 }
1026 static bool
1027 io_read_buf(struct io *io, char buf[], size_t bufsize)
1028 {
1029 char *result = io_get(io, '\n', TRUE);
1031 if (result) {
1032 result = chomp_string(result);
1033 string_ncopy_do(buf, bufsize, result, strlen(result));
1034 }
1036 return io_done(io) && result;
1037 }
1039 static bool
1040 io_run_buf(const char **argv, char buf[], size_t bufsize)
1041 {
1042 struct io io;
1044 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1045 }
1047 typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
1049 static int
1050 io_load(struct io *io, const char *separators,
1051 io_read_fn read_property, void *data)
1052 {
1053 char *name;
1054 int state = OK;
1056 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1057 char *value;
1058 size_t namelen;
1059 size_t valuelen;
1061 name = chomp_string(name);
1062 namelen = strcspn(name, separators);
1064 if (name[namelen]) {
1065 name[namelen] = 0;
1066 value = chomp_string(name + namelen + 1);
1067 valuelen = strlen(value);
1069 } else {
1070 value = "";
1071 valuelen = 0;
1072 }
1074 state = read_property(name, namelen, value, valuelen, data);
1075 }
1077 if (state != ERR && io_error(io))
1078 state = ERR;
1079 io_done(io);
1081 return state;
1082 }
1084 static int
1085 io_run_load(const char **argv, const char *separators,
1086 io_read_fn read_property, void *data)
1087 {
1088 struct io io;
1090 if (!io_run(&io, IO_RD, NULL, argv))
1091 return ERR;
1092 return io_load(&io, separators, read_property, data);
1093 }
1096 /*
1097 * User requests
1098 */
1100 #define REQ_INFO \
1101 /* XXX: Keep the view request first and in sync with views[]. */ \
1102 REQ_GROUP("View switching") \
1103 REQ_(VIEW_MAIN, "Show main view"), \
1104 REQ_(VIEW_DIFF, "Show diff view"), \
1105 REQ_(VIEW_LOG, "Show log view"), \
1106 REQ_(VIEW_TREE, "Show tree view"), \
1107 REQ_(VIEW_BLOB, "Show blob view"), \
1108 REQ_(VIEW_BLAME, "Show blame view"), \
1109 REQ_(VIEW_BRANCH, "Show branch view"), \
1110 REQ_(VIEW_HELP, "Show help page"), \
1111 REQ_(VIEW_PAGER, "Show pager view"), \
1112 REQ_(VIEW_STATUS, "Show status view"), \
1113 REQ_(VIEW_STAGE, "Show stage view"), \
1114 \
1115 REQ_GROUP("View manipulation") \
1116 REQ_(ENTER, "Enter current line and scroll"), \
1117 REQ_(NEXT, "Move to next"), \
1118 REQ_(PREVIOUS, "Move to previous"), \
1119 REQ_(PARENT, "Move to parent"), \
1120 REQ_(VIEW_NEXT, "Move focus to next view"), \
1121 REQ_(REFRESH, "Reload and refresh"), \
1122 REQ_(MAXIMIZE, "Maximize the current view"), \
1123 REQ_(VIEW_CLOSE, "Close the current view"), \
1124 REQ_(QUIT, "Close all views and quit"), \
1125 \
1126 REQ_GROUP("View specific requests") \
1127 REQ_(STATUS_UPDATE, "Update file status"), \
1128 REQ_(STATUS_REVERT, "Revert file changes"), \
1129 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1130 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1131 \
1132 REQ_GROUP("Cursor navigation") \
1133 REQ_(MOVE_UP, "Move cursor one line up"), \
1134 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1135 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1136 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1137 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1138 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1139 \
1140 REQ_GROUP("Scrolling") \
1141 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1142 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1143 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1144 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1145 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1146 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1147 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1148 \
1149 REQ_GROUP("Searching") \
1150 REQ_(SEARCH, "Search the view"), \
1151 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1152 REQ_(FIND_NEXT, "Find next search match"), \
1153 REQ_(FIND_PREV, "Find previous search match"), \
1154 \
1155 REQ_GROUP("Option manipulation") \
1156 REQ_(OPTIONS, "Open option menu"), \
1157 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1158 REQ_(TOGGLE_DATE, "Toggle date display"), \
1159 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1160 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1161 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1162 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1163 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1164 \
1165 REQ_GROUP("Misc") \
1166 REQ_(PROMPT, "Bring up the prompt"), \
1167 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1168 REQ_(SHOW_VERSION, "Show version information"), \
1169 REQ_(STOP_LOADING, "Stop all loading views"), \
1170 REQ_(EDIT, "Open in editor"), \
1171 REQ_(NONE, "Do nothing")
1174 /* User action requests. */
1175 enum request {
1176 #define REQ_GROUP(help)
1177 #define REQ_(req, help) REQ_##req
1179 /* Offset all requests to avoid conflicts with ncurses getch values. */
1180 REQ_UNKNOWN = KEY_MAX + 1,
1181 REQ_OFFSET,
1182 REQ_INFO
1184 #undef REQ_GROUP
1185 #undef REQ_
1186 };
1188 struct request_info {
1189 enum request request;
1190 const char *name;
1191 int namelen;
1192 const char *help;
1193 };
1195 static const struct request_info req_info[] = {
1196 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1197 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1198 REQ_INFO
1199 #undef REQ_GROUP
1200 #undef REQ_
1201 };
1203 static enum request
1204 get_request(const char *name)
1205 {
1206 int namelen = strlen(name);
1207 int i;
1209 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1210 if (enum_equals(req_info[i], name, namelen))
1211 return req_info[i].request;
1213 return REQ_UNKNOWN;
1214 }
1217 /*
1218 * Options
1219 */
1221 /* Option and state variables. */
1222 static enum date opt_date = DATE_DEFAULT;
1223 static enum author opt_author = AUTHOR_DEFAULT;
1224 static bool opt_line_number = FALSE;
1225 static bool opt_line_graphics = TRUE;
1226 static bool opt_rev_graph = FALSE;
1227 static bool opt_show_refs = TRUE;
1228 static bool opt_untracked_dirs_content = TRUE;
1229 static int opt_num_interval = 5;
1230 static double opt_hscroll = 0.50;
1231 static double opt_scale_split_view = 2.0 / 3.0;
1232 static int opt_tab_size = 8;
1233 static int opt_author_cols = AUTHOR_COLS;
1234 static char opt_path[SIZEOF_STR] = "";
1235 static char opt_file[SIZEOF_STR] = "";
1236 static char opt_ref[SIZEOF_REF] = "";
1237 static char opt_head[SIZEOF_REF] = "";
1238 static char opt_remote[SIZEOF_REF] = "";
1239 static char opt_encoding[20] = "UTF-8";
1240 static iconv_t opt_iconv_in = ICONV_NONE;
1241 static iconv_t opt_iconv_out = ICONV_NONE;
1242 static char opt_search[SIZEOF_STR] = "";
1243 static char opt_cdup[SIZEOF_STR] = "";
1244 static char opt_prefix[SIZEOF_STR] = "";
1245 static char opt_git_dir[SIZEOF_STR] = "";
1246 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1247 static char opt_editor[SIZEOF_STR] = "";
1248 static FILE *opt_tty = NULL;
1249 static const char **opt_diff_argv = NULL;
1250 static const char **opt_rev_argv = NULL;
1251 static const char **opt_file_argv = NULL;
1253 #define is_initial_commit() (!get_ref_head())
1254 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1257 /*
1258 * Line-oriented content detection.
1259 */
1261 #define LINE_INFO \
1262 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1276 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1277 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1279 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1283 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1284 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1285 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1286 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1287 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1291 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1293 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1294 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1295 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1297 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1298 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1300 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1301 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1307 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1309 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1310 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1311 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1312 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1313 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1314 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1315 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1317 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1318 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1319 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1320 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1322 enum line_type {
1323 #define LINE(type, line, fg, bg, attr) \
1324 LINE_##type
1325 LINE_INFO,
1326 LINE_NONE
1327 #undef LINE
1328 };
1330 struct line_info {
1331 const char *name; /* Option name. */
1332 int namelen; /* Size of option name. */
1333 const char *line; /* The start of line to match. */
1334 int linelen; /* Size of string to match. */
1335 int fg, bg, attr; /* Color and text attributes for the lines. */
1336 };
1338 static struct line_info line_info[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341 LINE_INFO
1342 #undef LINE
1343 };
1345 static enum line_type
1346 get_line_type(const char *line)
1347 {
1348 int linelen = strlen(line);
1349 enum line_type type;
1351 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1352 /* Case insensitive search matches Signed-off-by lines better. */
1353 if (linelen >= line_info[type].linelen &&
1354 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1355 return type;
1357 return LINE_DEFAULT;
1358 }
1360 static inline int
1361 get_line_attr(enum line_type type)
1362 {
1363 assert(type < ARRAY_SIZE(line_info));
1364 return COLOR_PAIR(type) | line_info[type].attr;
1365 }
1367 static struct line_info *
1368 get_line_info(const char *name)
1369 {
1370 size_t namelen = strlen(name);
1371 enum line_type type;
1373 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1374 if (enum_equals(line_info[type], name, namelen))
1375 return &line_info[type];
1377 return NULL;
1378 }
1380 static void
1381 init_colors(void)
1382 {
1383 int default_bg = line_info[LINE_DEFAULT].bg;
1384 int default_fg = line_info[LINE_DEFAULT].fg;
1385 enum line_type type;
1387 start_color();
1389 if (assume_default_colors(default_fg, default_bg) == ERR) {
1390 default_bg = COLOR_BLACK;
1391 default_fg = COLOR_WHITE;
1392 }
1394 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1395 struct line_info *info = &line_info[type];
1396 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1397 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1399 init_pair(type, fg, bg);
1400 }
1401 }
1403 struct line {
1404 enum line_type type;
1406 /* State flags */
1407 unsigned int selected:1;
1408 unsigned int dirty:1;
1409 unsigned int cleareol:1;
1410 unsigned int other:16;
1412 void *data; /* User data */
1413 };
1416 /*
1417 * Keys
1418 */
1420 struct keybinding {
1421 int alias;
1422 enum request request;
1423 };
1425 static struct keybinding default_keybindings[] = {
1426 /* View switching */
1427 { 'm', REQ_VIEW_MAIN },
1428 { 'd', REQ_VIEW_DIFF },
1429 { 'l', REQ_VIEW_LOG },
1430 { 't', REQ_VIEW_TREE },
1431 { 'f', REQ_VIEW_BLOB },
1432 { 'B', REQ_VIEW_BLAME },
1433 { 'H', REQ_VIEW_BRANCH },
1434 { 'p', REQ_VIEW_PAGER },
1435 { 'h', REQ_VIEW_HELP },
1436 { 'S', REQ_VIEW_STATUS },
1437 { 'c', REQ_VIEW_STAGE },
1439 /* View manipulation */
1440 { 'q', REQ_VIEW_CLOSE },
1441 { KEY_TAB, REQ_VIEW_NEXT },
1442 { KEY_RETURN, REQ_ENTER },
1443 { KEY_UP, REQ_PREVIOUS },
1444 { KEY_CTL('P'), REQ_PREVIOUS },
1445 { KEY_DOWN, REQ_NEXT },
1446 { KEY_CTL('N'), REQ_NEXT },
1447 { 'R', REQ_REFRESH },
1448 { KEY_F(5), REQ_REFRESH },
1449 { 'O', REQ_MAXIMIZE },
1451 /* Cursor navigation */
1452 { 'k', REQ_MOVE_UP },
1453 { 'j', REQ_MOVE_DOWN },
1454 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1455 { KEY_END, REQ_MOVE_LAST_LINE },
1456 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1457 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1458 { ' ', REQ_MOVE_PAGE_DOWN },
1459 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1460 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1461 { 'b', REQ_MOVE_PAGE_UP },
1462 { '-', REQ_MOVE_PAGE_UP },
1464 /* Scrolling */
1465 { '|', REQ_SCROLL_FIRST_COL },
1466 { KEY_LEFT, REQ_SCROLL_LEFT },
1467 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1468 { KEY_IC, REQ_SCROLL_LINE_UP },
1469 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1470 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1471 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1472 { 'w', REQ_SCROLL_PAGE_UP },
1473 { 's', REQ_SCROLL_PAGE_DOWN },
1475 /* Searching */
1476 { '/', REQ_SEARCH },
1477 { '?', REQ_SEARCH_BACK },
1478 { 'n', REQ_FIND_NEXT },
1479 { 'N', REQ_FIND_PREV },
1481 /* Misc */
1482 { 'Q', REQ_QUIT },
1483 { 'z', REQ_STOP_LOADING },
1484 { 'v', REQ_SHOW_VERSION },
1485 { 'r', REQ_SCREEN_REDRAW },
1486 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1487 { 'o', REQ_OPTIONS },
1488 { '.', REQ_TOGGLE_LINENO },
1489 { 'D', REQ_TOGGLE_DATE },
1490 { 'A', REQ_TOGGLE_AUTHOR },
1491 { 'g', REQ_TOGGLE_REV_GRAPH },
1492 { 'F', REQ_TOGGLE_REFS },
1493 { 'I', REQ_TOGGLE_SORT_ORDER },
1494 { 'i', REQ_TOGGLE_SORT_FIELD },
1495 { ':', REQ_PROMPT },
1496 { 'u', REQ_STATUS_UPDATE },
1497 { '!', REQ_STATUS_REVERT },
1498 { 'M', REQ_STATUS_MERGE },
1499 { '@', REQ_STAGE_NEXT },
1500 { ',', REQ_PARENT },
1501 { 'e', REQ_EDIT },
1502 };
1504 #define KEYMAP_INFO \
1505 KEYMAP_(GENERIC), \
1506 KEYMAP_(MAIN), \
1507 KEYMAP_(DIFF), \
1508 KEYMAP_(LOG), \
1509 KEYMAP_(TREE), \
1510 KEYMAP_(BLOB), \
1511 KEYMAP_(BLAME), \
1512 KEYMAP_(BRANCH), \
1513 KEYMAP_(PAGER), \
1514 KEYMAP_(HELP), \
1515 KEYMAP_(STATUS), \
1516 KEYMAP_(STAGE)
1518 enum keymap {
1519 #define KEYMAP_(name) KEYMAP_##name
1520 KEYMAP_INFO
1521 #undef KEYMAP_
1522 };
1524 static const struct enum_map keymap_table[] = {
1525 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1526 KEYMAP_INFO
1527 #undef KEYMAP_
1528 };
1530 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1532 struct keybinding_table {
1533 struct keybinding *data;
1534 size_t size;
1535 };
1537 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1539 static void
1540 add_keybinding(enum keymap keymap, enum request request, int key)
1541 {
1542 struct keybinding_table *table = &keybindings[keymap];
1543 size_t i;
1545 for (i = 0; i < keybindings[keymap].size; i++) {
1546 if (keybindings[keymap].data[i].alias == key) {
1547 keybindings[keymap].data[i].request = request;
1548 return;
1549 }
1550 }
1552 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1553 if (!table->data)
1554 die("Failed to allocate keybinding");
1555 table->data[table->size].alias = key;
1556 table->data[table->size++].request = request;
1558 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1559 int i;
1561 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562 if (default_keybindings[i].alias == key)
1563 default_keybindings[i].request = REQ_NONE;
1564 }
1565 }
1567 /* Looks for a key binding first in the given map, then in the generic map, and
1568 * lastly in the default keybindings. */
1569 static enum request
1570 get_keybinding(enum keymap keymap, int key)
1571 {
1572 size_t i;
1574 for (i = 0; i < keybindings[keymap].size; i++)
1575 if (keybindings[keymap].data[i].alias == key)
1576 return keybindings[keymap].data[i].request;
1578 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1579 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1580 return keybindings[KEYMAP_GENERIC].data[i].request;
1582 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1583 if (default_keybindings[i].alias == key)
1584 return default_keybindings[i].request;
1586 return (enum request) key;
1587 }
1590 struct key {
1591 const char *name;
1592 int value;
1593 };
1595 static const struct key key_table[] = {
1596 { "Enter", KEY_RETURN },
1597 { "Space", ' ' },
1598 { "Backspace", KEY_BACKSPACE },
1599 { "Tab", KEY_TAB },
1600 { "Escape", KEY_ESC },
1601 { "Left", KEY_LEFT },
1602 { "Right", KEY_RIGHT },
1603 { "Up", KEY_UP },
1604 { "Down", KEY_DOWN },
1605 { "Insert", KEY_IC },
1606 { "Delete", KEY_DC },
1607 { "Hash", '#' },
1608 { "Home", KEY_HOME },
1609 { "End", KEY_END },
1610 { "PageUp", KEY_PPAGE },
1611 { "PageDown", KEY_NPAGE },
1612 { "F1", KEY_F(1) },
1613 { "F2", KEY_F(2) },
1614 { "F3", KEY_F(3) },
1615 { "F4", KEY_F(4) },
1616 { "F5", KEY_F(5) },
1617 { "F6", KEY_F(6) },
1618 { "F7", KEY_F(7) },
1619 { "F8", KEY_F(8) },
1620 { "F9", KEY_F(9) },
1621 { "F10", KEY_F(10) },
1622 { "F11", KEY_F(11) },
1623 { "F12", KEY_F(12) },
1624 };
1626 static int
1627 get_key_value(const char *name)
1628 {
1629 int i;
1631 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1632 if (!strcasecmp(key_table[i].name, name))
1633 return key_table[i].value;
1635 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1636 return (int)name[1] & 0x1f;
1637 if (strlen(name) == 1 && isprint(*name))
1638 return (int) *name;
1639 return ERR;
1640 }
1642 static const char *
1643 get_key_name(int key_value)
1644 {
1645 static char key_char[] = "'X'\0";
1646 const char *seq = NULL;
1647 int key;
1649 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1650 if (key_table[key].value == key_value)
1651 seq = key_table[key].name;
1653 if (seq == NULL && key_value < 0x7f) {
1654 char *s = key_char + 1;
1656 if (key_value >= 0x20) {
1657 *s++ = key_value;
1658 } else {
1659 *s++ = '^';
1660 *s++ = 0x40 | (key_value & 0x1f);
1661 }
1662 *s++ = '\'';
1663 *s++ = '\0';
1664 seq = key_char;
1665 }
1667 return seq ? seq : "(no key)";
1668 }
1670 static bool
1671 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1672 {
1673 const char *sep = *pos > 0 ? ", " : "";
1674 const char *keyname = get_key_name(keybinding->alias);
1676 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1677 }
1679 static bool
1680 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1681 enum keymap keymap, bool all)
1682 {
1683 int i;
1685 for (i = 0; i < keybindings[keymap].size; i++) {
1686 if (keybindings[keymap].data[i].request == request) {
1687 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1688 return FALSE;
1689 if (!all)
1690 break;
1691 }
1692 }
1694 return TRUE;
1695 }
1697 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1699 static const char *
1700 get_keys(enum keymap keymap, enum request request, bool all)
1701 {
1702 static char buf[BUFSIZ];
1703 size_t pos = 0;
1704 int i;
1706 buf[pos] = 0;
1708 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1709 return "Too many keybindings!";
1710 if (pos > 0 && !all)
1711 return buf;
1713 if (keymap != KEYMAP_GENERIC) {
1714 /* Only the generic keymap includes the default keybindings when
1715 * listing all keys. */
1716 if (all)
1717 return buf;
1719 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1720 return "Too many keybindings!";
1721 if (pos)
1722 return buf;
1723 }
1725 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1726 if (default_keybindings[i].request == request) {
1727 if (!append_key(buf, &pos, &default_keybindings[i]))
1728 return "Too many keybindings!";
1729 if (!all)
1730 return buf;
1731 }
1732 }
1734 return buf;
1735 }
1737 struct run_request {
1738 enum keymap keymap;
1739 int key;
1740 const char **argv;
1741 };
1743 static struct run_request *run_request;
1744 static size_t run_requests;
1746 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1748 static enum request
1749 add_run_request(enum keymap keymap, int key, const char **argv)
1750 {
1751 struct run_request *req;
1753 if (!realloc_run_requests(&run_request, run_requests, 1))
1754 return REQ_NONE;
1756 req = &run_request[run_requests];
1757 req->keymap = keymap;
1758 req->key = key;
1759 req->argv = NULL;
1761 if (!argv_copy(&req->argv, argv))
1762 return REQ_NONE;
1764 return REQ_NONE + ++run_requests;
1765 }
1767 static struct run_request *
1768 get_run_request(enum request request)
1769 {
1770 if (request <= REQ_NONE)
1771 return NULL;
1772 return &run_request[request - REQ_NONE - 1];
1773 }
1775 static void
1776 add_builtin_run_requests(void)
1777 {
1778 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1779 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1780 const char *commit[] = { "git", "commit", NULL };
1781 const char *gc[] = { "git", "gc", NULL };
1782 struct run_request reqs[] = {
1783 { KEYMAP_MAIN, 'C', cherry_pick },
1784 { KEYMAP_STATUS, 'C', commit },
1785 { KEYMAP_BRANCH, 'C', checkout },
1786 { KEYMAP_GENERIC, 'G', gc },
1787 };
1788 int i;
1790 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1791 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1793 if (req != reqs[i].key)
1794 continue;
1795 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1796 if (req != REQ_NONE)
1797 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1798 }
1799 }
1801 /*
1802 * User config file handling.
1803 */
1805 #define OPT_ERR_INFO \
1806 OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
1807 OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
1808 OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
1809 OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
1810 OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
1811 OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
1812 OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
1813 OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
1814 OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
1815 OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
1816 OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
1817 OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
1818 OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
1819 OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
1820 OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
1821 OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
1823 enum option_code {
1824 #define OPT_ERR_(name, msg) OPT_ERR_ ## name
1825 OPT_ERR_INFO
1826 #undef OPT_ERR_
1827 OPT_OK
1828 };
1830 static const char *option_errors[] = {
1831 #define OPT_ERR_(name, msg) msg
1832 OPT_ERR_INFO
1833 #undef OPT_ERR_
1834 };
1836 static const struct enum_map color_map[] = {
1837 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1838 COLOR_MAP(DEFAULT),
1839 COLOR_MAP(BLACK),
1840 COLOR_MAP(BLUE),
1841 COLOR_MAP(CYAN),
1842 COLOR_MAP(GREEN),
1843 COLOR_MAP(MAGENTA),
1844 COLOR_MAP(RED),
1845 COLOR_MAP(WHITE),
1846 COLOR_MAP(YELLOW),
1847 };
1849 static const struct enum_map attr_map[] = {
1850 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1851 ATTR_MAP(NORMAL),
1852 ATTR_MAP(BLINK),
1853 ATTR_MAP(BOLD),
1854 ATTR_MAP(DIM),
1855 ATTR_MAP(REVERSE),
1856 ATTR_MAP(STANDOUT),
1857 ATTR_MAP(UNDERLINE),
1858 };
1860 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1862 static enum option_code
1863 parse_step(double *opt, const char *arg)
1864 {
1865 *opt = atoi(arg);
1866 if (!strchr(arg, '%'))
1867 return OPT_OK;
1869 /* "Shift down" so 100% and 1 does not conflict. */
1870 *opt = (*opt - 1) / 100;
1871 if (*opt >= 1.0) {
1872 *opt = 0.99;
1873 return OPT_ERR_INVALID_STEP_VALUE;
1874 }
1875 if (*opt < 0.0) {
1876 *opt = 1;
1877 return OPT_ERR_INVALID_STEP_VALUE;
1878 }
1879 return OPT_OK;
1880 }
1882 static enum option_code
1883 parse_int(int *opt, const char *arg, int min, int max)
1884 {
1885 int value = atoi(arg);
1887 if (min <= value && value <= max) {
1888 *opt = value;
1889 return OPT_OK;
1890 }
1892 return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
1893 }
1895 static bool
1896 set_color(int *color, const char *name)
1897 {
1898 if (map_enum(color, color_map, name))
1899 return TRUE;
1900 if (!prefixcmp(name, "color"))
1901 return parse_int(color, name + 5, 0, 255) == OK;
1902 return FALSE;
1903 }
1905 /* Wants: object fgcolor bgcolor [attribute] */
1906 static enum option_code
1907 option_color_command(int argc, const char *argv[])
1908 {
1909 struct line_info *info;
1911 if (argc < 3)
1912 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1914 info = get_line_info(argv[0]);
1915 if (!info) {
1916 static const struct enum_map obsolete[] = {
1917 ENUM_MAP("main-delim", LINE_DELIMITER),
1918 ENUM_MAP("main-date", LINE_DATE),
1919 ENUM_MAP("main-author", LINE_AUTHOR),
1920 };
1921 int index;
1923 if (!map_enum(&index, obsolete, argv[0])) {
1924 return OPT_ERR_UNKNOWN_COLOR_NAME;
1925 }
1926 info = &line_info[index];
1927 }
1929 if (!set_color(&info->fg, argv[1]) ||
1930 !set_color(&info->bg, argv[2])) {
1931 return OPT_ERR_UNKNOWN_COLOR;
1932 }
1934 info->attr = 0;
1935 while (argc-- > 3) {
1936 int attr;
1938 if (!set_attribute(&attr, argv[argc])) {
1939 return OPT_ERR_UNKNOWN_ATTRIBUTE;
1940 }
1941 info->attr |= attr;
1942 }
1944 return OPT_OK;
1945 }
1947 static enum option_code
1948 parse_bool(bool *opt, const char *arg)
1949 {
1950 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1951 ? TRUE : FALSE;
1952 return OPT_OK;
1953 }
1955 static enum option_code
1956 parse_enum_do(unsigned int *opt, const char *arg,
1957 const struct enum_map *map, size_t map_size)
1958 {
1959 bool is_true;
1961 assert(map_size > 1);
1963 if (map_enum_do(map, map_size, (int *) opt, arg))
1964 return OPT_OK;
1966 parse_bool(&is_true, arg);
1967 *opt = is_true ? map[1].value : map[0].value;
1968 return OPT_OK;
1969 }
1971 #define parse_enum(opt, arg, map) \
1972 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1974 static enum option_code
1975 parse_string(char *opt, const char *arg, size_t optsize)
1976 {
1977 int arglen = strlen(arg);
1979 switch (arg[0]) {
1980 case '\"':
1981 case '\'':
1982 if (arglen == 1 || arg[arglen - 1] != arg[0])
1983 return OPT_ERR_UNMATCHED_QUOTATION;
1984 arg += 1; arglen -= 2;
1985 default:
1986 string_ncopy_do(opt, optsize, arg, arglen);
1987 return OPT_OK;
1988 }
1989 }
1991 /* Wants: name = value */
1992 static enum option_code
1993 option_set_command(int argc, const char *argv[])
1994 {
1995 if (argc != 3)
1996 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1998 if (strcmp(argv[1], "="))
1999 return OPT_ERR_NO_VALUE_ASSIGNED;
2001 if (!strcmp(argv[0], "show-author"))
2002 return parse_enum(&opt_author, argv[2], author_map);
2004 if (!strcmp(argv[0], "show-date"))
2005 return parse_enum(&opt_date, argv[2], date_map);
2007 if (!strcmp(argv[0], "show-rev-graph"))
2008 return parse_bool(&opt_rev_graph, argv[2]);
2010 if (!strcmp(argv[0], "show-refs"))
2011 return parse_bool(&opt_show_refs, argv[2]);
2013 if (!strcmp(argv[0], "show-line-numbers"))
2014 return parse_bool(&opt_line_number, argv[2]);
2016 if (!strcmp(argv[0], "line-graphics"))
2017 return parse_bool(&opt_line_graphics, argv[2]);
2019 if (!strcmp(argv[0], "line-number-interval"))
2020 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2022 if (!strcmp(argv[0], "author-width"))
2023 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2025 if (!strcmp(argv[0], "horizontal-scroll"))
2026 return parse_step(&opt_hscroll, argv[2]);
2028 if (!strcmp(argv[0], "split-view-height"))
2029 return parse_step(&opt_scale_split_view, argv[2]);
2031 if (!strcmp(argv[0], "tab-size"))
2032 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2034 if (!strcmp(argv[0], "commit-encoding"))
2035 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2037 if (!strcmp(argv[0], "status-untracked-dirs"))
2038 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2040 return OPT_ERR_UNKNOWN_VARIABLE_NAME;
2041 }
2043 /* Wants: mode request key */
2044 static enum option_code
2045 option_bind_command(int argc, const char *argv[])
2046 {
2047 enum request request;
2048 int keymap = -1;
2049 int key;
2051 if (argc < 3)
2052 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
2054 if (!set_keymap(&keymap, argv[0]))
2055 return OPT_ERR_UNKNOWN_KEY_MAP;
2057 key = get_key_value(argv[1]);
2058 if (key == ERR)
2059 return OPT_ERR_UNKNOWN_KEY;
2061 request = get_request(argv[2]);
2062 if (request == REQ_UNKNOWN) {
2063 static const struct enum_map obsolete[] = {
2064 ENUM_MAP("cherry-pick", REQ_NONE),
2065 ENUM_MAP("screen-resize", REQ_NONE),
2066 ENUM_MAP("tree-parent", REQ_PARENT),
2067 };
2068 int alias;
2070 if (map_enum(&alias, obsolete, argv[2])) {
2071 if (alias != REQ_NONE)
2072 add_keybinding(keymap, alias, key);
2073 return OPT_ERR_OBSOLETE_REQUEST_NAME;
2074 }
2075 }
2076 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2077 request = add_run_request(keymap, key, argv + 2);
2078 if (request == REQ_UNKNOWN)
2079 return OPT_ERR_UNKNOWN_REQUEST_NAME;
2081 add_keybinding(keymap, request, key);
2083 return OPT_OK;
2084 }
2086 static enum option_code
2087 set_option(const char *opt, char *value)
2088 {
2089 const char *argv[SIZEOF_ARG];
2090 int argc = 0;
2092 if (!argv_from_string(argv, &argc, value))
2093 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
2095 if (!strcmp(opt, "color"))
2096 return option_color_command(argc, argv);
2098 if (!strcmp(opt, "set"))
2099 return option_set_command(argc, argv);
2101 if (!strcmp(opt, "bind"))
2102 return option_bind_command(argc, argv);
2104 return OPT_ERR_UNKNOWN_OPTION_COMMAND;
2105 }
2107 struct config_state {
2108 int lineno;
2109 bool errors;
2110 };
2112 static int
2113 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2114 {
2115 struct config_state *config = data;
2116 enum option_code status = OPT_ERR_NO_OPTION_VALUE;
2118 config->lineno++;
2120 /* Check for comment markers, since read_properties() will
2121 * only ensure opt and value are split at first " \t". */
2122 optlen = strcspn(opt, "#");
2123 if (optlen == 0)
2124 return OK;
2126 if (opt[optlen] == 0) {
2127 /* Look for comment endings in the value. */
2128 size_t len = strcspn(value, "#");
2130 if (len < valuelen) {
2131 valuelen = len;
2132 value[valuelen] = 0;
2133 }
2135 status = set_option(opt, value);
2136 }
2138 if (status != OPT_OK) {
2139 warn("Error on line %d, near '%.*s': %s",
2140 config->lineno, (int) optlen, opt, option_errors[status]);
2141 config->errors = TRUE;
2142 }
2144 /* Always keep going if errors are encountered. */
2145 return OK;
2146 }
2148 static void
2149 load_option_file(const char *path)
2150 {
2151 struct config_state config = { 0, FALSE };
2152 struct io io;
2154 /* It's OK that the file doesn't exist. */
2155 if (!io_open(&io, "%s", path))
2156 return;
2158 if (io_load(&io, " \t", read_option, &config) == ERR ||
2159 config.errors == TRUE)
2160 warn("Errors while loading %s.", path);
2161 }
2163 static int
2164 load_options(void)
2165 {
2166 const char *home = getenv("HOME");
2167 const char *tigrc_user = getenv("TIGRC_USER");
2168 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2169 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2170 char buf[SIZEOF_STR];
2172 if (!tigrc_system)
2173 tigrc_system = SYSCONFDIR "/tigrc";
2174 load_option_file(tigrc_system);
2176 if (!tigrc_user) {
2177 if (!home || !string_format(buf, "%s/.tigrc", home))
2178 return ERR;
2179 tigrc_user = buf;
2180 }
2181 load_option_file(tigrc_user);
2183 /* Add _after_ loading config files to avoid adding run requests
2184 * that conflict with keybindings. */
2185 add_builtin_run_requests();
2187 if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2188 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2189 int argc = 0;
2191 if (!string_format(buf, "%s", tig_diff_opts) ||
2192 !argv_from_string(diff_opts, &argc, buf))
2193 die("TIG_DIFF_OPTS contains too many arguments");
2194 else if (!argv_copy(&opt_diff_argv, diff_opts))
2195 die("Failed to format TIG_DIFF_OPTS arguments");
2196 }
2198 return OK;
2199 }
2202 /*
2203 * The viewer
2204 */
2206 struct view;
2207 struct view_ops;
2209 /* The display array of active views and the index of the current view. */
2210 static struct view *display[2];
2211 static unsigned int current_view;
2213 #define foreach_displayed_view(view, i) \
2214 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2216 #define displayed_views() (display[1] != NULL ? 2 : 1)
2218 /* Current head and commit ID */
2219 static char ref_blob[SIZEOF_REF] = "";
2220 static char ref_commit[SIZEOF_REF] = "HEAD";
2221 static char ref_head[SIZEOF_REF] = "HEAD";
2222 static char ref_branch[SIZEOF_REF] = "";
2224 enum view_type {
2225 VIEW_MAIN,
2226 VIEW_DIFF,
2227 VIEW_LOG,
2228 VIEW_TREE,
2229 VIEW_BLOB,
2230 VIEW_BLAME,
2231 VIEW_BRANCH,
2232 VIEW_HELP,
2233 VIEW_PAGER,
2234 VIEW_STATUS,
2235 VIEW_STAGE,
2236 };
2238 struct view {
2239 enum view_type type; /* View type */
2240 const char *name; /* View name */
2241 const char *cmd_env; /* Command line set via environment */
2242 const char *id; /* Points to either of ref_{head,commit,blob} */
2244 struct view_ops *ops; /* View operations */
2246 enum keymap keymap; /* What keymap does this view have */
2247 bool git_dir; /* Whether the view requires a git directory. */
2249 char ref[SIZEOF_REF]; /* Hovered commit reference */
2250 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2252 int height, width; /* The width and height of the main window */
2253 WINDOW *win; /* The main window */
2254 WINDOW *title; /* The title window living below the main window */
2256 /* Navigation */
2257 unsigned long offset; /* Offset of the window top */
2258 unsigned long yoffset; /* Offset from the window side. */
2259 unsigned long lineno; /* Current line number */
2260 unsigned long p_offset; /* Previous offset of the window top */
2261 unsigned long p_yoffset;/* Previous offset from the window side */
2262 unsigned long p_lineno; /* Previous current line number */
2263 bool p_restore; /* Should the previous position be restored. */
2265 /* Searching */
2266 char grep[SIZEOF_STR]; /* Search string */
2267 regex_t *regex; /* Pre-compiled regexp */
2269 /* If non-NULL, points to the view that opened this view. If this view
2270 * is closed tig will switch back to the parent view. */
2271 struct view *parent;
2272 struct view *prev;
2274 /* Buffering */
2275 size_t lines; /* Total number of lines */
2276 struct line *line; /* Line index */
2277 unsigned int digits; /* Number of digits in the lines member. */
2279 /* Drawing */
2280 struct line *curline; /* Line currently being drawn. */
2281 enum line_type curtype; /* Attribute currently used for drawing. */
2282 unsigned long col; /* Column when drawing. */
2283 bool has_scrolled; /* View was scrolled. */
2285 /* Loading */
2286 const char **argv; /* Shell command arguments. */
2287 const char *dir; /* Directory from which to execute. */
2288 struct io io;
2289 struct io *pipe;
2290 time_t start_time;
2291 time_t update_secs;
2292 };
2294 struct view_ops {
2295 /* What type of content being displayed. Used in the title bar. */
2296 const char *type;
2297 /* Default command arguments. */
2298 const char **argv;
2299 /* Open and reads in all view content. */
2300 bool (*open)(struct view *view);
2301 /* Read one line; updates view->line. */
2302 bool (*read)(struct view *view, char *data);
2303 /* Draw one line; @lineno must be < view->height. */
2304 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2305 /* Depending on view handle a special requests. */
2306 enum request (*request)(struct view *view, enum request request, struct line *line);
2307 /* Search for regexp in a line. */
2308 bool (*grep)(struct view *view, struct line *line);
2309 /* Select line */
2310 void (*select)(struct view *view, struct line *line);
2311 /* Prepare view for loading */
2312 bool (*prepare)(struct view *view);
2313 };
2315 static struct view_ops blame_ops;
2316 static struct view_ops blob_ops;
2317 static struct view_ops diff_ops;
2318 static struct view_ops help_ops;
2319 static struct view_ops log_ops;
2320 static struct view_ops main_ops;
2321 static struct view_ops pager_ops;
2322 static struct view_ops stage_ops;
2323 static struct view_ops status_ops;
2324 static struct view_ops tree_ops;
2325 static struct view_ops branch_ops;
2327 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2328 { type, name, #env, ref, ops, map, git }
2330 #define VIEW_(id, name, ops, git, ref) \
2331 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2333 static struct view views[] = {
2334 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2335 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2336 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2337 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2338 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2339 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2340 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2341 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2342 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2343 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2344 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2345 };
2347 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2349 #define foreach_view(view, i) \
2350 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2352 #define view_is_displayed(view) \
2353 (view == display[0] || view == display[1])
2355 static enum request
2356 view_request(struct view *view, enum request request)
2357 {
2358 if (!view || !view->lines)
2359 return request;
2360 return view->ops->request(view, request, &view->line[view->lineno]);
2361 }
2364 /*
2365 * View drawing.
2366 */
2368 static inline void
2369 set_view_attr(struct view *view, enum line_type type)
2370 {
2371 if (!view->curline->selected && view->curtype != type) {
2372 (void) wattrset(view->win, get_line_attr(type));
2373 wchgat(view->win, -1, 0, type, NULL);
2374 view->curtype = type;
2375 }
2376 }
2378 static int
2379 draw_chars(struct view *view, enum line_type type, const char *string,
2380 int max_len, bool use_tilde)
2381 {
2382 static char out_buffer[BUFSIZ * 2];
2383 int len = 0;
2384 int col = 0;
2385 int trimmed = FALSE;
2386 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2388 if (max_len <= 0)
2389 return 0;
2391 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2393 set_view_attr(view, type);
2394 if (len > 0) {
2395 if (opt_iconv_out != ICONV_NONE) {
2396 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2397 size_t inlen = len + 1;
2399 char *outbuf = out_buffer;
2400 size_t outlen = sizeof(out_buffer);
2402 size_t ret;
2404 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2405 if (ret != (size_t) -1) {
2406 string = out_buffer;
2407 len = sizeof(out_buffer) - outlen;
2408 }
2409 }
2411 waddnstr(view->win, string, len);
2413 if (trimmed && use_tilde) {
2414 set_view_attr(view, LINE_DELIMITER);
2415 waddch(view->win, '~');
2416 col++;
2417 }
2418 }
2420 return col;
2421 }
2423 static int
2424 draw_space(struct view *view, enum line_type type, int max, int spaces)
2425 {
2426 static char space[] = " ";
2427 int col = 0;
2429 spaces = MIN(max, spaces);
2431 while (spaces > 0) {
2432 int len = MIN(spaces, sizeof(space) - 1);
2434 col += draw_chars(view, type, space, len, FALSE);
2435 spaces -= len;
2436 }
2438 return col;
2439 }
2441 static bool
2442 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2443 {
2444 char text[SIZEOF_STR];
2446 do {
2447 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2449 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2450 string += pos;
2451 } while (*string && view->width + view->yoffset > view->col);
2453 return view->width + view->yoffset <= view->col;
2454 }
2456 static bool
2457 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2458 {
2459 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2460 int max = view->width + view->yoffset - view->col;
2461 int i;
2463 if (max < size)
2464 size = max;
2466 set_view_attr(view, type);
2467 /* Using waddch() instead of waddnstr() ensures that
2468 * they'll be rendered correctly for the cursor line. */
2469 for (i = skip; i < size; i++)
2470 waddch(view->win, graphic[i]);
2472 view->col += size;
2473 if (size < max && skip <= size)
2474 waddch(view->win, ' ');
2475 view->col++;
2477 return view->width + view->yoffset <= view->col;
2478 }
2480 static bool
2481 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2482 {
2483 int max = MIN(view->width + view->yoffset - view->col, len);
2484 int col;
2486 if (text)
2487 col = draw_chars(view, type, text, max - 1, trim);
2488 else
2489 col = draw_space(view, type, max - 1, max - 1);
2491 view->col += col;
2492 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2493 return view->width + view->yoffset <= view->col;
2494 }
2496 static bool
2497 draw_date(struct view *view, struct time *time)
2498 {
2499 const char *date = mkdate(time, opt_date);
2500 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2502 return draw_field(view, LINE_DATE, date, cols, FALSE);
2503 }
2505 static bool
2506 draw_author(struct view *view, const char *author)
2507 {
2508 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2509 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2511 if (abbreviate && author)
2512 author = get_author_initials(author);
2514 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2515 }
2517 static bool
2518 draw_mode(struct view *view, mode_t mode)
2519 {
2520 const char *str;
2522 if (S_ISDIR(mode))
2523 str = "drwxr-xr-x";
2524 else if (S_ISLNK(mode))
2525 str = "lrwxrwxrwx";
2526 else if (S_ISGITLINK(mode))
2527 str = "m---------";
2528 else if (S_ISREG(mode) && mode & S_IXUSR)
2529 str = "-rwxr-xr-x";
2530 else if (S_ISREG(mode))
2531 str = "-rw-r--r--";
2532 else
2533 str = "----------";
2535 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2536 }
2538 static bool
2539 draw_lineno(struct view *view, unsigned int lineno)
2540 {
2541 char number[10];
2542 int digits3 = view->digits < 3 ? 3 : view->digits;
2543 int max = MIN(view->width + view->yoffset - view->col, digits3);
2544 char *text = NULL;
2545 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2547 lineno += view->offset + 1;
2548 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2549 static char fmt[] = "%1ld";
2551 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2552 if (string_format(number, fmt, lineno))
2553 text = number;
2554 }
2555 if (text)
2556 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2557 else
2558 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2559 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2560 }
2562 static bool
2563 draw_view_line(struct view *view, unsigned int lineno)
2564 {
2565 struct line *line;
2566 bool selected = (view->offset + lineno == view->lineno);
2568 assert(view_is_displayed(view));
2570 if (view->offset + lineno >= view->lines)
2571 return FALSE;
2573 line = &view->line[view->offset + lineno];
2575 wmove(view->win, lineno, 0);
2576 if (line->cleareol)
2577 wclrtoeol(view->win);
2578 view->col = 0;
2579 view->curline = line;
2580 view->curtype = LINE_NONE;
2581 line->selected = FALSE;
2582 line->dirty = line->cleareol = 0;
2584 if (selected) {
2585 set_view_attr(view, LINE_CURSOR);
2586 line->selected = TRUE;
2587 view->ops->select(view, line);
2588 }
2590 return view->ops->draw(view, line, lineno);
2591 }
2593 static void
2594 redraw_view_dirty(struct view *view)
2595 {
2596 bool dirty = FALSE;
2597 int lineno;
2599 for (lineno = 0; lineno < view->height; lineno++) {
2600 if (view->offset + lineno >= view->lines)
2601 break;
2602 if (!view->line[view->offset + lineno].dirty)
2603 continue;
2604 dirty = TRUE;
2605 if (!draw_view_line(view, lineno))
2606 break;
2607 }
2609 if (!dirty)
2610 return;
2611 wnoutrefresh(view->win);
2612 }
2614 static void
2615 redraw_view_from(struct view *view, int lineno)
2616 {
2617 assert(0 <= lineno && lineno < view->height);
2619 for (; lineno < view->height; lineno++) {
2620 if (!draw_view_line(view, lineno))
2621 break;
2622 }
2624 wnoutrefresh(view->win);
2625 }
2627 static void
2628 redraw_view(struct view *view)
2629 {
2630 werase(view->win);
2631 redraw_view_from(view, 0);
2632 }
2635 static void
2636 update_view_title(struct view *view)
2637 {
2638 char buf[SIZEOF_STR];
2639 char state[SIZEOF_STR];
2640 size_t bufpos = 0, statelen = 0;
2642 assert(view_is_displayed(view));
2644 if (view->type != VIEW_STATUS && view->lines) {
2645 unsigned int view_lines = view->offset + view->height;
2646 unsigned int lines = view->lines
2647 ? MIN(view_lines, view->lines) * 100 / view->lines
2648 : 0;
2650 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2651 view->ops->type,
2652 view->lineno + 1,
2653 view->lines,
2654 lines);
2656 }
2658 if (view->pipe) {
2659 time_t secs = time(NULL) - view->start_time;
2661 /* Three git seconds are a long time ... */
2662 if (secs > 2)
2663 string_format_from(state, &statelen, " loading %lds", secs);
2664 }
2666 string_format_from(buf, &bufpos, "[%s]", view->name);
2667 if (*view->ref && bufpos < view->width) {
2668 size_t refsize = strlen(view->ref);
2669 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2671 if (minsize < view->width)
2672 refsize = view->width - minsize + 7;
2673 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2674 }
2676 if (statelen && bufpos < view->width) {
2677 string_format_from(buf, &bufpos, "%s", state);
2678 }
2680 if (view == display[current_view])
2681 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2682 else
2683 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2685 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2686 wclrtoeol(view->title);
2687 wnoutrefresh(view->title);
2688 }
2690 static int
2691 apply_step(double step, int value)
2692 {
2693 if (step >= 1)
2694 return (int) step;
2695 value *= step + 0.01;
2696 return value ? value : 1;
2697 }
2699 static void
2700 resize_display(void)
2701 {
2702 int offset, i;
2703 struct view *base = display[0];
2704 struct view *view = display[1] ? display[1] : display[0];
2706 /* Setup window dimensions */
2708 getmaxyx(stdscr, base->height, base->width);
2710 /* Make room for the status window. */
2711 base->height -= 1;
2713 if (view != base) {
2714 /* Horizontal split. */
2715 view->width = base->width;
2716 view->height = apply_step(opt_scale_split_view, base->height);
2717 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2718 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2719 base->height -= view->height;
2721 /* Make room for the title bar. */
2722 view->height -= 1;
2723 }
2725 /* Make room for the title bar. */
2726 base->height -= 1;
2728 offset = 0;
2730 foreach_displayed_view (view, i) {
2731 if (!view->win) {
2732 view->win = newwin(view->height, 0, offset, 0);
2733 if (!view->win)
2734 die("Failed to create %s view", view->name);
2736 scrollok(view->win, FALSE);
2738 view->title = newwin(1, 0, offset + view->height, 0);
2739 if (!view->title)
2740 die("Failed to create title window");
2742 } else {
2743 wresize(view->win, view->height, view->width);
2744 mvwin(view->win, offset, 0);
2745 mvwin(view->title, offset + view->height, 0);
2746 }
2748 offset += view->height + 1;
2749 }
2750 }
2752 static void
2753 redraw_display(bool clear)
2754 {
2755 struct view *view;
2756 int i;
2758 foreach_displayed_view (view, i) {
2759 if (clear)
2760 wclear(view->win);
2761 redraw_view(view);
2762 update_view_title(view);
2763 }
2764 }
2767 /*
2768 * Option management
2769 */
2771 #define TOGGLE_MENU \
2772 TOGGLE_(LINENO, '.', "line numbers", &opt_line_number, NULL) \
2773 TOGGLE_(DATE, 'D', "dates", &opt_date, date_map) \
2774 TOGGLE_(AUTHOR, 'A', "author names", &opt_author, author_map) \
2775 TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \
2776 TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL)
2778 static void
2779 toggle_option(enum request request)
2780 {
2781 const struct {
2782 enum request request;
2783 const struct enum_map *map;
2784 size_t map_size;
2785 } data[] = {
2786 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
2787 TOGGLE_MENU
2788 #undef TOGGLE_
2789 };
2790 const struct menu_item menu[] = {
2791 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2792 TOGGLE_MENU
2793 #undef TOGGLE_
2794 { 0 }
2795 };
2796 int i = 0;
2798 if (request == REQ_OPTIONS) {
2799 if (!prompt_menu("Toggle option", menu, &i))
2800 return;
2801 } else {
2802 while (i < ARRAY_SIZE(data) && data[i].request != request)
2803 i++;
2804 if (i >= ARRAY_SIZE(data))
2805 die("Invalid request (%d)", request);
2806 }
2808 if (data[i].map != NULL) {
2809 unsigned int *opt = menu[i].data;
2811 *opt = (*opt + 1) % data[i].map_size;
2812 redraw_display(FALSE);
2813 report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2815 } else {
2816 bool *option = menu[i].data;
2818 *option = !*option;
2819 redraw_display(FALSE);
2820 report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
2821 }
2822 }
2824 static void
2825 maximize_view(struct view *view)
2826 {
2827 memset(display, 0, sizeof(display));
2828 current_view = 0;
2829 display[current_view] = view;
2830 resize_display();
2831 redraw_display(FALSE);
2832 report("");
2833 }
2836 /*
2837 * Navigation
2838 */
2840 static bool
2841 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2842 {
2843 if (lineno >= view->lines)
2844 lineno = view->lines > 0 ? view->lines - 1 : 0;
2846 if (offset > lineno || offset + view->height <= lineno) {
2847 unsigned long half = view->height / 2;
2849 if (lineno > half)
2850 offset = lineno - half;
2851 else
2852 offset = 0;
2853 }
2855 if (offset != view->offset || lineno != view->lineno) {
2856 view->offset = offset;
2857 view->lineno = lineno;
2858 return TRUE;
2859 }
2861 return FALSE;
2862 }
2864 /* Scrolling backend */
2865 static void
2866 do_scroll_view(struct view *view, int lines)
2867 {
2868 bool redraw_current_line = FALSE;
2870 /* The rendering expects the new offset. */
2871 view->offset += lines;
2873 assert(0 <= view->offset && view->offset < view->lines);
2874 assert(lines);
2876 /* Move current line into the view. */
2877 if (view->lineno < view->offset) {
2878 view->lineno = view->offset;
2879 redraw_current_line = TRUE;
2880 } else if (view->lineno >= view->offset + view->height) {
2881 view->lineno = view->offset + view->height - 1;
2882 redraw_current_line = TRUE;
2883 }
2885 assert(view->offset <= view->lineno && view->lineno < view->lines);
2887 /* Redraw the whole screen if scrolling is pointless. */
2888 if (view->height < ABS(lines)) {
2889 redraw_view(view);
2891 } else {
2892 int line = lines > 0 ? view->height - lines : 0;
2893 int end = line + ABS(lines);
2895 scrollok(view->win, TRUE);
2896 wscrl(view->win, lines);
2897 scrollok(view->win, FALSE);
2899 while (line < end && draw_view_line(view, line))
2900 line++;
2902 if (redraw_current_line)
2903 draw_view_line(view, view->lineno - view->offset);
2904 wnoutrefresh(view->win);
2905 }
2907 view->has_scrolled = TRUE;
2908 report("");
2909 }
2911 /* Scroll frontend */
2912 static void
2913 scroll_view(struct view *view, enum request request)
2914 {
2915 int lines = 1;
2917 assert(view_is_displayed(view));
2919 switch (request) {
2920 case REQ_SCROLL_FIRST_COL:
2921 view->yoffset = 0;
2922 redraw_view_from(view, 0);
2923 report("");
2924 return;
2925 case REQ_SCROLL_LEFT:
2926 if (view->yoffset == 0) {
2927 report("Cannot scroll beyond the first column");
2928 return;
2929 }
2930 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2931 view->yoffset = 0;
2932 else
2933 view->yoffset -= apply_step(opt_hscroll, view->width);
2934 redraw_view_from(view, 0);
2935 report("");
2936 return;
2937 case REQ_SCROLL_RIGHT:
2938 view->yoffset += apply_step(opt_hscroll, view->width);
2939 redraw_view(view);
2940 report("");
2941 return;
2942 case REQ_SCROLL_PAGE_DOWN:
2943 lines = view->height;
2944 case REQ_SCROLL_LINE_DOWN:
2945 if (view->offset + lines > view->lines)
2946 lines = view->lines - view->offset;
2948 if (lines == 0 || view->offset + view->height >= view->lines) {
2949 report("Cannot scroll beyond the last line");
2950 return;
2951 }
2952 break;
2954 case REQ_SCROLL_PAGE_UP:
2955 lines = view->height;
2956 case REQ_SCROLL_LINE_UP:
2957 if (lines > view->offset)
2958 lines = view->offset;
2960 if (lines == 0) {
2961 report("Cannot scroll beyond the first line");
2962 return;
2963 }
2965 lines = -lines;
2966 break;
2968 default:
2969 die("request %d not handled in switch", request);
2970 }
2972 do_scroll_view(view, lines);
2973 }
2975 /* Cursor moving */
2976 static void
2977 move_view(struct view *view, enum request request)
2978 {
2979 int scroll_steps = 0;
2980 int steps;
2982 switch (request) {
2983 case REQ_MOVE_FIRST_LINE:
2984 steps = -view->lineno;
2985 break;
2987 case REQ_MOVE_LAST_LINE:
2988 steps = view->lines - view->lineno - 1;
2989 break;
2991 case REQ_MOVE_PAGE_UP:
2992 steps = view->height > view->lineno
2993 ? -view->lineno : -view->height;
2994 break;
2996 case REQ_MOVE_PAGE_DOWN:
2997 steps = view->lineno + view->height >= view->lines
2998 ? view->lines - view->lineno - 1 : view->height;
2999 break;
3001 case REQ_MOVE_UP:
3002 steps = -1;
3003 break;
3005 case REQ_MOVE_DOWN:
3006 steps = 1;
3007 break;
3009 default:
3010 die("request %d not handled in switch", request);
3011 }
3013 if (steps <= 0 && view->lineno == 0) {
3014 report("Cannot move beyond the first line");
3015 return;
3017 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3018 report("Cannot move beyond the last line");
3019 return;
3020 }
3022 /* Move the current line */
3023 view->lineno += steps;
3024 assert(0 <= view->lineno && view->lineno < view->lines);
3026 /* Check whether the view needs to be scrolled */
3027 if (view->lineno < view->offset ||
3028 view->lineno >= view->offset + view->height) {
3029 scroll_steps = steps;
3030 if (steps < 0 && -steps > view->offset) {
3031 scroll_steps = -view->offset;
3033 } else if (steps > 0) {
3034 if (view->lineno == view->lines - 1 &&
3035 view->lines > view->height) {
3036 scroll_steps = view->lines - view->offset - 1;
3037 if (scroll_steps >= view->height)
3038 scroll_steps -= view->height - 1;
3039 }
3040 }
3041 }
3043 if (!view_is_displayed(view)) {
3044 view->offset += scroll_steps;
3045 assert(0 <= view->offset && view->offset < view->lines);
3046 view->ops->select(view, &view->line[view->lineno]);
3047 return;
3048 }
3050 /* Repaint the old "current" line if we be scrolling */
3051 if (ABS(steps) < view->height)
3052 draw_view_line(view, view->lineno - steps - view->offset);
3054 if (scroll_steps) {
3055 do_scroll_view(view, scroll_steps);
3056 return;
3057 }
3059 /* Draw the current line */
3060 draw_view_line(view, view->lineno - view->offset);
3062 wnoutrefresh(view->win);
3063 report("");
3064 }
3067 /*
3068 * Searching
3069 */
3071 static void search_view(struct view *view, enum request request);
3073 static bool
3074 grep_text(struct view *view, const char *text[])
3075 {
3076 regmatch_t pmatch;
3077 size_t i;
3079 for (i = 0; text[i]; i++)
3080 if (*text[i] &&
3081 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3082 return TRUE;
3083 return FALSE;
3084 }
3086 static void
3087 select_view_line(struct view *view, unsigned long lineno)
3088 {
3089 unsigned long old_lineno = view->lineno;
3090 unsigned long old_offset = view->offset;
3092 if (goto_view_line(view, view->offset, lineno)) {
3093 if (view_is_displayed(view)) {
3094 if (old_offset != view->offset) {
3095 redraw_view(view);
3096 } else {
3097 draw_view_line(view, old_lineno - view->offset);
3098 draw_view_line(view, view->lineno - view->offset);
3099 wnoutrefresh(view->win);
3100 }
3101 } else {
3102 view->ops->select(view, &view->line[view->lineno]);
3103 }
3104 }
3105 }
3107 static void
3108 find_next(struct view *view, enum request request)
3109 {
3110 unsigned long lineno = view->lineno;
3111 int direction;
3113 if (!*view->grep) {
3114 if (!*opt_search)
3115 report("No previous search");
3116 else
3117 search_view(view, request);
3118 return;
3119 }
3121 switch (request) {
3122 case REQ_SEARCH:
3123 case REQ_FIND_NEXT:
3124 direction = 1;
3125 break;
3127 case REQ_SEARCH_BACK:
3128 case REQ_FIND_PREV:
3129 direction = -1;
3130 break;
3132 default:
3133 return;
3134 }
3136 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3137 lineno += direction;
3139 /* Note, lineno is unsigned long so will wrap around in which case it
3140 * will become bigger than view->lines. */
3141 for (; lineno < view->lines; lineno += direction) {
3142 if (view->ops->grep(view, &view->line[lineno])) {
3143 select_view_line(view, lineno);
3144 report("Line %ld matches '%s'", lineno + 1, view->grep);
3145 return;
3146 }
3147 }
3149 report("No match found for '%s'", view->grep);
3150 }
3152 static void
3153 search_view(struct view *view, enum request request)
3154 {
3155 int regex_err;
3157 if (view->regex) {
3158 regfree(view->regex);
3159 *view->grep = 0;
3160 } else {
3161 view->regex = calloc(1, sizeof(*view->regex));
3162 if (!view->regex)
3163 return;
3164 }
3166 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3167 if (regex_err != 0) {
3168 char buf[SIZEOF_STR] = "unknown error";
3170 regerror(regex_err, view->regex, buf, sizeof(buf));
3171 report("Search failed: %s", buf);
3172 return;
3173 }
3175 string_copy(view->grep, opt_search);
3177 find_next(view, request);
3178 }
3180 /*
3181 * Incremental updating
3182 */
3184 static void
3185 reset_view(struct view *view)
3186 {
3187 int i;
3189 for (i = 0; i < view->lines; i++)
3190 free(view->line[i].data);
3191 free(view->line);
3193 view->p_offset = view->offset;
3194 view->p_yoffset = view->yoffset;
3195 view->p_lineno = view->lineno;
3197 view->line = NULL;
3198 view->offset = 0;
3199 view->yoffset = 0;
3200 view->lines = 0;
3201 view->lineno = 0;
3202 view->vid[0] = 0;
3203 view->update_secs = 0;
3204 }
3206 static const char *
3207 format_arg(const char *name)
3208 {
3209 static struct {
3210 const char *name;
3211 size_t namelen;
3212 const char *value;
3213 const char *value_if_empty;
3214 } vars[] = {
3215 #define FORMAT_VAR(name, value, value_if_empty) \
3216 { name, STRING_SIZE(name), value, value_if_empty }
3217 FORMAT_VAR("%(directory)", opt_path, ""),
3218 FORMAT_VAR("%(file)", opt_file, ""),
3219 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3220 FORMAT_VAR("%(head)", ref_head, ""),
3221 FORMAT_VAR("%(commit)", ref_commit, ""),
3222 FORMAT_VAR("%(blob)", ref_blob, ""),
3223 FORMAT_VAR("%(branch)", ref_branch, ""),
3224 };
3225 int i;
3227 for (i = 0; i < ARRAY_SIZE(vars); i++)
3228 if (!strncmp(name, vars[i].name, vars[i].namelen))
3229 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3231 report("Unknown replacement: `%s`", name);
3232 return NULL;
3233 }
3235 static bool
3236 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3237 {
3238 char buf[SIZEOF_STR];
3239 int argc;
3241 argv_free(*dst_argv);
3243 for (argc = 0; src_argv[argc]; argc++) {
3244 const char *arg = src_argv[argc];
3245 size_t bufpos = 0;
3247 if (!strcmp(arg, "%(fileargs)")) {
3248 if (!argv_append_array(dst_argv, opt_file_argv))
3249 break;
3250 continue;
3252 } else if (!strcmp(arg, "%(diffargs)")) {
3253 if (!argv_append_array(dst_argv, opt_diff_argv))
3254 break;
3255 continue;
3257 } else if (!strcmp(arg, "%(revargs)") ||
3258 (first && !strcmp(arg, "%(commit)"))) {
3259 if (!argv_append_array(dst_argv, opt_rev_argv))
3260 break;
3261 continue;
3262 }
3264 while (arg) {
3265 char *next = strstr(arg, "%(");
3266 int len = next - arg;
3267 const char *value;
3269 if (!next || !replace) {
3270 len = strlen(arg);
3271 value = "";
3273 } else {
3274 value = format_arg(next);
3276 if (!value) {
3277 return FALSE;
3278 }
3279 }
3281 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3282 return FALSE;
3284 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3285 }
3287 if (!argv_append(dst_argv, buf))
3288 break;
3289 }
3291 return src_argv[argc] == NULL;
3292 }
3294 static bool
3295 restore_view_position(struct view *view)
3296 {
3297 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3298 return FALSE;
3300 /* Changing the view position cancels the restoring. */
3301 /* FIXME: Changing back to the first line is not detected. */
3302 if (view->offset != 0 || view->lineno != 0) {
3303 view->p_restore = FALSE;
3304 return FALSE;
3305 }
3307 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3308 view_is_displayed(view))
3309 werase(view->win);
3311 view->yoffset = view->p_yoffset;
3312 view->p_restore = FALSE;
3314 return TRUE;
3315 }
3317 static void
3318 end_update(struct view *view, bool force)
3319 {
3320 if (!view->pipe)
3321 return;
3322 while (!view->ops->read(view, NULL))
3323 if (!force)
3324 return;
3325 if (force)
3326 io_kill(view->pipe);
3327 io_done(view->pipe);
3328 view->pipe = NULL;
3329 }
3331 static void
3332 setup_update(struct view *view, const char *vid)
3333 {
3334 reset_view(view);
3335 string_copy_rev(view->vid, vid);
3336 view->pipe = &view->io;
3337 view->start_time = time(NULL);
3338 }
3340 static bool
3341 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3342 {
3343 view->dir = dir;
3344 return format_argv(&view->argv, argv, replace, !view->prev);
3345 }
3347 static bool
3348 prepare_update(struct view *view, const char *argv[], const char *dir)
3349 {
3350 if (view->pipe)
3351 end_update(view, TRUE);
3352 return prepare_io(view, dir, argv, FALSE);
3353 }
3355 static bool
3356 start_update(struct view *view, const char **argv, const char *dir)
3357 {
3358 if (view->pipe)
3359 io_done(view->pipe);
3360 return prepare_io(view, dir, argv, FALSE) &&
3361 io_run(&view->io, IO_RD, dir, view->argv);
3362 }
3364 static bool
3365 prepare_update_file(struct view *view, const char *name)
3366 {
3367 if (view->pipe)
3368 end_update(view, TRUE);
3369 argv_free(view->argv);
3370 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3371 }
3373 static bool
3374 begin_update(struct view *view, bool refresh)
3375 {
3376 if (view->pipe)
3377 end_update(view, TRUE);
3379 if (!refresh) {
3380 if (view->ops->prepare) {
3381 if (!view->ops->prepare(view))
3382 return FALSE;
3383 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3384 return FALSE;
3385 }
3387 /* Put the current ref_* value to the view title ref
3388 * member. This is needed by the blob view. Most other
3389 * views sets it automatically after loading because the
3390 * first line is a commit line. */
3391 string_copy_rev(view->ref, view->id);
3392 }
3394 if (view->argv && view->argv[0] &&
3395 !io_run(&view->io, IO_RD, view->dir, view->argv))
3396 return FALSE;
3398 setup_update(view, view->id);
3400 return TRUE;
3401 }
3403 static bool
3404 update_view(struct view *view)
3405 {
3406 char out_buffer[BUFSIZ * 2];
3407 char *line;
3408 /* Clear the view and redraw everything since the tree sorting
3409 * might have rearranged things. */
3410 bool redraw = view->lines == 0;
3411 bool can_read = TRUE;
3413 if (!view->pipe)
3414 return TRUE;
3416 if (!io_can_read(view->pipe)) {
3417 if (view->lines == 0 && view_is_displayed(view)) {
3418 time_t secs = time(NULL) - view->start_time;
3420 if (secs > 1 && secs > view->update_secs) {
3421 if (view->update_secs == 0)
3422 redraw_view(view);
3423 update_view_title(view);
3424 view->update_secs = secs;
3425 }
3426 }
3427 return TRUE;
3428 }
3430 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3431 if (opt_iconv_in != ICONV_NONE) {
3432 ICONV_CONST char *inbuf = line;
3433 size_t inlen = strlen(line) + 1;
3435 char *outbuf = out_buffer;
3436 size_t outlen = sizeof(out_buffer);
3438 size_t ret;
3440 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3441 if (ret != (size_t) -1)
3442 line = out_buffer;
3443 }
3445 if (!view->ops->read(view, line)) {
3446 report("Allocation failure");
3447 end_update(view, TRUE);
3448 return FALSE;
3449 }
3450 }
3452 {
3453 unsigned long lines = view->lines;
3454 int digits;
3456 for (digits = 0; lines; digits++)
3457 lines /= 10;
3459 /* Keep the displayed view in sync with line number scaling. */
3460 if (digits != view->digits) {
3461 view->digits = digits;
3462 if (opt_line_number || view->type == VIEW_BLAME)
3463 redraw = TRUE;
3464 }
3465 }
3467 if (io_error(view->pipe)) {
3468 report("Failed to read: %s", io_strerror(view->pipe));
3469 end_update(view, TRUE);
3471 } else if (io_eof(view->pipe)) {
3472 if (view_is_displayed(view))
3473 report("");
3474 end_update(view, FALSE);
3475 }
3477 if (restore_view_position(view))
3478 redraw = TRUE;
3480 if (!view_is_displayed(view))
3481 return TRUE;
3483 if (redraw)
3484 redraw_view_from(view, 0);
3485 else
3486 redraw_view_dirty(view);
3488 /* Update the title _after_ the redraw so that if the redraw picks up a
3489 * commit reference in view->ref it'll be available here. */
3490 update_view_title(view);
3491 return TRUE;
3492 }
3494 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3496 static struct line *
3497 add_line_data(struct view *view, void *data, enum line_type type)
3498 {
3499 struct line *line;
3501 if (!realloc_lines(&view->line, view->lines, 1))
3502 return NULL;
3504 line = &view->line[view->lines++];
3505 memset(line, 0, sizeof(*line));
3506 line->type = type;
3507 line->data = data;
3508 line->dirty = 1;
3510 return line;
3511 }
3513 static struct line *
3514 add_line_text(struct view *view, const char *text, enum line_type type)
3515 {
3516 char *data = text ? strdup(text) : NULL;
3518 return data ? add_line_data(view, data, type) : NULL;
3519 }
3521 static struct line *
3522 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3523 {
3524 char buf[SIZEOF_STR];
3525 va_list args;
3527 va_start(args, fmt);
3528 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3529 buf[0] = 0;
3530 va_end(args);
3532 return buf[0] ? add_line_text(view, buf, type) : NULL;
3533 }
3535 /*
3536 * View opening
3537 */
3539 enum open_flags {
3540 OPEN_DEFAULT = 0, /* Use default view switching. */
3541 OPEN_SPLIT = 1, /* Split current view. */
3542 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3543 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3544 OPEN_PREPARED = 32, /* Open already prepared command. */
3545 };
3547 static void
3548 open_view(struct view *prev, enum request request, enum open_flags flags)
3549 {
3550 bool split = !!(flags & OPEN_SPLIT);
3551 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3552 bool nomaximize = !!(flags & OPEN_REFRESH);
3553 struct view *view = VIEW(request);
3554 int nviews = displayed_views();
3555 struct view *base_view = display[0];
3557 if (view == prev && nviews == 1 && !reload) {
3558 report("Already in %s view", view->name);
3559 return;
3560 }
3562 if (view->git_dir && !opt_git_dir[0]) {
3563 report("The %s view is disabled in pager view", view->name);
3564 return;
3565 }
3567 if (split) {
3568 display[1] = view;
3569 current_view = 1;
3570 view->parent = prev;
3571 } else if (!nomaximize) {
3572 /* Maximize the current view. */
3573 memset(display, 0, sizeof(display));
3574 current_view = 0;
3575 display[current_view] = view;
3576 }
3578 /* No prev signals that this is the first loaded view. */
3579 if (prev && view != prev) {
3580 view->prev = prev;
3581 }
3583 /* Resize the view when switching between split- and full-screen,
3584 * or when switching between two different full-screen views. */
3585 if (nviews != displayed_views() ||
3586 (nviews == 1 && base_view != display[0]))
3587 resize_display();
3589 if (view->ops->open) {
3590 if (view->pipe)
3591 end_update(view, TRUE);
3592 if (!view->ops->open(view)) {
3593 report("Failed to load %s view", view->name);
3594 return;
3595 }
3596 restore_view_position(view);
3598 } else if ((reload || strcmp(view->vid, view->id)) &&
3599 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3600 report("Failed to load %s view", view->name);
3601 return;
3602 }
3604 if (split && prev->lineno - prev->offset >= prev->height) {
3605 /* Take the title line into account. */
3606 int lines = prev->lineno - prev->offset - prev->height + 1;
3608 /* Scroll the view that was split if the current line is
3609 * outside the new limited view. */
3610 do_scroll_view(prev, lines);
3611 }
3613 if (prev && view != prev && split && view_is_displayed(prev)) {
3614 /* "Blur" the previous view. */
3615 update_view_title(prev);
3616 }
3618 if (view->pipe && view->lines == 0) {
3619 /* Clear the old view and let the incremental updating refill
3620 * the screen. */
3621 werase(view->win);
3622 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3623 report("");
3624 } else if (view_is_displayed(view)) {
3625 redraw_view(view);
3626 report("");
3627 }
3628 }
3630 static void
3631 open_external_viewer(const char *argv[], const char *dir)
3632 {
3633 def_prog_mode(); /* save current tty modes */
3634 endwin(); /* restore original tty modes */
3635 io_run_fg(argv, dir);
3636 fprintf(stderr, "Press Enter to continue");
3637 getc(opt_tty);
3638 reset_prog_mode();
3639 redraw_display(TRUE);
3640 }
3642 static void
3643 open_mergetool(const char *file)
3644 {
3645 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3647 open_external_viewer(mergetool_argv, opt_cdup);
3648 }
3650 static void
3651 open_editor(const char *file)
3652 {
3653 const char *editor_argv[] = { "vi", file, NULL };
3654 const char *editor;
3656 editor = getenv("GIT_EDITOR");
3657 if (!editor && *opt_editor)
3658 editor = opt_editor;
3659 if (!editor)
3660 editor = getenv("VISUAL");
3661 if (!editor)
3662 editor = getenv("EDITOR");
3663 if (!editor)
3664 editor = "vi";
3666 editor_argv[0] = editor;
3667 open_external_viewer(editor_argv, opt_cdup);
3668 }
3670 static void
3671 open_run_request(enum request request)
3672 {
3673 struct run_request *req = get_run_request(request);
3674 const char **argv = NULL;
3676 if (!req) {
3677 report("Unknown run request");
3678 return;
3679 }
3681 if (format_argv(&argv, req->argv, TRUE, FALSE))
3682 open_external_viewer(argv, NULL);
3683 if (argv)
3684 argv_free(argv);
3685 free(argv);
3686 }
3688 /*
3689 * User request switch noodle
3690 */
3692 static int
3693 view_driver(struct view *view, enum request request)
3694 {
3695 int i;
3697 if (request == REQ_NONE)
3698 return TRUE;
3700 if (request > REQ_NONE) {
3701 open_run_request(request);
3702 view_request(view, REQ_REFRESH);
3703 return TRUE;
3704 }
3706 request = view_request(view, request);
3707 if (request == REQ_NONE)
3708 return TRUE;
3710 switch (request) {
3711 case REQ_MOVE_UP:
3712 case REQ_MOVE_DOWN:
3713 case REQ_MOVE_PAGE_UP:
3714 case REQ_MOVE_PAGE_DOWN:
3715 case REQ_MOVE_FIRST_LINE:
3716 case REQ_MOVE_LAST_LINE:
3717 move_view(view, request);
3718 break;
3720 case REQ_SCROLL_FIRST_COL:
3721 case REQ_SCROLL_LEFT:
3722 case REQ_SCROLL_RIGHT:
3723 case REQ_SCROLL_LINE_DOWN:
3724 case REQ_SCROLL_LINE_UP:
3725 case REQ_SCROLL_PAGE_DOWN:
3726 case REQ_SCROLL_PAGE_UP:
3727 scroll_view(view, request);
3728 break;
3730 case REQ_VIEW_BLAME:
3731 if (!opt_file[0]) {
3732 report("No file chosen, press %s to open tree view",
3733 get_key(view->keymap, REQ_VIEW_TREE));
3734 break;
3735 }
3736 open_view(view, request, OPEN_DEFAULT);
3737 break;
3739 case REQ_VIEW_BLOB:
3740 if (!ref_blob[0]) {
3741 report("No file chosen, press %s to open tree view",
3742 get_key(view->keymap, REQ_VIEW_TREE));
3743 break;
3744 }
3745 open_view(view, request, OPEN_DEFAULT);
3746 break;
3748 case REQ_VIEW_PAGER:
3749 if (view == NULL) {
3750 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3751 die("Failed to open stdin");
3752 open_view(view, request, OPEN_PREPARED);
3753 break;
3754 }
3756 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3757 report("No pager content, press %s to run command from prompt",
3758 get_key(view->keymap, REQ_PROMPT));
3759 break;
3760 }
3761 open_view(view, request, OPEN_DEFAULT);
3762 break;
3764 case REQ_VIEW_STAGE:
3765 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3766 report("No stage content, press %s to open the status view and choose file",
3767 get_key(view->keymap, REQ_VIEW_STATUS));
3768 break;
3769 }
3770 open_view(view, request, OPEN_DEFAULT);
3771 break;
3773 case REQ_VIEW_STATUS:
3774 if (opt_is_inside_work_tree == FALSE) {
3775 report("The status view requires a working tree");
3776 break;
3777 }
3778 open_view(view, request, OPEN_DEFAULT);
3779 break;
3781 case REQ_VIEW_MAIN:
3782 case REQ_VIEW_DIFF:
3783 case REQ_VIEW_LOG:
3784 case REQ_VIEW_TREE:
3785 case REQ_VIEW_HELP:
3786 case REQ_VIEW_BRANCH:
3787 open_view(view, request, OPEN_DEFAULT);
3788 break;
3790 case REQ_NEXT:
3791 case REQ_PREVIOUS:
3792 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3794 if (view->parent) {
3795 int line;
3797 view = view->parent;
3798 line = view->lineno;
3799 move_view(view, request);
3800 if (view_is_displayed(view))
3801 update_view_title(view);
3802 if (line != view->lineno)
3803 view_request(view, REQ_ENTER);
3804 } else {
3805 move_view(view, request);
3806 }
3807 break;
3809 case REQ_VIEW_NEXT:
3810 {
3811 int nviews = displayed_views();
3812 int next_view = (current_view + 1) % nviews;
3814 if (next_view == current_view) {
3815 report("Only one view is displayed");
3816 break;
3817 }
3819 current_view = next_view;
3820 /* Blur out the title of the previous view. */
3821 update_view_title(view);
3822 report("");
3823 break;
3824 }
3825 case REQ_REFRESH:
3826 report("Refreshing is not yet supported for the %s view", view->name);
3827 break;
3829 case REQ_MAXIMIZE:
3830 if (displayed_views() == 2)
3831 maximize_view(view);
3832 break;
3834 case REQ_OPTIONS:
3835 case REQ_TOGGLE_LINENO:
3836 case REQ_TOGGLE_DATE:
3837 case REQ_TOGGLE_AUTHOR:
3838 case REQ_TOGGLE_REV_GRAPH:
3839 case REQ_TOGGLE_REFS:
3840 toggle_option(request);
3841 break;
3843 case REQ_TOGGLE_SORT_FIELD:
3844 case REQ_TOGGLE_SORT_ORDER:
3845 report("Sorting is not yet supported for the %s view", view->name);
3846 break;
3848 case REQ_SEARCH:
3849 case REQ_SEARCH_BACK:
3850 search_view(view, request);
3851 break;
3853 case REQ_FIND_NEXT:
3854 case REQ_FIND_PREV:
3855 find_next(view, request);
3856 break;
3858 case REQ_STOP_LOADING:
3859 foreach_view(view, i) {
3860 if (view->pipe)
3861 report("Stopped loading the %s view", view->name),
3862 end_update(view, TRUE);
3863 }
3864 break;
3866 case REQ_SHOW_VERSION:
3867 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3868 return TRUE;
3870 case REQ_SCREEN_REDRAW:
3871 redraw_display(TRUE);
3872 break;
3874 case REQ_EDIT:
3875 report("Nothing to edit");
3876 break;
3878 case REQ_ENTER:
3879 report("Nothing to enter");
3880 break;
3882 case REQ_VIEW_CLOSE:
3883 /* XXX: Mark closed views by letting view->prev point to the
3884 * view itself. Parents to closed view should never be
3885 * followed. */
3886 if (view->prev && view->prev != view) {
3887 maximize_view(view->prev);
3888 view->prev = view;
3889 break;
3890 }
3891 /* Fall-through */
3892 case REQ_QUIT:
3893 return FALSE;
3895 default:
3896 report("Unknown key, press %s for help",
3897 get_key(view->keymap, REQ_VIEW_HELP));
3898 return TRUE;
3899 }
3901 return TRUE;
3902 }
3905 /*
3906 * View backend utilities
3907 */
3909 enum sort_field {
3910 ORDERBY_NAME,
3911 ORDERBY_DATE,
3912 ORDERBY_AUTHOR,
3913 };
3915 struct sort_state {
3916 const enum sort_field *fields;
3917 size_t size, current;
3918 bool reverse;
3919 };
3921 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3922 #define get_sort_field(state) ((state).fields[(state).current])
3923 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3925 static void
3926 sort_view(struct view *view, enum request request, struct sort_state *state,
3927 int (*compare)(const void *, const void *))
3928 {
3929 switch (request) {
3930 case REQ_TOGGLE_SORT_FIELD:
3931 state->current = (state->current + 1) % state->size;
3932 break;
3934 case REQ_TOGGLE_SORT_ORDER:
3935 state->reverse = !state->reverse;
3936 break;
3937 default:
3938 die("Not a sort request");
3939 }
3941 qsort(view->line, view->lines, sizeof(*view->line), compare);
3942 redraw_view(view);
3943 }
3945 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3947 /* Small author cache to reduce memory consumption. It uses binary
3948 * search to lookup or find place to position new entries. No entries
3949 * are ever freed. */
3950 static const char *
3951 get_author(const char *name)
3952 {
3953 static const char **authors;
3954 static size_t authors_size;
3955 int from = 0, to = authors_size - 1;
3957 while (from <= to) {
3958 size_t pos = (to + from) / 2;
3959 int cmp = strcmp(name, authors[pos]);
3961 if (!cmp)
3962 return authors[pos];
3964 if (cmp < 0)
3965 to = pos - 1;
3966 else
3967 from = pos + 1;
3968 }
3970 if (!realloc_authors(&authors, authors_size, 1))
3971 return NULL;
3972 name = strdup(name);
3973 if (!name)
3974 return NULL;
3976 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3977 authors[from] = name;
3978 authors_size++;
3980 return name;
3981 }
3983 static void
3984 parse_timesec(struct time *time, const char *sec)
3985 {
3986 time->sec = (time_t) atol(sec);
3987 }
3989 static void
3990 parse_timezone(struct time *time, const char *zone)
3991 {
3992 long tz;
3994 tz = ('0' - zone[1]) * 60 * 60 * 10;
3995 tz += ('0' - zone[2]) * 60 * 60;
3996 tz += ('0' - zone[3]) * 60 * 10;
3997 tz += ('0' - zone[4]) * 60;
3999 if (zone[0] == '-')
4000 tz = -tz;
4002 time->tz = tz;
4003 time->sec -= tz;
4004 }
4006 /* Parse author lines where the name may be empty:
4007 * author <email@address.tld> 1138474660 +0100
4008 */
4009 static void
4010 parse_author_line(char *ident, const char **author, struct time *time)
4011 {
4012 char *nameend = strchr(ident, '<');
4013 char *emailend = strchr(ident, '>');
4015 if (nameend && emailend)
4016 *nameend = *emailend = 0;
4017 ident = chomp_string(ident);
4018 if (!*ident) {
4019 if (nameend)
4020 ident = chomp_string(nameend + 1);
4021 if (!*ident)
4022 ident = "Unknown";
4023 }
4025 *author = get_author(ident);
4027 /* Parse epoch and timezone */
4028 if (emailend && emailend[1] == ' ') {
4029 char *secs = emailend + 2;
4030 char *zone = strchr(secs, ' ');
4032 parse_timesec(time, secs);
4034 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4035 parse_timezone(time, zone + 1);
4036 }
4037 }
4039 /*
4040 * Pager backend
4041 */
4043 static bool
4044 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4045 {
4046 if (opt_line_number && draw_lineno(view, lineno))
4047 return TRUE;
4049 draw_text(view, line->type, line->data, TRUE);
4050 return TRUE;
4051 }
4053 static bool
4054 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4055 {
4056 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4057 char ref[SIZEOF_STR];
4059 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4060 return TRUE;
4062 /* This is the only fatal call, since it can "corrupt" the buffer. */
4063 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4064 return FALSE;
4066 return TRUE;
4067 }
4069 static void
4070 add_pager_refs(struct view *view, struct line *line)
4071 {
4072 char buf[SIZEOF_STR];
4073 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4074 struct ref_list *list;
4075 size_t bufpos = 0, i;
4076 const char *sep = "Refs: ";
4077 bool is_tag = FALSE;
4079 assert(line->type == LINE_COMMIT);
4081 list = get_ref_list(commit_id);
4082 if (!list) {
4083 if (view->type == VIEW_DIFF)
4084 goto try_add_describe_ref;
4085 return;
4086 }
4088 for (i = 0; i < list->size; i++) {
4089 struct ref *ref = list->refs[i];
4090 const char *fmt = ref->tag ? "%s[%s]" :
4091 ref->remote ? "%s<%s>" : "%s%s";
4093 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4094 return;
4095 sep = ", ";
4096 if (ref->tag)
4097 is_tag = TRUE;
4098 }
4100 if (!is_tag && view->type == VIEW_DIFF) {
4101 try_add_describe_ref:
4102 /* Add <tag>-g<commit_id> "fake" reference. */
4103 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4104 return;
4105 }
4107 if (bufpos == 0)
4108 return;
4110 add_line_text(view, buf, LINE_PP_REFS);
4111 }
4113 static bool
4114 pager_read(struct view *view, char *data)
4115 {
4116 struct line *line;
4118 if (!data)
4119 return TRUE;
4121 line = add_line_text(view, data, get_line_type(data));
4122 if (!line)
4123 return FALSE;
4125 if (line->type == LINE_COMMIT &&
4126 (view->type == VIEW_DIFF ||
4127 view->type == VIEW_LOG))
4128 add_pager_refs(view, line);
4130 return TRUE;
4131 }
4133 static enum request
4134 pager_request(struct view *view, enum request request, struct line *line)
4135 {
4136 int split = 0;
4138 if (request != REQ_ENTER)
4139 return request;
4141 if (line->type == LINE_COMMIT &&
4142 (view->type == VIEW_LOG ||
4143 view->type == VIEW_PAGER)) {
4144 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4145 split = 1;
4146 }
4148 /* Always scroll the view even if it was split. That way
4149 * you can use Enter to scroll through the log view and
4150 * split open each commit diff. */
4151 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4153 /* FIXME: A minor workaround. Scrolling the view will call report("")
4154 * but if we are scrolling a non-current view this won't properly
4155 * update the view title. */
4156 if (split)
4157 update_view_title(view);
4159 return REQ_NONE;
4160 }
4162 static bool
4163 pager_grep(struct view *view, struct line *line)
4164 {
4165 const char *text[] = { line->data, NULL };
4167 return grep_text(view, text);
4168 }
4170 static void
4171 pager_select(struct view *view, struct line *line)
4172 {
4173 if (line->type == LINE_COMMIT) {
4174 char *text = (char *)line->data + STRING_SIZE("commit ");
4176 if (view->type != VIEW_PAGER)
4177 string_copy_rev(view->ref, text);
4178 string_copy_rev(ref_commit, text);
4179 }
4180 }
4182 static struct view_ops pager_ops = {
4183 "line",
4184 NULL,
4185 NULL,
4186 pager_read,
4187 pager_draw,
4188 pager_request,
4189 pager_grep,
4190 pager_select,
4191 };
4193 static const char *log_argv[SIZEOF_ARG] = {
4194 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4195 };
4197 static enum request
4198 log_request(struct view *view, enum request request, struct line *line)
4199 {
4200 switch (request) {
4201 case REQ_REFRESH:
4202 load_refs();
4203 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4204 return REQ_NONE;
4205 default:
4206 return pager_request(view, request, line);
4207 }
4208 }
4210 static struct view_ops log_ops = {
4211 "line",
4212 log_argv,
4213 NULL,
4214 pager_read,
4215 pager_draw,
4216 log_request,
4217 pager_grep,
4218 pager_select,
4219 };
4221 static const char *diff_argv[SIZEOF_ARG] = {
4222 "git", "show", "--pretty=fuller", "--no-color", "--root",
4223 "--patch-with-stat", "--find-copies-harder", "-C",
4224 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4225 };
4227 static bool
4228 diff_read(struct view *view, char *data)
4229 {
4230 if (!data) {
4231 /* Fall back to retry if no diff will be shown. */
4232 if (view->lines == 0 && opt_file_argv) {
4233 int pos = argv_size(view->argv)
4234 - argv_size(opt_file_argv) - 1;
4236 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4237 for (; view->argv[pos]; pos++) {
4238 free((void *) view->argv[pos]);
4239 view->argv[pos] = NULL;
4240 }
4242 if (view->pipe)
4243 io_done(view->pipe);
4244 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4245 return FALSE;
4246 }
4247 }
4248 return TRUE;
4249 }
4251 return pager_read(view, data);
4252 }
4254 static struct view_ops diff_ops = {
4255 "line",
4256 diff_argv,
4257 NULL,
4258 diff_read,
4259 pager_draw,
4260 pager_request,
4261 pager_grep,
4262 pager_select,
4263 };
4265 /*
4266 * Help backend
4267 */
4269 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4271 static bool
4272 help_open_keymap_title(struct view *view, enum keymap keymap)
4273 {
4274 struct line *line;
4276 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4277 help_keymap_hidden[keymap] ? '+' : '-',
4278 enum_name(keymap_table[keymap]));
4279 if (line)
4280 line->other = keymap;
4282 return help_keymap_hidden[keymap];
4283 }
4285 static void
4286 help_open_keymap(struct view *view, enum keymap keymap)
4287 {
4288 const char *group = NULL;
4289 char buf[SIZEOF_STR];
4290 size_t bufpos;
4291 bool add_title = TRUE;
4292 int i;
4294 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4295 const char *key = NULL;
4297 if (req_info[i].request == REQ_NONE)
4298 continue;
4300 if (!req_info[i].request) {
4301 group = req_info[i].help;
4302 continue;
4303 }
4305 key = get_keys(keymap, req_info[i].request, TRUE);
4306 if (!key || !*key)
4307 continue;
4309 if (add_title && help_open_keymap_title(view, keymap))
4310 return;
4311 add_title = FALSE;
4313 if (group) {
4314 add_line_text(view, group, LINE_HELP_GROUP);
4315 group = NULL;
4316 }
4318 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4319 enum_name(req_info[i]), req_info[i].help);
4320 }
4322 group = "External commands:";
4324 for (i = 0; i < run_requests; i++) {
4325 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4326 const char *key;
4327 int argc;
4329 if (!req || req->keymap != keymap)
4330 continue;
4332 key = get_key_name(req->key);
4333 if (!*key)
4334 key = "(no key defined)";
4336 if (add_title && help_open_keymap_title(view, keymap))
4337 return;
4338 if (group) {
4339 add_line_text(view, group, LINE_HELP_GROUP);
4340 group = NULL;
4341 }
4343 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4344 if (!string_format_from(buf, &bufpos, "%s%s",
4345 argc ? " " : "", req->argv[argc]))
4346 return;
4348 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4349 }
4350 }
4352 static bool
4353 help_open(struct view *view)
4354 {
4355 enum keymap keymap;
4357 reset_view(view);
4358 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4359 add_line_text(view, "", LINE_DEFAULT);
4361 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4362 help_open_keymap(view, keymap);
4364 return TRUE;
4365 }
4367 static enum request
4368 help_request(struct view *view, enum request request, struct line *line)
4369 {
4370 switch (request) {
4371 case REQ_ENTER:
4372 if (line->type == LINE_HELP_KEYMAP) {
4373 help_keymap_hidden[line->other] =
4374 !help_keymap_hidden[line->other];
4375 view->p_restore = TRUE;
4376 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4377 }
4379 return REQ_NONE;
4380 default:
4381 return pager_request(view, request, line);
4382 }
4383 }
4385 static struct view_ops help_ops = {
4386 "line",
4387 NULL,
4388 help_open,
4389 NULL,
4390 pager_draw,
4391 help_request,
4392 pager_grep,
4393 pager_select,
4394 };
4397 /*
4398 * Tree backend
4399 */
4401 struct tree_stack_entry {
4402 struct tree_stack_entry *prev; /* Entry below this in the stack */
4403 unsigned long lineno; /* Line number to restore */
4404 char *name; /* Position of name in opt_path */
4405 };
4407 /* The top of the path stack. */
4408 static struct tree_stack_entry *tree_stack = NULL;
4409 unsigned long tree_lineno = 0;
4411 static void
4412 pop_tree_stack_entry(void)
4413 {
4414 struct tree_stack_entry *entry = tree_stack;
4416 tree_lineno = entry->lineno;
4417 entry->name[0] = 0;
4418 tree_stack = entry->prev;
4419 free(entry);
4420 }
4422 static void
4423 push_tree_stack_entry(const char *name, unsigned long lineno)
4424 {
4425 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4426 size_t pathlen = strlen(opt_path);
4428 if (!entry)
4429 return;
4431 entry->prev = tree_stack;
4432 entry->name = opt_path + pathlen;
4433 tree_stack = entry;
4435 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4436 pop_tree_stack_entry();
4437 return;
4438 }
4440 /* Move the current line to the first tree entry. */
4441 tree_lineno = 1;
4442 entry->lineno = lineno;
4443 }
4445 /* Parse output from git-ls-tree(1):
4446 *
4447 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4448 */
4450 #define SIZEOF_TREE_ATTR \
4451 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4453 #define SIZEOF_TREE_MODE \
4454 STRING_SIZE("100644 ")
4456 #define TREE_ID_OFFSET \
4457 STRING_SIZE("100644 blob ")
4459 struct tree_entry {
4460 char id[SIZEOF_REV];
4461 mode_t mode;
4462 struct time time; /* Date from the author ident. */
4463 const char *author; /* Author of the commit. */
4464 char name[1];
4465 };
4467 static const char *
4468 tree_path(const struct line *line)
4469 {
4470 return ((struct tree_entry *) line->data)->name;
4471 }
4473 static int
4474 tree_compare_entry(const struct line *line1, const struct line *line2)
4475 {
4476 if (line1->type != line2->type)
4477 return line1->type == LINE_TREE_DIR ? -1 : 1;
4478 return strcmp(tree_path(line1), tree_path(line2));
4479 }
4481 static const enum sort_field tree_sort_fields[] = {
4482 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4483 };
4484 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4486 static int
4487 tree_compare(const void *l1, const void *l2)
4488 {
4489 const struct line *line1 = (const struct line *) l1;
4490 const struct line *line2 = (const struct line *) l2;
4491 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4492 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4494 if (line1->type == LINE_TREE_HEAD)
4495 return -1;
4496 if (line2->type == LINE_TREE_HEAD)
4497 return 1;
4499 switch (get_sort_field(tree_sort_state)) {
4500 case ORDERBY_DATE:
4501 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4503 case ORDERBY_AUTHOR:
4504 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4506 case ORDERBY_NAME:
4507 default:
4508 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4509 }
4510 }
4513 static struct line *
4514 tree_entry(struct view *view, enum line_type type, const char *path,
4515 const char *mode, const char *id)
4516 {
4517 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4518 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4520 if (!entry || !line) {
4521 free(entry);
4522 return NULL;
4523 }
4525 strncpy(entry->name, path, strlen(path));
4526 if (mode)
4527 entry->mode = strtoul(mode, NULL, 8);
4528 if (id)
4529 string_copy_rev(entry->id, id);
4531 return line;
4532 }
4534 static bool
4535 tree_read_date(struct view *view, char *text, bool *read_date)
4536 {
4537 static const char *author_name;
4538 static struct time author_time;
4540 if (!text && *read_date) {
4541 *read_date = FALSE;
4542 return TRUE;
4544 } else if (!text) {
4545 char *path = *opt_path ? opt_path : ".";
4546 /* Find next entry to process */
4547 const char *log_file[] = {
4548 "git", "log", "--no-color", "--pretty=raw",
4549 "--cc", "--raw", view->id, "--", path, NULL
4550 };
4552 if (!view->lines) {
4553 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4554 report("Tree is empty");
4555 return TRUE;
4556 }
4558 if (!start_update(view, log_file, opt_cdup)) {
4559 report("Failed to load tree data");
4560 return TRUE;
4561 }
4563 *read_date = TRUE;
4564 return FALSE;
4566 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4567 parse_author_line(text + STRING_SIZE("author "),
4568 &author_name, &author_time);
4570 } else if (*text == ':') {
4571 char *pos;
4572 size_t annotated = 1;
4573 size_t i;
4575 pos = strchr(text, '\t');
4576 if (!pos)
4577 return TRUE;
4578 text = pos + 1;
4579 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4580 text += strlen(opt_path);
4581 pos = strchr(text, '/');
4582 if (pos)
4583 *pos = 0;
4585 for (i = 1; i < view->lines; i++) {
4586 struct line *line = &view->line[i];
4587 struct tree_entry *entry = line->data;
4589 annotated += !!entry->author;
4590 if (entry->author || strcmp(entry->name, text))
4591 continue;
4593 entry->author = author_name;
4594 entry->time = author_time;
4595 line->dirty = 1;
4596 break;
4597 }
4599 if (annotated == view->lines)
4600 io_kill(view->pipe);
4601 }
4602 return TRUE;
4603 }
4605 static bool
4606 tree_read(struct view *view, char *text)
4607 {
4608 static bool read_date = FALSE;
4609 struct tree_entry *data;
4610 struct line *entry, *line;
4611 enum line_type type;
4612 size_t textlen = text ? strlen(text) : 0;
4613 char *path = text + SIZEOF_TREE_ATTR;
4615 if (read_date || !text)
4616 return tree_read_date(view, text, &read_date);
4618 if (textlen <= SIZEOF_TREE_ATTR)
4619 return FALSE;
4620 if (view->lines == 0 &&
4621 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4622 return FALSE;
4624 /* Strip the path part ... */
4625 if (*opt_path) {
4626 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4627 size_t striplen = strlen(opt_path);
4629 if (pathlen > striplen)
4630 memmove(path, path + striplen,
4631 pathlen - striplen + 1);
4633 /* Insert "link" to parent directory. */
4634 if (view->lines == 1 &&
4635 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4636 return FALSE;
4637 }
4639 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4640 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4641 if (!entry)
4642 return FALSE;
4643 data = entry->data;
4645 /* Skip "Directory ..." and ".." line. */
4646 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4647 if (tree_compare_entry(line, entry) <= 0)
4648 continue;
4650 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4652 line->data = data;
4653 line->type = type;
4654 for (; line <= entry; line++)
4655 line->dirty = line->cleareol = 1;
4656 return TRUE;
4657 }
4659 if (tree_lineno > view->lineno) {
4660 view->lineno = tree_lineno;
4661 tree_lineno = 0;
4662 }
4664 return TRUE;
4665 }
4667 static bool
4668 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4669 {
4670 struct tree_entry *entry = line->data;
4672 if (line->type == LINE_TREE_HEAD) {
4673 if (draw_text(view, line->type, "Directory path /", TRUE))
4674 return TRUE;
4675 } else {
4676 if (draw_mode(view, entry->mode))
4677 return TRUE;
4679 if (opt_author && draw_author(view, entry->author))
4680 return TRUE;
4682 if (opt_date && draw_date(view, &entry->time))
4683 return TRUE;
4684 }
4685 if (draw_text(view, line->type, entry->name, TRUE))
4686 return TRUE;
4687 return TRUE;
4688 }
4690 static void
4691 open_blob_editor(const char *id)
4692 {
4693 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4694 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4695 int fd = mkstemp(file);
4697 if (fd == -1)
4698 report("Failed to create temporary file");
4699 else if (!io_run_append(blob_argv, fd))
4700 report("Failed to save blob data to file");
4701 else
4702 open_editor(file);
4703 if (fd != -1)
4704 unlink(file);
4705 }
4707 static enum request
4708 tree_request(struct view *view, enum request request, struct line *line)
4709 {
4710 enum open_flags flags;
4711 struct tree_entry *entry = line->data;
4713 switch (request) {
4714 case REQ_VIEW_BLAME:
4715 if (line->type != LINE_TREE_FILE) {
4716 report("Blame only supported for files");
4717 return REQ_NONE;
4718 }
4720 string_copy(opt_ref, view->vid);
4721 return request;
4723 case REQ_EDIT:
4724 if (line->type != LINE_TREE_FILE) {
4725 report("Edit only supported for files");
4726 } else if (!is_head_commit(view->vid)) {
4727 open_blob_editor(entry->id);
4728 } else {
4729 open_editor(opt_file);
4730 }
4731 return REQ_NONE;
4733 case REQ_TOGGLE_SORT_FIELD:
4734 case REQ_TOGGLE_SORT_ORDER:
4735 sort_view(view, request, &tree_sort_state, tree_compare);
4736 return REQ_NONE;
4738 case REQ_PARENT:
4739 if (!*opt_path) {
4740 /* quit view if at top of tree */
4741 return REQ_VIEW_CLOSE;
4742 }
4743 /* fake 'cd ..' */
4744 line = &view->line[1];
4745 break;
4747 case REQ_ENTER:
4748 break;
4750 default:
4751 return request;
4752 }
4754 /* Cleanup the stack if the tree view is at a different tree. */
4755 while (!*opt_path && tree_stack)
4756 pop_tree_stack_entry();
4758 switch (line->type) {
4759 case LINE_TREE_DIR:
4760 /* Depending on whether it is a subdirectory or parent link
4761 * mangle the path buffer. */
4762 if (line == &view->line[1] && *opt_path) {
4763 pop_tree_stack_entry();
4765 } else {
4766 const char *basename = tree_path(line);
4768 push_tree_stack_entry(basename, view->lineno);
4769 }
4771 /* Trees and subtrees share the same ID, so they are not not
4772 * unique like blobs. */
4773 flags = OPEN_RELOAD;
4774 request = REQ_VIEW_TREE;
4775 break;
4777 case LINE_TREE_FILE:
4778 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4779 request = REQ_VIEW_BLOB;
4780 break;
4782 default:
4783 return REQ_NONE;
4784 }
4786 open_view(view, request, flags);
4787 if (request == REQ_VIEW_TREE)
4788 view->lineno = tree_lineno;
4790 return REQ_NONE;
4791 }
4793 static bool
4794 tree_grep(struct view *view, struct line *line)
4795 {
4796 struct tree_entry *entry = line->data;
4797 const char *text[] = {
4798 entry->name,
4799 opt_author ? entry->author : "",
4800 mkdate(&entry->time, opt_date),
4801 NULL
4802 };
4804 return grep_text(view, text);
4805 }
4807 static void
4808 tree_select(struct view *view, struct line *line)
4809 {
4810 struct tree_entry *entry = line->data;
4812 if (line->type == LINE_TREE_FILE) {
4813 string_copy_rev(ref_blob, entry->id);
4814 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4816 } else if (line->type != LINE_TREE_DIR) {
4817 return;
4818 }
4820 string_copy_rev(view->ref, entry->id);
4821 }
4823 static bool
4824 tree_prepare(struct view *view)
4825 {
4826 if (view->lines == 0 && opt_prefix[0]) {
4827 char *pos = opt_prefix;
4829 while (pos && *pos) {
4830 char *end = strchr(pos, '/');
4832 if (end)
4833 *end = 0;
4834 push_tree_stack_entry(pos, 0);
4835 pos = end;
4836 if (end) {
4837 *end = '/';
4838 pos++;
4839 }
4840 }
4842 } else if (strcmp(view->vid, view->id)) {
4843 opt_path[0] = 0;
4844 }
4846 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4847 }
4849 static const char *tree_argv[SIZEOF_ARG] = {
4850 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4851 };
4853 static struct view_ops tree_ops = {
4854 "file",
4855 tree_argv,
4856 NULL,
4857 tree_read,
4858 tree_draw,
4859 tree_request,
4860 tree_grep,
4861 tree_select,
4862 tree_prepare,
4863 };
4865 static bool
4866 blob_read(struct view *view, char *line)
4867 {
4868 if (!line)
4869 return TRUE;
4870 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4871 }
4873 static enum request
4874 blob_request(struct view *view, enum request request, struct line *line)
4875 {
4876 switch (request) {
4877 case REQ_EDIT:
4878 open_blob_editor(view->vid);
4879 return REQ_NONE;
4880 default:
4881 return pager_request(view, request, line);
4882 }
4883 }
4885 static const char *blob_argv[SIZEOF_ARG] = {
4886 "git", "cat-file", "blob", "%(blob)", NULL
4887 };
4889 static struct view_ops blob_ops = {
4890 "line",
4891 blob_argv,
4892 NULL,
4893 blob_read,
4894 pager_draw,
4895 blob_request,
4896 pager_grep,
4897 pager_select,
4898 };
4900 /*
4901 * Blame backend
4902 *
4903 * Loading the blame view is a two phase job:
4904 *
4905 * 1. File content is read either using opt_file from the
4906 * filesystem or using git-cat-file.
4907 * 2. Then blame information is incrementally added by
4908 * reading output from git-blame.
4909 */
4911 struct blame_commit {
4912 char id[SIZEOF_REV]; /* SHA1 ID. */
4913 char title[128]; /* First line of the commit message. */
4914 const char *author; /* Author of the commit. */
4915 struct time time; /* Date from the author ident. */
4916 char filename[128]; /* Name of file. */
4917 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4918 char parent_filename[128]; /* Parent/previous name of file. */
4919 };
4921 struct blame {
4922 struct blame_commit *commit;
4923 unsigned long lineno;
4924 char text[1];
4925 };
4927 static bool
4928 blame_open(struct view *view)
4929 {
4930 char path[SIZEOF_STR];
4931 size_t i;
4933 if (!view->prev && *opt_prefix) {
4934 string_copy(path, opt_file);
4935 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4936 return FALSE;
4937 }
4939 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4940 const char *blame_cat_file_argv[] = {
4941 "git", "cat-file", "blob", path, NULL
4942 };
4944 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4945 !start_update(view, blame_cat_file_argv, opt_cdup))
4946 return FALSE;
4947 }
4949 /* First pass: remove multiple references to the same commit. */
4950 for (i = 0; i < view->lines; i++) {
4951 struct blame *blame = view->line[i].data;
4953 if (blame->commit && blame->commit->id[0])
4954 blame->commit->id[0] = 0;
4955 else
4956 blame->commit = NULL;
4957 }
4959 /* Second pass: free existing references. */
4960 for (i = 0; i < view->lines; i++) {
4961 struct blame *blame = view->line[i].data;
4963 if (blame->commit)
4964 free(blame->commit);
4965 }
4967 setup_update(view, opt_file);
4968 string_format(view->ref, "%s ...", opt_file);
4970 return TRUE;
4971 }
4973 static struct blame_commit *
4974 get_blame_commit(struct view *view, const char *id)
4975 {
4976 size_t i;
4978 for (i = 0; i < view->lines; i++) {
4979 struct blame *blame = view->line[i].data;
4981 if (!blame->commit)
4982 continue;
4984 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4985 return blame->commit;
4986 }
4988 {
4989 struct blame_commit *commit = calloc(1, sizeof(*commit));
4991 if (commit)
4992 string_ncopy(commit->id, id, SIZEOF_REV);
4993 return commit;
4994 }
4995 }
4997 static bool
4998 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4999 {
5000 const char *pos = *posref;
5002 *posref = NULL;
5003 pos = strchr(pos + 1, ' ');
5004 if (!pos || !isdigit(pos[1]))
5005 return FALSE;
5006 *number = atoi(pos + 1);
5007 if (*number < min || *number > max)
5008 return FALSE;
5010 *posref = pos;
5011 return TRUE;
5012 }
5014 static struct blame_commit *
5015 parse_blame_commit(struct view *view, const char *text, int *blamed)
5016 {
5017 struct blame_commit *commit;
5018 struct blame *blame;
5019 const char *pos = text + SIZEOF_REV - 2;
5020 size_t orig_lineno = 0;
5021 size_t lineno;
5022 size_t group;
5024 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5025 return NULL;
5027 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5028 !parse_number(&pos, &lineno, 1, view->lines) ||
5029 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5030 return NULL;
5032 commit = get_blame_commit(view, text);
5033 if (!commit)
5034 return NULL;
5036 *blamed += group;
5037 while (group--) {
5038 struct line *line = &view->line[lineno + group - 1];
5040 blame = line->data;
5041 blame->commit = commit;
5042 blame->lineno = orig_lineno + group - 1;
5043 line->dirty = 1;
5044 }
5046 return commit;
5047 }
5049 static bool
5050 blame_read_file(struct view *view, const char *line, bool *read_file)
5051 {
5052 if (!line) {
5053 const char *blame_argv[] = {
5054 "git", "blame", "--incremental",
5055 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5056 };
5058 if (view->lines == 0 && !view->prev)
5059 die("No blame exist for %s", view->vid);
5061 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5062 report("Failed to load blame data");
5063 return TRUE;
5064 }
5066 *read_file = FALSE;
5067 return FALSE;
5069 } else {
5070 size_t linelen = strlen(line);
5071 struct blame *blame = malloc(sizeof(*blame) + linelen);
5073 if (!blame)
5074 return FALSE;
5076 blame->commit = NULL;
5077 strncpy(blame->text, line, linelen);
5078 blame->text[linelen] = 0;
5079 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5080 }
5081 }
5083 static bool
5084 match_blame_header(const char *name, char **line)
5085 {
5086 size_t namelen = strlen(name);
5087 bool matched = !strncmp(name, *line, namelen);
5089 if (matched)
5090 *line += namelen;
5092 return matched;
5093 }
5095 static bool
5096 blame_read(struct view *view, char *line)
5097 {
5098 static struct blame_commit *commit = NULL;
5099 static int blamed = 0;
5100 static bool read_file = TRUE;
5102 if (read_file)
5103 return blame_read_file(view, line, &read_file);
5105 if (!line) {
5106 /* Reset all! */
5107 commit = NULL;
5108 blamed = 0;
5109 read_file = TRUE;
5110 string_format(view->ref, "%s", view->vid);
5111 if (view_is_displayed(view)) {
5112 update_view_title(view);
5113 redraw_view_from(view, 0);
5114 }
5115 return TRUE;
5116 }
5118 if (!commit) {
5119 commit = parse_blame_commit(view, line, &blamed);
5120 string_format(view->ref, "%s %2d%%", view->vid,
5121 view->lines ? blamed * 100 / view->lines : 0);
5123 } else if (match_blame_header("author ", &line)) {
5124 commit->author = get_author(line);
5126 } else if (match_blame_header("author-time ", &line)) {
5127 parse_timesec(&commit->time, line);
5129 } else if (match_blame_header("author-tz ", &line)) {
5130 parse_timezone(&commit->time, line);
5132 } else if (match_blame_header("summary ", &line)) {
5133 string_ncopy(commit->title, line, strlen(line));
5135 } else if (match_blame_header("previous ", &line)) {
5136 if (strlen(line) <= SIZEOF_REV)
5137 return FALSE;
5138 string_copy_rev(commit->parent_id, line);
5139 line += SIZEOF_REV;
5140 string_ncopy(commit->parent_filename, line, strlen(line));
5142 } else if (match_blame_header("filename ", &line)) {
5143 string_ncopy(commit->filename, line, strlen(line));
5144 commit = NULL;
5145 }
5147 return TRUE;
5148 }
5150 static bool
5151 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5152 {
5153 struct blame *blame = line->data;
5154 struct time *time = NULL;
5155 const char *id = NULL, *author = NULL;
5157 if (blame->commit && *blame->commit->filename) {
5158 id = blame->commit->id;
5159 author = blame->commit->author;
5160 time = &blame->commit->time;
5161 }
5163 if (opt_date && draw_date(view, time))
5164 return TRUE;
5166 if (opt_author && draw_author(view, author))
5167 return TRUE;
5169 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5170 return TRUE;
5172 if (draw_lineno(view, lineno))
5173 return TRUE;
5175 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5176 return TRUE;
5177 }
5179 static bool
5180 check_blame_commit(struct blame *blame, bool check_null_id)
5181 {
5182 if (!blame->commit)
5183 report("Commit data not loaded yet");
5184 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5185 report("No commit exist for the selected line");
5186 else
5187 return TRUE;
5188 return FALSE;
5189 }
5191 static void
5192 setup_blame_parent_line(struct view *view, struct blame *blame)
5193 {
5194 char from[SIZEOF_REF + SIZEOF_STR];
5195 char to[SIZEOF_REF + SIZEOF_STR];
5196 const char *diff_tree_argv[] = {
5197 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5198 "-U0", from, to, "--", NULL
5199 };
5200 struct io io;
5201 int parent_lineno = -1;
5202 int blamed_lineno = -1;
5203 char *line;
5205 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5206 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5207 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5208 return;
5210 while ((line = io_get(&io, '\n', TRUE))) {
5211 if (*line == '@') {
5212 char *pos = strchr(line, '+');
5214 parent_lineno = atoi(line + 4);
5215 if (pos)
5216 blamed_lineno = atoi(pos + 1);
5218 } else if (*line == '+' && parent_lineno != -1) {
5219 if (blame->lineno == blamed_lineno - 1 &&
5220 !strcmp(blame->text, line + 1)) {
5221 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5222 break;
5223 }
5224 blamed_lineno++;
5225 }
5226 }
5228 io_done(&io);
5229 }
5231 static enum request
5232 blame_request(struct view *view, enum request request, struct line *line)
5233 {
5234 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5235 struct blame *blame = line->data;
5237 switch (request) {
5238 case REQ_VIEW_BLAME:
5239 if (check_blame_commit(blame, TRUE)) {
5240 string_copy(opt_ref, blame->commit->id);
5241 string_copy(opt_file, blame->commit->filename);
5242 if (blame->lineno)
5243 view->lineno = blame->lineno;
5244 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5245 }
5246 break;
5248 case REQ_PARENT:
5249 if (!check_blame_commit(blame, TRUE))
5250 break;
5251 if (!*blame->commit->parent_id) {
5252 report("The selected commit has no parents");
5253 } else {
5254 string_copy_rev(opt_ref, blame->commit->parent_id);
5255 string_copy(opt_file, blame->commit->parent_filename);
5256 setup_blame_parent_line(view, blame);
5257 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5258 }
5259 break;
5261 case REQ_ENTER:
5262 if (!check_blame_commit(blame, FALSE))
5263 break;
5265 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5266 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5267 break;
5269 if (!strcmp(blame->commit->id, NULL_ID)) {
5270 struct view *diff = VIEW(REQ_VIEW_DIFF);
5271 const char *diff_index_argv[] = {
5272 "git", "diff-index", "--root", "--patch-with-stat",
5273 "-C", "-M", "HEAD", "--", view->vid, NULL
5274 };
5276 if (!*blame->commit->parent_id) {
5277 diff_index_argv[1] = "diff";
5278 diff_index_argv[2] = "--no-color";
5279 diff_index_argv[6] = "--";
5280 diff_index_argv[7] = "/dev/null";
5281 }
5283 if (!prepare_update(diff, diff_index_argv, NULL)) {
5284 report("Failed to allocate diff command");
5285 break;
5286 }
5287 flags |= OPEN_PREPARED;
5288 }
5290 open_view(view, REQ_VIEW_DIFF, flags);
5291 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5292 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5293 break;
5295 default:
5296 return request;
5297 }
5299 return REQ_NONE;
5300 }
5302 static bool
5303 blame_grep(struct view *view, struct line *line)
5304 {
5305 struct blame *blame = line->data;
5306 struct blame_commit *commit = blame->commit;
5307 const char *text[] = {
5308 blame->text,
5309 commit ? commit->title : "",
5310 commit ? commit->id : "",
5311 commit && opt_author ? commit->author : "",
5312 commit ? mkdate(&commit->time, opt_date) : "",
5313 NULL
5314 };
5316 return grep_text(view, text);
5317 }
5319 static void
5320 blame_select(struct view *view, struct line *line)
5321 {
5322 struct blame *blame = line->data;
5323 struct blame_commit *commit = blame->commit;
5325 if (!commit)
5326 return;
5328 if (!strcmp(commit->id, NULL_ID))
5329 string_ncopy(ref_commit, "HEAD", 4);
5330 else
5331 string_copy_rev(ref_commit, commit->id);
5332 }
5334 static struct view_ops blame_ops = {
5335 "line",
5336 NULL,
5337 blame_open,
5338 blame_read,
5339 blame_draw,
5340 blame_request,
5341 blame_grep,
5342 blame_select,
5343 };
5345 /*
5346 * Branch backend
5347 */
5349 struct branch {
5350 const char *author; /* Author of the last commit. */
5351 struct time time; /* Date of the last activity. */
5352 const struct ref *ref; /* Name and commit ID information. */
5353 };
5355 static const struct ref branch_all;
5357 static const enum sort_field branch_sort_fields[] = {
5358 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5359 };
5360 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5362 static int
5363 branch_compare(const void *l1, const void *l2)
5364 {
5365 const struct branch *branch1 = ((const struct line *) l1)->data;
5366 const struct branch *branch2 = ((const struct line *) l2)->data;
5368 switch (get_sort_field(branch_sort_state)) {
5369 case ORDERBY_DATE:
5370 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5372 case ORDERBY_AUTHOR:
5373 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5375 case ORDERBY_NAME:
5376 default:
5377 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5378 }
5379 }
5381 static bool
5382 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5383 {
5384 struct branch *branch = line->data;
5385 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5387 if (opt_date && draw_date(view, &branch->time))
5388 return TRUE;
5390 if (opt_author && draw_author(view, branch->author))
5391 return TRUE;
5393 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5394 return TRUE;
5395 }
5397 static enum request
5398 branch_request(struct view *view, enum request request, struct line *line)
5399 {
5400 struct branch *branch = line->data;
5402 switch (request) {
5403 case REQ_REFRESH:
5404 load_refs();
5405 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5406 return REQ_NONE;
5408 case REQ_TOGGLE_SORT_FIELD:
5409 case REQ_TOGGLE_SORT_ORDER:
5410 sort_view(view, request, &branch_sort_state, branch_compare);
5411 return REQ_NONE;
5413 case REQ_ENTER:
5414 {
5415 const struct ref *ref = branch->ref;
5416 const char *all_branches_argv[] = {
5417 "git", "log", "--no-color", "--pretty=raw", "--parents",
5418 "--topo-order",
5419 ref == &branch_all ? "--all" : ref->name, NULL
5420 };
5421 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5423 if (!prepare_update(main_view, all_branches_argv, NULL))
5424 report("Failed to load view of all branches");
5425 else
5426 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5427 return REQ_NONE;
5428 }
5429 default:
5430 return request;
5431 }
5432 }
5434 static bool
5435 branch_read(struct view *view, char *line)
5436 {
5437 static char id[SIZEOF_REV];
5438 struct branch *reference;
5439 size_t i;
5441 if (!line)
5442 return TRUE;
5444 switch (get_line_type(line)) {
5445 case LINE_COMMIT:
5446 string_copy_rev(id, line + STRING_SIZE("commit "));
5447 return TRUE;
5449 case LINE_AUTHOR:
5450 for (i = 0, reference = NULL; i < view->lines; i++) {
5451 struct branch *branch = view->line[i].data;
5453 if (strcmp(branch->ref->id, id))
5454 continue;
5456 view->line[i].dirty = TRUE;
5457 if (reference) {
5458 branch->author = reference->author;
5459 branch->time = reference->time;
5460 continue;
5461 }
5463 parse_author_line(line + STRING_SIZE("author "),
5464 &branch->author, &branch->time);
5465 reference = branch;
5466 }
5467 return TRUE;
5469 default:
5470 return TRUE;
5471 }
5473 }
5475 static bool
5476 branch_open_visitor(void *data, const struct ref *ref)
5477 {
5478 struct view *view = data;
5479 struct branch *branch;
5481 if (ref->tag || ref->ltag || ref->remote)
5482 return TRUE;
5484 branch = calloc(1, sizeof(*branch));
5485 if (!branch)
5486 return FALSE;
5488 branch->ref = ref;
5489 return !!add_line_data(view, branch, LINE_DEFAULT);
5490 }
5492 static bool
5493 branch_open(struct view *view)
5494 {
5495 const char *branch_log[] = {
5496 "git", "log", "--no-color", "--pretty=raw",
5497 "--simplify-by-decoration", "--all", NULL
5498 };
5500 if (!start_update(view, branch_log, NULL)) {
5501 report("Failed to load branch data");
5502 return TRUE;
5503 }
5505 setup_update(view, view->id);
5506 branch_open_visitor(view, &branch_all);
5507 foreach_ref(branch_open_visitor, view);
5508 view->p_restore = TRUE;
5510 return TRUE;
5511 }
5513 static bool
5514 branch_grep(struct view *view, struct line *line)
5515 {
5516 struct branch *branch = line->data;
5517 const char *text[] = {
5518 branch->ref->name,
5519 branch->author,
5520 NULL
5521 };
5523 return grep_text(view, text);
5524 }
5526 static void
5527 branch_select(struct view *view, struct line *line)
5528 {
5529 struct branch *branch = line->data;
5531 string_copy_rev(view->ref, branch->ref->id);
5532 string_copy_rev(ref_commit, branch->ref->id);
5533 string_copy_rev(ref_head, branch->ref->id);
5534 string_copy_rev(ref_branch, branch->ref->name);
5535 }
5537 static struct view_ops branch_ops = {
5538 "branch",
5539 NULL,
5540 branch_open,
5541 branch_read,
5542 branch_draw,
5543 branch_request,
5544 branch_grep,
5545 branch_select,
5546 };
5548 /*
5549 * Status backend
5550 */
5552 struct status {
5553 char status;
5554 struct {
5555 mode_t mode;
5556 char rev[SIZEOF_REV];
5557 char name[SIZEOF_STR];
5558 } old;
5559 struct {
5560 mode_t mode;
5561 char rev[SIZEOF_REV];
5562 char name[SIZEOF_STR];
5563 } new;
5564 };
5566 static char status_onbranch[SIZEOF_STR];
5567 static struct status stage_status;
5568 static enum line_type stage_line_type;
5569 static size_t stage_chunks;
5570 static int *stage_chunk;
5572 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5574 /* This should work even for the "On branch" line. */
5575 static inline bool
5576 status_has_none(struct view *view, struct line *line)
5577 {
5578 return line < view->line + view->lines && !line[1].data;
5579 }
5581 /* Get fields from the diff line:
5582 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5583 */
5584 static inline bool
5585 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5586 {
5587 const char *old_mode = buf + 1;
5588 const char *new_mode = buf + 8;
5589 const char *old_rev = buf + 15;
5590 const char *new_rev = buf + 56;
5591 const char *status = buf + 97;
5593 if (bufsize < 98 ||
5594 old_mode[-1] != ':' ||
5595 new_mode[-1] != ' ' ||
5596 old_rev[-1] != ' ' ||
5597 new_rev[-1] != ' ' ||
5598 status[-1] != ' ')
5599 return FALSE;
5601 file->status = *status;
5603 string_copy_rev(file->old.rev, old_rev);
5604 string_copy_rev(file->new.rev, new_rev);
5606 file->old.mode = strtoul(old_mode, NULL, 8);
5607 file->new.mode = strtoul(new_mode, NULL, 8);
5609 file->old.name[0] = file->new.name[0] = 0;
5611 return TRUE;
5612 }
5614 static bool
5615 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5616 {
5617 struct status *unmerged = NULL;
5618 char *buf;
5619 struct io io;
5621 if (!io_run(&io, IO_RD, opt_cdup, argv))
5622 return FALSE;
5624 add_line_data(view, NULL, type);
5626 while ((buf = io_get(&io, 0, TRUE))) {
5627 struct status *file = unmerged;
5629 if (!file) {
5630 file = calloc(1, sizeof(*file));
5631 if (!file || !add_line_data(view, file, type))
5632 goto error_out;
5633 }
5635 /* Parse diff info part. */
5636 if (status) {
5637 file->status = status;
5638 if (status == 'A')
5639 string_copy(file->old.rev, NULL_ID);
5641 } else if (!file->status || file == unmerged) {
5642 if (!status_get_diff(file, buf, strlen(buf)))
5643 goto error_out;
5645 buf = io_get(&io, 0, TRUE);
5646 if (!buf)
5647 break;
5649 /* Collapse all modified entries that follow an
5650 * associated unmerged entry. */
5651 if (unmerged == file) {
5652 unmerged->status = 'U';
5653 unmerged = NULL;
5654 } else if (file->status == 'U') {
5655 unmerged = file;
5656 }
5657 }
5659 /* Grab the old name for rename/copy. */
5660 if (!*file->old.name &&
5661 (file->status == 'R' || file->status == 'C')) {
5662 string_ncopy(file->old.name, buf, strlen(buf));
5664 buf = io_get(&io, 0, TRUE);
5665 if (!buf)
5666 break;
5667 }
5669 /* git-ls-files just delivers a NUL separated list of
5670 * file names similar to the second half of the
5671 * git-diff-* output. */
5672 string_ncopy(file->new.name, buf, strlen(buf));
5673 if (!*file->old.name)
5674 string_copy(file->old.name, file->new.name);
5675 file = NULL;
5676 }
5678 if (io_error(&io)) {
5679 error_out:
5680 io_done(&io);
5681 return FALSE;
5682 }
5684 if (!view->line[view->lines - 1].data)
5685 add_line_data(view, NULL, LINE_STAT_NONE);
5687 io_done(&io);
5688 return TRUE;
5689 }
5691 /* Don't show unmerged entries in the staged section. */
5692 static const char *status_diff_index_argv[] = {
5693 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5694 "--cached", "-M", "HEAD", NULL
5695 };
5697 static const char *status_diff_files_argv[] = {
5698 "git", "diff-files", "-z", NULL
5699 };
5701 static const char *status_list_other_argv[] = {
5702 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5703 };
5705 static const char *status_list_no_head_argv[] = {
5706 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5707 };
5709 static const char *update_index_argv[] = {
5710 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5711 };
5713 /* Restore the previous line number to stay in the context or select a
5714 * line with something that can be updated. */
5715 static void
5716 status_restore(struct view *view)
5717 {
5718 if (view->p_lineno >= view->lines)
5719 view->p_lineno = view->lines - 1;
5720 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5721 view->p_lineno++;
5722 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5723 view->p_lineno--;
5725 /* If the above fails, always skip the "On branch" line. */
5726 if (view->p_lineno < view->lines)
5727 view->lineno = view->p_lineno;
5728 else
5729 view->lineno = 1;
5731 if (view->lineno < view->offset)
5732 view->offset = view->lineno;
5733 else if (view->offset + view->height <= view->lineno)
5734 view->offset = view->lineno - view->height + 1;
5736 view->p_restore = FALSE;
5737 }
5739 static void
5740 status_update_onbranch(void)
5741 {
5742 static const char *paths[][2] = {
5743 { "rebase-apply/rebasing", "Rebasing" },
5744 { "rebase-apply/applying", "Applying mailbox" },
5745 { "rebase-apply/", "Rebasing mailbox" },
5746 { "rebase-merge/interactive", "Interactive rebase" },
5747 { "rebase-merge/", "Rebase merge" },
5748 { "MERGE_HEAD", "Merging" },
5749 { "BISECT_LOG", "Bisecting" },
5750 { "HEAD", "On branch" },
5751 };
5752 char buf[SIZEOF_STR];
5753 struct stat stat;
5754 int i;
5756 if (is_initial_commit()) {
5757 string_copy(status_onbranch, "Initial commit");
5758 return;
5759 }
5761 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5762 char *head = opt_head;
5764 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5765 lstat(buf, &stat) < 0)
5766 continue;
5768 if (!*opt_head) {
5769 struct io io;
5771 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5772 io_read_buf(&io, buf, sizeof(buf))) {
5773 head = buf;
5774 if (!prefixcmp(head, "refs/heads/"))
5775 head += STRING_SIZE("refs/heads/");
5776 }
5777 }
5779 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5780 string_copy(status_onbranch, opt_head);
5781 return;
5782 }
5784 string_copy(status_onbranch, "Not currently on any branch");
5785 }
5787 /* First parse staged info using git-diff-index(1), then parse unstaged
5788 * info using git-diff-files(1), and finally untracked files using
5789 * git-ls-files(1). */
5790 static bool
5791 status_open(struct view *view)
5792 {
5793 reset_view(view);
5795 add_line_data(view, NULL, LINE_STAT_HEAD);
5796 status_update_onbranch();
5798 io_run_bg(update_index_argv);
5800 if (is_initial_commit()) {
5801 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5802 return FALSE;
5803 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5804 return FALSE;
5805 }
5807 if (!opt_untracked_dirs_content)
5808 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5810 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5811 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5812 return FALSE;
5814 /* Restore the exact position or use the specialized restore
5815 * mode? */
5816 if (!view->p_restore)
5817 status_restore(view);
5818 return TRUE;
5819 }
5821 static bool
5822 status_draw(struct view *view, struct line *line, unsigned int lineno)
5823 {
5824 struct status *status = line->data;
5825 enum line_type type;
5826 const char *text;
5828 if (!status) {
5829 switch (line->type) {
5830 case LINE_STAT_STAGED:
5831 type = LINE_STAT_SECTION;
5832 text = "Changes to be committed:";
5833 break;
5835 case LINE_STAT_UNSTAGED:
5836 type = LINE_STAT_SECTION;
5837 text = "Changed but not updated:";
5838 break;
5840 case LINE_STAT_UNTRACKED:
5841 type = LINE_STAT_SECTION;
5842 text = "Untracked files:";
5843 break;
5845 case LINE_STAT_NONE:
5846 type = LINE_DEFAULT;
5847 text = " (no files)";
5848 break;
5850 case LINE_STAT_HEAD:
5851 type = LINE_STAT_HEAD;
5852 text = status_onbranch;
5853 break;
5855 default:
5856 return FALSE;
5857 }
5858 } else {
5859 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5861 buf[0] = status->status;
5862 if (draw_text(view, line->type, buf, TRUE))
5863 return TRUE;
5864 type = LINE_DEFAULT;
5865 text = status->new.name;
5866 }
5868 draw_text(view, type, text, TRUE);
5869 return TRUE;
5870 }
5872 static enum request
5873 status_load_error(struct view *view, struct view *stage, const char *path)
5874 {
5875 if (displayed_views() == 2 || display[current_view] != view)
5876 maximize_view(view);
5877 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5878 return REQ_NONE;
5879 }
5881 static enum request
5882 status_enter(struct view *view, struct line *line)
5883 {
5884 struct status *status = line->data;
5885 const char *oldpath = status ? status->old.name : NULL;
5886 /* Diffs for unmerged entries are empty when passing the new
5887 * path, so leave it empty. */
5888 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5889 const char *info;
5890 enum open_flags split;
5891 struct view *stage = VIEW(REQ_VIEW_STAGE);
5893 if (line->type == LINE_STAT_NONE ||
5894 (!status && line[1].type == LINE_STAT_NONE)) {
5895 report("No file to diff");
5896 return REQ_NONE;
5897 }
5899 switch (line->type) {
5900 case LINE_STAT_STAGED:
5901 if (is_initial_commit()) {
5902 const char *no_head_diff_argv[] = {
5903 "git", "diff", "--no-color", "--patch-with-stat",
5904 "--", "/dev/null", newpath, NULL
5905 };
5907 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5908 return status_load_error(view, stage, newpath);
5909 } else {
5910 const char *index_show_argv[] = {
5911 "git", "diff-index", "--root", "--patch-with-stat",
5912 "-C", "-M", "--cached", "HEAD", "--",
5913 oldpath, newpath, NULL
5914 };
5916 if (!prepare_update(stage, index_show_argv, opt_cdup))
5917 return status_load_error(view, stage, newpath);
5918 }
5920 if (status)
5921 info = "Staged changes to %s";
5922 else
5923 info = "Staged changes";
5924 break;
5926 case LINE_STAT_UNSTAGED:
5927 {
5928 const char *files_show_argv[] = {
5929 "git", "diff-files", "--root", "--patch-with-stat",
5930 "-C", "-M", "--", oldpath, newpath, NULL
5931 };
5933 if (!prepare_update(stage, files_show_argv, opt_cdup))
5934 return status_load_error(view, stage, newpath);
5935 if (status)
5936 info = "Unstaged changes to %s";
5937 else
5938 info = "Unstaged changes";
5939 break;
5940 }
5941 case LINE_STAT_UNTRACKED:
5942 if (!newpath) {
5943 report("No file to show");
5944 return REQ_NONE;
5945 }
5947 if (!suffixcmp(status->new.name, -1, "/")) {
5948 report("Cannot display a directory");
5949 return REQ_NONE;
5950 }
5952 if (!prepare_update_file(stage, newpath))
5953 return status_load_error(view, stage, newpath);
5954 info = "Untracked file %s";
5955 break;
5957 case LINE_STAT_HEAD:
5958 return REQ_NONE;
5960 default:
5961 die("line type %d not handled in switch", line->type);
5962 }
5964 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5965 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5966 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5967 if (status) {
5968 stage_status = *status;
5969 } else {
5970 memset(&stage_status, 0, sizeof(stage_status));
5971 }
5973 stage_line_type = line->type;
5974 stage_chunks = 0;
5975 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5976 }
5978 return REQ_NONE;
5979 }
5981 static bool
5982 status_exists(struct status *status, enum line_type type)
5983 {
5984 struct view *view = VIEW(REQ_VIEW_STATUS);
5985 unsigned long lineno;
5987 for (lineno = 0; lineno < view->lines; lineno++) {
5988 struct line *line = &view->line[lineno];
5989 struct status *pos = line->data;
5991 if (line->type != type)
5992 continue;
5993 if (!pos && (!status || !status->status) && line[1].data) {
5994 select_view_line(view, lineno);
5995 return TRUE;
5996 }
5997 if (pos && !strcmp(status->new.name, pos->new.name)) {
5998 select_view_line(view, lineno);
5999 return TRUE;
6000 }
6001 }
6003 return FALSE;
6004 }
6007 static bool
6008 status_update_prepare(struct io *io, enum line_type type)
6009 {
6010 const char *staged_argv[] = {
6011 "git", "update-index", "-z", "--index-info", NULL
6012 };
6013 const char *others_argv[] = {
6014 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6015 };
6017 switch (type) {
6018 case LINE_STAT_STAGED:
6019 return io_run(io, IO_WR, opt_cdup, staged_argv);
6021 case LINE_STAT_UNSTAGED:
6022 case LINE_STAT_UNTRACKED:
6023 return io_run(io, IO_WR, opt_cdup, others_argv);
6025 default:
6026 die("line type %d not handled in switch", type);
6027 return FALSE;
6028 }
6029 }
6031 static bool
6032 status_update_write(struct io *io, struct status *status, enum line_type type)
6033 {
6034 char buf[SIZEOF_STR];
6035 size_t bufsize = 0;
6037 switch (type) {
6038 case LINE_STAT_STAGED:
6039 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6040 status->old.mode,
6041 status->old.rev,
6042 status->old.name, 0))
6043 return FALSE;
6044 break;
6046 case LINE_STAT_UNSTAGED:
6047 case LINE_STAT_UNTRACKED:
6048 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6049 return FALSE;
6050 break;
6052 default:
6053 die("line type %d not handled in switch", type);
6054 }
6056 return io_write(io, buf, bufsize);
6057 }
6059 static bool
6060 status_update_file(struct status *status, enum line_type type)
6061 {
6062 struct io io;
6063 bool result;
6065 if (!status_update_prepare(&io, type))
6066 return FALSE;
6068 result = status_update_write(&io, status, type);
6069 return io_done(&io) && result;
6070 }
6072 static bool
6073 status_update_files(struct view *view, struct line *line)
6074 {
6075 char buf[sizeof(view->ref)];
6076 struct io io;
6077 bool result = TRUE;
6078 struct line *pos = view->line + view->lines;
6079 int files = 0;
6080 int file, done;
6081 int cursor_y = -1, cursor_x = -1;
6083 if (!status_update_prepare(&io, line->type))
6084 return FALSE;
6086 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6087 files++;
6089 string_copy(buf, view->ref);
6090 getsyx(cursor_y, cursor_x);
6091 for (file = 0, done = 5; result && file < files; line++, file++) {
6092 int almost_done = file * 100 / files;
6094 if (almost_done > done) {
6095 done = almost_done;
6096 string_format(view->ref, "updating file %u of %u (%d%% done)",
6097 file, files, done);
6098 update_view_title(view);
6099 setsyx(cursor_y, cursor_x);
6100 doupdate();
6101 }
6102 result = status_update_write(&io, line->data, line->type);
6103 }
6104 string_copy(view->ref, buf);
6106 return io_done(&io) && result;
6107 }
6109 static bool
6110 status_update(struct view *view)
6111 {
6112 struct line *line = &view->line[view->lineno];
6114 assert(view->lines);
6116 if (!line->data) {
6117 /* This should work even for the "On branch" line. */
6118 if (line < view->line + view->lines && !line[1].data) {
6119 report("Nothing to update");
6120 return FALSE;
6121 }
6123 if (!status_update_files(view, line + 1)) {
6124 report("Failed to update file status");
6125 return FALSE;
6126 }
6128 } else if (!status_update_file(line->data, line->type)) {
6129 report("Failed to update file status");
6130 return FALSE;
6131 }
6133 return TRUE;
6134 }
6136 static bool
6137 status_revert(struct status *status, enum line_type type, bool has_none)
6138 {
6139 if (!status || type != LINE_STAT_UNSTAGED) {
6140 if (type == LINE_STAT_STAGED) {
6141 report("Cannot revert changes to staged files");
6142 } else if (type == LINE_STAT_UNTRACKED) {
6143 report("Cannot revert changes to untracked files");
6144 } else if (has_none) {
6145 report("Nothing to revert");
6146 } else {
6147 report("Cannot revert changes to multiple files");
6148 }
6150 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6151 char mode[10] = "100644";
6152 const char *reset_argv[] = {
6153 "git", "update-index", "--cacheinfo", mode,
6154 status->old.rev, status->old.name, NULL
6155 };
6156 const char *checkout_argv[] = {
6157 "git", "checkout", "--", status->old.name, NULL
6158 };
6160 if (status->status == 'U') {
6161 string_format(mode, "%5o", status->old.mode);
6163 if (status->old.mode == 0 && status->new.mode == 0) {
6164 reset_argv[2] = "--force-remove";
6165 reset_argv[3] = status->old.name;
6166 reset_argv[4] = NULL;
6167 }
6169 if (!io_run_fg(reset_argv, opt_cdup))
6170 return FALSE;
6171 if (status->old.mode == 0 && status->new.mode == 0)
6172 return TRUE;
6173 }
6175 return io_run_fg(checkout_argv, opt_cdup);
6176 }
6178 return FALSE;
6179 }
6181 static enum request
6182 status_request(struct view *view, enum request request, struct line *line)
6183 {
6184 struct status *status = line->data;
6186 switch (request) {
6187 case REQ_STATUS_UPDATE:
6188 if (!status_update(view))
6189 return REQ_NONE;
6190 break;
6192 case REQ_STATUS_REVERT:
6193 if (!status_revert(status, line->type, status_has_none(view, line)))
6194 return REQ_NONE;
6195 break;
6197 case REQ_STATUS_MERGE:
6198 if (!status || status->status != 'U') {
6199 report("Merging only possible for files with unmerged status ('U').");
6200 return REQ_NONE;
6201 }
6202 open_mergetool(status->new.name);
6203 break;
6205 case REQ_EDIT:
6206 if (!status)
6207 return request;
6208 if (status->status == 'D') {
6209 report("File has been deleted.");
6210 return REQ_NONE;
6211 }
6213 open_editor(status->new.name);
6214 break;
6216 case REQ_VIEW_BLAME:
6217 if (status)
6218 opt_ref[0] = 0;
6219 return request;
6221 case REQ_ENTER:
6222 /* After returning the status view has been split to
6223 * show the stage view. No further reloading is
6224 * necessary. */
6225 return status_enter(view, line);
6227 case REQ_REFRESH:
6228 /* Simply reload the view. */
6229 break;
6231 default:
6232 return request;
6233 }
6235 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6237 return REQ_NONE;
6238 }
6240 static void
6241 status_select(struct view *view, struct line *line)
6242 {
6243 struct status *status = line->data;
6244 char file[SIZEOF_STR] = "all files";
6245 const char *text;
6246 const char *key;
6248 if (status && !string_format(file, "'%s'", status->new.name))
6249 return;
6251 if (!status && line[1].type == LINE_STAT_NONE)
6252 line++;
6254 switch (line->type) {
6255 case LINE_STAT_STAGED:
6256 text = "Press %s to unstage %s for commit";
6257 break;
6259 case LINE_STAT_UNSTAGED:
6260 text = "Press %s to stage %s for commit";
6261 break;
6263 case LINE_STAT_UNTRACKED:
6264 text = "Press %s to stage %s for addition";
6265 break;
6267 case LINE_STAT_HEAD:
6268 case LINE_STAT_NONE:
6269 text = "Nothing to update";
6270 break;
6272 default:
6273 die("line type %d not handled in switch", line->type);
6274 }
6276 if (status && status->status == 'U') {
6277 text = "Press %s to resolve conflict in %s";
6278 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6280 } else {
6281 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6282 }
6284 string_format(view->ref, text, key, file);
6285 if (status)
6286 string_copy(opt_file, status->new.name);
6287 }
6289 static bool
6290 status_grep(struct view *view, struct line *line)
6291 {
6292 struct status *status = line->data;
6294 if (status) {
6295 const char buf[2] = { status->status, 0 };
6296 const char *text[] = { status->new.name, buf, NULL };
6298 return grep_text(view, text);
6299 }
6301 return FALSE;
6302 }
6304 static struct view_ops status_ops = {
6305 "file",
6306 NULL,
6307 status_open,
6308 NULL,
6309 status_draw,
6310 status_request,
6311 status_grep,
6312 status_select,
6313 };
6316 static bool
6317 stage_diff_write(struct io *io, struct line *line, struct line *end)
6318 {
6319 while (line < end) {
6320 if (!io_write(io, line->data, strlen(line->data)) ||
6321 !io_write(io, "\n", 1))
6322 return FALSE;
6323 line++;
6324 if (line->type == LINE_DIFF_CHUNK ||
6325 line->type == LINE_DIFF_HEADER)
6326 break;
6327 }
6329 return TRUE;
6330 }
6332 static struct line *
6333 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6334 {
6335 for (; view->line < line; line--)
6336 if (line->type == type)
6337 return line;
6339 return NULL;
6340 }
6342 static bool
6343 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6344 {
6345 const char *apply_argv[SIZEOF_ARG] = {
6346 "git", "apply", "--whitespace=nowarn", NULL
6347 };
6348 struct line *diff_hdr;
6349 struct io io;
6350 int argc = 3;
6352 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6353 if (!diff_hdr)
6354 return FALSE;
6356 if (!revert)
6357 apply_argv[argc++] = "--cached";
6358 if (revert || stage_line_type == LINE_STAT_STAGED)
6359 apply_argv[argc++] = "-R";
6360 apply_argv[argc++] = "-";
6361 apply_argv[argc++] = NULL;
6362 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6363 return FALSE;
6365 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6366 !stage_diff_write(&io, chunk, view->line + view->lines))
6367 chunk = NULL;
6369 io_done(&io);
6370 io_run_bg(update_index_argv);
6372 return chunk ? TRUE : FALSE;
6373 }
6375 static bool
6376 stage_update(struct view *view, struct line *line)
6377 {
6378 struct line *chunk = NULL;
6380 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6381 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6383 if (chunk) {
6384 if (!stage_apply_chunk(view, chunk, FALSE)) {
6385 report("Failed to apply chunk");
6386 return FALSE;
6387 }
6389 } else if (!stage_status.status) {
6390 view = VIEW(REQ_VIEW_STATUS);
6392 for (line = view->line; line < view->line + view->lines; line++)
6393 if (line->type == stage_line_type)
6394 break;
6396 if (!status_update_files(view, line + 1)) {
6397 report("Failed to update files");
6398 return FALSE;
6399 }
6401 } else if (!status_update_file(&stage_status, stage_line_type)) {
6402 report("Failed to update file");
6403 return FALSE;
6404 }
6406 return TRUE;
6407 }
6409 static bool
6410 stage_revert(struct view *view, struct line *line)
6411 {
6412 struct line *chunk = NULL;
6414 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6415 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6417 if (chunk) {
6418 if (!prompt_yesno("Are you sure you want to revert changes?"))
6419 return FALSE;
6421 if (!stage_apply_chunk(view, chunk, TRUE)) {
6422 report("Failed to revert chunk");
6423 return FALSE;
6424 }
6425 return TRUE;
6427 } else {
6428 return status_revert(stage_status.status ? &stage_status : NULL,
6429 stage_line_type, FALSE);
6430 }
6431 }
6434 static void
6435 stage_next(struct view *view, struct line *line)
6436 {
6437 int i;
6439 if (!stage_chunks) {
6440 for (line = view->line; line < view->line + view->lines; line++) {
6441 if (line->type != LINE_DIFF_CHUNK)
6442 continue;
6444 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6445 report("Allocation failure");
6446 return;
6447 }
6449 stage_chunk[stage_chunks++] = line - view->line;
6450 }
6451 }
6453 for (i = 0; i < stage_chunks; i++) {
6454 if (stage_chunk[i] > view->lineno) {
6455 do_scroll_view(view, stage_chunk[i] - view->lineno);
6456 report("Chunk %d of %d", i + 1, stage_chunks);
6457 return;
6458 }
6459 }
6461 report("No next chunk found");
6462 }
6464 static enum request
6465 stage_request(struct view *view, enum request request, struct line *line)
6466 {
6467 switch (request) {
6468 case REQ_STATUS_UPDATE:
6469 if (!stage_update(view, line))
6470 return REQ_NONE;
6471 break;
6473 case REQ_STATUS_REVERT:
6474 if (!stage_revert(view, line))
6475 return REQ_NONE;
6476 break;
6478 case REQ_STAGE_NEXT:
6479 if (stage_line_type == LINE_STAT_UNTRACKED) {
6480 report("File is untracked; press %s to add",
6481 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6482 return REQ_NONE;
6483 }
6484 stage_next(view, line);
6485 return REQ_NONE;
6487 case REQ_EDIT:
6488 if (!stage_status.new.name[0])
6489 return request;
6490 if (stage_status.status == 'D') {
6491 report("File has been deleted.");
6492 return REQ_NONE;
6493 }
6495 open_editor(stage_status.new.name);
6496 break;
6498 case REQ_REFRESH:
6499 /* Reload everything ... */
6500 break;
6502 case REQ_VIEW_BLAME:
6503 if (stage_status.new.name[0]) {
6504 string_copy(opt_file, stage_status.new.name);
6505 opt_ref[0] = 0;
6506 }
6507 return request;
6509 case REQ_ENTER:
6510 return pager_request(view, request, line);
6512 default:
6513 return request;
6514 }
6516 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6517 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6519 /* Check whether the staged entry still exists, and close the
6520 * stage view if it doesn't. */
6521 if (!status_exists(&stage_status, stage_line_type)) {
6522 status_restore(VIEW(REQ_VIEW_STATUS));
6523 return REQ_VIEW_CLOSE;
6524 }
6526 if (stage_line_type == LINE_STAT_UNTRACKED) {
6527 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6528 report("Cannot display a directory");
6529 return REQ_NONE;
6530 }
6532 if (!prepare_update_file(view, stage_status.new.name)) {
6533 report("Failed to open file: %s", strerror(errno));
6534 return REQ_NONE;
6535 }
6536 }
6537 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6539 return REQ_NONE;
6540 }
6542 static struct view_ops stage_ops = {
6543 "line",
6544 NULL,
6545 NULL,
6546 pager_read,
6547 pager_draw,
6548 stage_request,
6549 pager_grep,
6550 pager_select,
6551 };
6554 /*
6555 * Revision graph
6556 */
6558 struct commit {
6559 char id[SIZEOF_REV]; /* SHA1 ID. */
6560 char title[128]; /* First line of the commit message. */
6561 const char *author; /* Author of the commit. */
6562 struct time time; /* Date from the author ident. */
6563 struct ref_list *refs; /* Repository references. */
6564 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6565 size_t graph_size; /* The width of the graph array. */
6566 bool has_parents; /* Rewritten --parents seen. */
6567 };
6569 /* Size of rev graph with no "padding" columns */
6570 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6572 struct rev_graph {
6573 struct rev_graph *prev, *next, *parents;
6574 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6575 size_t size;
6576 struct commit *commit;
6577 size_t pos;
6578 unsigned int boundary:1;
6579 };
6581 /* Parents of the commit being visualized. */
6582 static struct rev_graph graph_parents[4];
6584 /* The current stack of revisions on the graph. */
6585 static struct rev_graph graph_stacks[4] = {
6586 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6587 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6588 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6589 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6590 };
6592 static inline bool
6593 graph_parent_is_merge(struct rev_graph *graph)
6594 {
6595 return graph->parents->size > 1;
6596 }
6598 static inline void
6599 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6600 {
6601 struct commit *commit = graph->commit;
6603 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6604 commit->graph[commit->graph_size++] = symbol;
6605 }
6607 static void
6608 clear_rev_graph(struct rev_graph *graph)
6609 {
6610 graph->boundary = 0;
6611 graph->size = graph->pos = 0;
6612 graph->commit = NULL;
6613 memset(graph->parents, 0, sizeof(*graph->parents));
6614 }
6616 static void
6617 done_rev_graph(struct rev_graph *graph)
6618 {
6619 if (graph_parent_is_merge(graph) &&
6620 graph->pos < graph->size - 1 &&
6621 graph->next->size == graph->size + graph->parents->size - 1) {
6622 size_t i = graph->pos + graph->parents->size - 1;
6624 graph->commit->graph_size = i * 2;
6625 while (i < graph->next->size - 1) {
6626 append_to_rev_graph(graph, ' ');
6627 append_to_rev_graph(graph, '\\');
6628 i++;
6629 }
6630 }
6632 clear_rev_graph(graph);
6633 }
6635 static void
6636 push_rev_graph(struct rev_graph *graph, const char *parent)
6637 {
6638 int i;
6640 /* "Collapse" duplicate parents lines.
6641 *
6642 * FIXME: This needs to also update update the drawn graph but
6643 * for now it just serves as a method for pruning graph lines. */
6644 for (i = 0; i < graph->size; i++)
6645 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6646 return;
6648 if (graph->size < SIZEOF_REVITEMS) {
6649 string_copy_rev(graph->rev[graph->size++], parent);
6650 }
6651 }
6653 static chtype
6654 get_rev_graph_symbol(struct rev_graph *graph)
6655 {
6656 chtype symbol;
6658 if (graph->boundary)
6659 symbol = REVGRAPH_BOUND;
6660 else if (graph->parents->size == 0)
6661 symbol = REVGRAPH_INIT;
6662 else if (graph_parent_is_merge(graph))
6663 symbol = REVGRAPH_MERGE;
6664 else if (graph->pos >= graph->size)
6665 symbol = REVGRAPH_BRANCH;
6666 else
6667 symbol = REVGRAPH_COMMIT;
6669 return symbol;
6670 }
6672 static void
6673 draw_rev_graph(struct rev_graph *graph)
6674 {
6675 struct rev_filler {
6676 chtype separator, line;
6677 };
6678 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6679 static struct rev_filler fillers[] = {
6680 { ' ', '|' },
6681 { '`', '.' },
6682 { '\'', ' ' },
6683 { '/', ' ' },
6684 };
6685 chtype symbol = get_rev_graph_symbol(graph);
6686 struct rev_filler *filler;
6687 size_t i;
6689 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6690 filler = &fillers[DEFAULT];
6692 for (i = 0; i < graph->pos; i++) {
6693 append_to_rev_graph(graph, filler->line);
6694 if (graph_parent_is_merge(graph->prev) &&
6695 graph->prev->pos == i)
6696 filler = &fillers[RSHARP];
6698 append_to_rev_graph(graph, filler->separator);
6699 }
6701 /* Place the symbol for this revision. */
6702 append_to_rev_graph(graph, symbol);
6704 if (graph->prev->size > graph->size)
6705 filler = &fillers[RDIAG];
6706 else
6707 filler = &fillers[DEFAULT];
6709 i++;
6711 for (; i < graph->size; i++) {
6712 append_to_rev_graph(graph, filler->separator);
6713 append_to_rev_graph(graph, filler->line);
6714 if (graph_parent_is_merge(graph->prev) &&
6715 i < graph->prev->pos + graph->parents->size)
6716 filler = &fillers[RSHARP];
6717 if (graph->prev->size > graph->size)
6718 filler = &fillers[LDIAG];
6719 }
6721 if (graph->prev->size > graph->size) {
6722 append_to_rev_graph(graph, filler->separator);
6723 if (filler->line != ' ')
6724 append_to_rev_graph(graph, filler->line);
6725 }
6726 }
6728 /* Prepare the next rev graph */
6729 static void
6730 prepare_rev_graph(struct rev_graph *graph)
6731 {
6732 size_t i;
6734 /* First, traverse all lines of revisions up to the active one. */
6735 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6736 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6737 break;
6739 push_rev_graph(graph->next, graph->rev[graph->pos]);
6740 }
6742 /* Interleave the new revision parent(s). */
6743 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6744 push_rev_graph(graph->next, graph->parents->rev[i]);
6746 /* Lastly, put any remaining revisions. */
6747 for (i = graph->pos + 1; i < graph->size; i++)
6748 push_rev_graph(graph->next, graph->rev[i]);
6749 }
6751 static void
6752 update_rev_graph(struct view *view, struct rev_graph *graph)
6753 {
6754 /* If this is the finalizing update ... */
6755 if (graph->commit)
6756 prepare_rev_graph(graph);
6758 /* Graph visualization needs a one rev look-ahead,
6759 * so the first update doesn't visualize anything. */
6760 if (!graph->prev->commit)
6761 return;
6763 if (view->lines > 2)
6764 view->line[view->lines - 3].dirty = 1;
6765 if (view->lines > 1)
6766 view->line[view->lines - 2].dirty = 1;
6767 draw_rev_graph(graph->prev);
6768 done_rev_graph(graph->prev->prev);
6769 }
6772 /*
6773 * Main view backend
6774 */
6776 static const char *main_argv[SIZEOF_ARG] = {
6777 "git", "log", "--no-color", "--pretty=raw", "--parents",
6778 "--topo-order", "%(diffargs)", "%(revargs)",
6779 "--", "%(fileargs)", NULL
6780 };
6782 static bool
6783 main_draw(struct view *view, struct line *line, unsigned int lineno)
6784 {
6785 struct commit *commit = line->data;
6787 if (!commit->author)
6788 return FALSE;
6790 if (opt_date && draw_date(view, &commit->time))
6791 return TRUE;
6793 if (opt_author && draw_author(view, commit->author))
6794 return TRUE;
6796 if (opt_rev_graph && commit->graph_size &&
6797 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6798 return TRUE;
6800 if (opt_show_refs && commit->refs) {
6801 size_t i;
6803 for (i = 0; i < commit->refs->size; i++) {
6804 struct ref *ref = commit->refs->refs[i];
6805 enum line_type type;
6807 if (ref->head)
6808 type = LINE_MAIN_HEAD;
6809 else if (ref->ltag)
6810 type = LINE_MAIN_LOCAL_TAG;
6811 else if (ref->tag)
6812 type = LINE_MAIN_TAG;
6813 else if (ref->tracked)
6814 type = LINE_MAIN_TRACKED;
6815 else if (ref->remote)
6816 type = LINE_MAIN_REMOTE;
6817 else
6818 type = LINE_MAIN_REF;
6820 if (draw_text(view, type, "[", TRUE) ||
6821 draw_text(view, type, ref->name, TRUE) ||
6822 draw_text(view, type, "]", TRUE))
6823 return TRUE;
6825 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6826 return TRUE;
6827 }
6828 }
6830 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6831 return TRUE;
6832 }
6834 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6835 static bool
6836 main_read(struct view *view, char *line)
6837 {
6838 static struct rev_graph *graph = graph_stacks;
6839 enum line_type type;
6840 struct commit *commit;
6842 if (!line) {
6843 int i;
6845 if (!view->lines && !view->prev)
6846 die("No revisions match the given arguments.");
6847 if (view->lines > 0) {
6848 commit = view->line[view->lines - 1].data;
6849 view->line[view->lines - 1].dirty = 1;
6850 if (!commit->author) {
6851 view->lines--;
6852 free(commit);
6853 graph->commit = NULL;
6854 }
6855 }
6856 update_rev_graph(view, graph);
6858 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6859 clear_rev_graph(&graph_stacks[i]);
6860 return TRUE;
6861 }
6863 type = get_line_type(line);
6864 if (type == LINE_COMMIT) {
6865 commit = calloc(1, sizeof(struct commit));
6866 if (!commit)
6867 return FALSE;
6869 line += STRING_SIZE("commit ");
6870 if (*line == '-') {
6871 graph->boundary = 1;
6872 line++;
6873 }
6875 string_copy_rev(commit->id, line);
6876 commit->refs = get_ref_list(commit->id);
6877 graph->commit = commit;
6878 add_line_data(view, commit, LINE_MAIN_COMMIT);
6880 while ((line = strchr(line, ' '))) {
6881 line++;
6882 push_rev_graph(graph->parents, line);
6883 commit->has_parents = TRUE;
6884 }
6885 return TRUE;
6886 }
6888 if (!view->lines)
6889 return TRUE;
6890 commit = view->line[view->lines - 1].data;
6892 switch (type) {
6893 case LINE_PARENT:
6894 if (commit->has_parents)
6895 break;
6896 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6897 break;
6899 case LINE_AUTHOR:
6900 parse_author_line(line + STRING_SIZE("author "),
6901 &commit->author, &commit->time);
6902 update_rev_graph(view, graph);
6903 graph = graph->next;
6904 break;
6906 default:
6907 /* Fill in the commit title if it has not already been set. */
6908 if (commit->title[0])
6909 break;
6911 /* Require titles to start with a non-space character at the
6912 * offset used by git log. */
6913 if (strncmp(line, " ", 4))
6914 break;
6915 line += 4;
6916 /* Well, if the title starts with a whitespace character,
6917 * try to be forgiving. Otherwise we end up with no title. */
6918 while (isspace(*line))
6919 line++;
6920 if (*line == '\0')
6921 break;
6922 /* FIXME: More graceful handling of titles; append "..." to
6923 * shortened titles, etc. */
6925 string_expand(commit->title, sizeof(commit->title), line, 1);
6926 view->line[view->lines - 1].dirty = 1;
6927 }
6929 return TRUE;
6930 }
6932 static enum request
6933 main_request(struct view *view, enum request request, struct line *line)
6934 {
6935 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6937 switch (request) {
6938 case REQ_ENTER:
6939 if (view_is_displayed(view) && display[0] != view)
6940 maximize_view(view);
6941 open_view(view, REQ_VIEW_DIFF, flags);
6942 break;
6943 case REQ_REFRESH:
6944 load_refs();
6945 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6946 break;
6947 default:
6948 return request;
6949 }
6951 return REQ_NONE;
6952 }
6954 static bool
6955 grep_refs(struct ref_list *list, regex_t *regex)
6956 {
6957 regmatch_t pmatch;
6958 size_t i;
6960 if (!opt_show_refs || !list)
6961 return FALSE;
6963 for (i = 0; i < list->size; i++) {
6964 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6965 return TRUE;
6966 }
6968 return FALSE;
6969 }
6971 static bool
6972 main_grep(struct view *view, struct line *line)
6973 {
6974 struct commit *commit = line->data;
6975 const char *text[] = {
6976 commit->title,
6977 opt_author ? commit->author : "",
6978 mkdate(&commit->time, opt_date),
6979 NULL
6980 };
6982 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6983 }
6985 static void
6986 main_select(struct view *view, struct line *line)
6987 {
6988 struct commit *commit = line->data;
6990 string_copy_rev(view->ref, commit->id);
6991 string_copy_rev(ref_commit, view->ref);
6992 }
6994 static struct view_ops main_ops = {
6995 "commit",
6996 main_argv,
6997 NULL,
6998 main_read,
6999 main_draw,
7000 main_request,
7001 main_grep,
7002 main_select,
7003 };
7006 /*
7007 * Status management
7008 */
7010 /* Whether or not the curses interface has been initialized. */
7011 static bool cursed = FALSE;
7013 /* Terminal hacks and workarounds. */
7014 static bool use_scroll_redrawwin;
7015 static bool use_scroll_status_wclear;
7017 /* The status window is used for polling keystrokes. */
7018 static WINDOW *status_win;
7020 /* Reading from the prompt? */
7021 static bool input_mode = FALSE;
7023 static bool status_empty = FALSE;
7025 /* Update status and title window. */
7026 static void
7027 report(const char *msg, ...)
7028 {
7029 struct view *view = display[current_view];
7031 if (input_mode)
7032 return;
7034 if (!view) {
7035 char buf[SIZEOF_STR];
7036 va_list args;
7038 va_start(args, msg);
7039 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7040 buf[sizeof(buf) - 1] = 0;
7041 buf[sizeof(buf) - 2] = '.';
7042 buf[sizeof(buf) - 3] = '.';
7043 buf[sizeof(buf) - 4] = '.';
7044 }
7045 va_end(args);
7046 die("%s", buf);
7047 }
7049 if (!status_empty || *msg) {
7050 va_list args;
7052 va_start(args, msg);
7054 wmove(status_win, 0, 0);
7055 if (view->has_scrolled && use_scroll_status_wclear)
7056 wclear(status_win);
7057 if (*msg) {
7058 vwprintw(status_win, msg, args);
7059 status_empty = FALSE;
7060 } else {
7061 status_empty = TRUE;
7062 }
7063 wclrtoeol(status_win);
7064 wnoutrefresh(status_win);
7066 va_end(args);
7067 }
7069 update_view_title(view);
7070 }
7072 static void
7073 init_display(void)
7074 {
7075 const char *term;
7076 int x, y;
7078 /* Initialize the curses library */
7079 if (isatty(STDIN_FILENO)) {
7080 cursed = !!initscr();
7081 opt_tty = stdin;
7082 } else {
7083 /* Leave stdin and stdout alone when acting as a pager. */
7084 opt_tty = fopen("/dev/tty", "r+");
7085 if (!opt_tty)
7086 die("Failed to open /dev/tty");
7087 cursed = !!newterm(NULL, opt_tty, opt_tty);
7088 }
7090 if (!cursed)
7091 die("Failed to initialize curses");
7093 nonl(); /* Disable conversion and detect newlines from input. */
7094 cbreak(); /* Take input chars one at a time, no wait for \n */
7095 noecho(); /* Don't echo input */
7096 leaveok(stdscr, FALSE);
7098 if (has_colors())
7099 init_colors();
7101 getmaxyx(stdscr, y, x);
7102 status_win = newwin(1, 0, y - 1, 0);
7103 if (!status_win)
7104 die("Failed to create status window");
7106 /* Enable keyboard mapping */
7107 keypad(status_win, TRUE);
7108 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7110 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7111 set_tabsize(opt_tab_size);
7112 #else
7113 TABSIZE = opt_tab_size;
7114 #endif
7116 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7117 if (term && !strcmp(term, "gnome-terminal")) {
7118 /* In the gnome-terminal-emulator, the message from
7119 * scrolling up one line when impossible followed by
7120 * scrolling down one line causes corruption of the
7121 * status line. This is fixed by calling wclear. */
7122 use_scroll_status_wclear = TRUE;
7123 use_scroll_redrawwin = FALSE;
7125 } else if (term && !strcmp(term, "xrvt-xpm")) {
7126 /* No problems with full optimizations in xrvt-(unicode)
7127 * and aterm. */
7128 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7130 } else {
7131 /* When scrolling in (u)xterm the last line in the
7132 * scrolling direction will update slowly. */
7133 use_scroll_redrawwin = TRUE;
7134 use_scroll_status_wclear = FALSE;
7135 }
7136 }
7138 static int
7139 get_input(int prompt_position)
7140 {
7141 struct view *view;
7142 int i, key, cursor_y, cursor_x;
7144 if (prompt_position)
7145 input_mode = TRUE;
7147 while (TRUE) {
7148 bool loading = FALSE;
7150 foreach_view (view, i) {
7151 update_view(view);
7152 if (view_is_displayed(view) && view->has_scrolled &&
7153 use_scroll_redrawwin)
7154 redrawwin(view->win);
7155 view->has_scrolled = FALSE;
7156 if (view->pipe)
7157 loading = TRUE;
7158 }
7160 /* Update the cursor position. */
7161 if (prompt_position) {
7162 getbegyx(status_win, cursor_y, cursor_x);
7163 cursor_x = prompt_position;
7164 } else {
7165 view = display[current_view];
7166 getbegyx(view->win, cursor_y, cursor_x);
7167 cursor_x = view->width - 1;
7168 cursor_y += view->lineno - view->offset;
7169 }
7170 setsyx(cursor_y, cursor_x);
7172 /* Refresh, accept single keystroke of input */
7173 doupdate();
7174 nodelay(status_win, loading);
7175 key = wgetch(status_win);
7177 /* wgetch() with nodelay() enabled returns ERR when
7178 * there's no input. */
7179 if (key == ERR) {
7181 } else if (key == KEY_RESIZE) {
7182 int height, width;
7184 getmaxyx(stdscr, height, width);
7186 wresize(status_win, 1, width);
7187 mvwin(status_win, height - 1, 0);
7188 wnoutrefresh(status_win);
7189 resize_display();
7190 redraw_display(TRUE);
7192 } else {
7193 input_mode = FALSE;
7194 return key;
7195 }
7196 }
7197 }
7199 static char *
7200 prompt_input(const char *prompt, input_handler handler, void *data)
7201 {
7202 enum input_status status = INPUT_OK;
7203 static char buf[SIZEOF_STR];
7204 size_t pos = 0;
7206 buf[pos] = 0;
7208 while (status == INPUT_OK || status == INPUT_SKIP) {
7209 int key;
7211 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7212 wclrtoeol(status_win);
7214 key = get_input(pos + 1);
7215 switch (key) {
7216 case KEY_RETURN:
7217 case KEY_ENTER:
7218 case '\n':
7219 status = pos ? INPUT_STOP : INPUT_CANCEL;
7220 break;
7222 case KEY_BACKSPACE:
7223 if (pos > 0)
7224 buf[--pos] = 0;
7225 else
7226 status = INPUT_CANCEL;
7227 break;
7229 case KEY_ESC:
7230 status = INPUT_CANCEL;
7231 break;
7233 default:
7234 if (pos >= sizeof(buf)) {
7235 report("Input string too long");
7236 return NULL;
7237 }
7239 status = handler(data, buf, key);
7240 if (status == INPUT_OK)
7241 buf[pos++] = (char) key;
7242 }
7243 }
7245 /* Clear the status window */
7246 status_empty = FALSE;
7247 report("");
7249 if (status == INPUT_CANCEL)
7250 return NULL;
7252 buf[pos++] = 0;
7254 return buf;
7255 }
7257 static enum input_status
7258 prompt_yesno_handler(void *data, char *buf, int c)
7259 {
7260 if (c == 'y' || c == 'Y')
7261 return INPUT_STOP;
7262 if (c == 'n' || c == 'N')
7263 return INPUT_CANCEL;
7264 return INPUT_SKIP;
7265 }
7267 static bool
7268 prompt_yesno(const char *prompt)
7269 {
7270 char prompt2[SIZEOF_STR];
7272 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7273 return FALSE;
7275 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7276 }
7278 static enum input_status
7279 read_prompt_handler(void *data, char *buf, int c)
7280 {
7281 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7282 }
7284 static char *
7285 read_prompt(const char *prompt)
7286 {
7287 return prompt_input(prompt, read_prompt_handler, NULL);
7288 }
7290 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7291 {
7292 enum input_status status = INPUT_OK;
7293 int size = 0;
7295 while (items[size].text)
7296 size++;
7298 while (status == INPUT_OK) {
7299 const struct menu_item *item = &items[*selected];
7300 int key;
7301 int i;
7303 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7304 prompt, *selected + 1, size);
7305 if (item->hotkey)
7306 wprintw(status_win, "[%c] ", (char) item->hotkey);
7307 wprintw(status_win, "%s", item->text);
7308 wclrtoeol(status_win);
7310 key = get_input(COLS - 1);
7311 switch (key) {
7312 case KEY_RETURN:
7313 case KEY_ENTER:
7314 case '\n':
7315 status = INPUT_STOP;
7316 break;
7318 case KEY_LEFT:
7319 case KEY_UP:
7320 *selected = *selected - 1;
7321 if (*selected < 0)
7322 *selected = size - 1;
7323 break;
7325 case KEY_RIGHT:
7326 case KEY_DOWN:
7327 *selected = (*selected + 1) % size;
7328 break;
7330 case KEY_ESC:
7331 status = INPUT_CANCEL;
7332 break;
7334 default:
7335 for (i = 0; items[i].text; i++)
7336 if (items[i].hotkey == key) {
7337 *selected = i;
7338 status = INPUT_STOP;
7339 break;
7340 }
7341 }
7342 }
7344 /* Clear the status window */
7345 status_empty = FALSE;
7346 report("");
7348 return status != INPUT_CANCEL;
7349 }
7351 /*
7352 * Repository properties
7353 */
7355 static struct ref **refs = NULL;
7356 static size_t refs_size = 0;
7357 static struct ref *refs_head = NULL;
7359 static struct ref_list **ref_lists = NULL;
7360 static size_t ref_lists_size = 0;
7362 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7363 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7364 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7366 static int
7367 compare_refs(const void *ref1_, const void *ref2_)
7368 {
7369 const struct ref *ref1 = *(const struct ref **)ref1_;
7370 const struct ref *ref2 = *(const struct ref **)ref2_;
7372 if (ref1->tag != ref2->tag)
7373 return ref2->tag - ref1->tag;
7374 if (ref1->ltag != ref2->ltag)
7375 return ref2->ltag - ref2->ltag;
7376 if (ref1->head != ref2->head)
7377 return ref2->head - ref1->head;
7378 if (ref1->tracked != ref2->tracked)
7379 return ref2->tracked - ref1->tracked;
7380 if (ref1->remote != ref2->remote)
7381 return ref2->remote - ref1->remote;
7382 return strcmp(ref1->name, ref2->name);
7383 }
7385 static void
7386 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7387 {
7388 size_t i;
7390 for (i = 0; i < refs_size; i++)
7391 if (!visitor(data, refs[i]))
7392 break;
7393 }
7395 static struct ref *
7396 get_ref_head()
7397 {
7398 return refs_head;
7399 }
7401 static struct ref_list *
7402 get_ref_list(const char *id)
7403 {
7404 struct ref_list *list;
7405 size_t i;
7407 for (i = 0; i < ref_lists_size; i++)
7408 if (!strcmp(id, ref_lists[i]->id))
7409 return ref_lists[i];
7411 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7412 return NULL;
7413 list = calloc(1, sizeof(*list));
7414 if (!list)
7415 return NULL;
7417 for (i = 0; i < refs_size; i++) {
7418 if (!strcmp(id, refs[i]->id) &&
7419 realloc_refs_list(&list->refs, list->size, 1))
7420 list->refs[list->size++] = refs[i];
7421 }
7423 if (!list->refs) {
7424 free(list);
7425 return NULL;
7426 }
7428 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7429 ref_lists[ref_lists_size++] = list;
7430 return list;
7431 }
7433 static int
7434 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7435 {
7436 struct ref *ref = NULL;
7437 bool tag = FALSE;
7438 bool ltag = FALSE;
7439 bool remote = FALSE;
7440 bool tracked = FALSE;
7441 bool head = FALSE;
7442 int from = 0, to = refs_size - 1;
7444 if (!prefixcmp(name, "refs/tags/")) {
7445 if (!suffixcmp(name, namelen, "^{}")) {
7446 namelen -= 3;
7447 name[namelen] = 0;
7448 } else {
7449 ltag = TRUE;
7450 }
7452 tag = TRUE;
7453 namelen -= STRING_SIZE("refs/tags/");
7454 name += STRING_SIZE("refs/tags/");
7456 } else if (!prefixcmp(name, "refs/remotes/")) {
7457 remote = TRUE;
7458 namelen -= STRING_SIZE("refs/remotes/");
7459 name += STRING_SIZE("refs/remotes/");
7460 tracked = !strcmp(opt_remote, name);
7462 } else if (!prefixcmp(name, "refs/heads/")) {
7463 namelen -= STRING_SIZE("refs/heads/");
7464 name += STRING_SIZE("refs/heads/");
7465 if (!strncmp(opt_head, name, namelen))
7466 return OK;
7468 } else if (!strcmp(name, "HEAD")) {
7469 head = TRUE;
7470 if (*opt_head) {
7471 namelen = strlen(opt_head);
7472 name = opt_head;
7473 }
7474 }
7476 /* If we are reloading or it's an annotated tag, replace the
7477 * previous SHA1 with the resolved commit id; relies on the fact
7478 * git-ls-remote lists the commit id of an annotated tag right
7479 * before the commit id it points to. */
7480 while (from <= to) {
7481 size_t pos = (to + from) / 2;
7482 int cmp = strcmp(name, refs[pos]->name);
7484 if (!cmp) {
7485 ref = refs[pos];
7486 break;
7487 }
7489 if (cmp < 0)
7490 to = pos - 1;
7491 else
7492 from = pos + 1;
7493 }
7495 if (!ref) {
7496 if (!realloc_refs(&refs, refs_size, 1))
7497 return ERR;
7498 ref = calloc(1, sizeof(*ref) + namelen);
7499 if (!ref)
7500 return ERR;
7501 memmove(refs + from + 1, refs + from,
7502 (refs_size - from) * sizeof(*refs));
7503 refs[from] = ref;
7504 strncpy(ref->name, name, namelen);
7505 refs_size++;
7506 }
7508 ref->head = head;
7509 ref->tag = tag;
7510 ref->ltag = ltag;
7511 ref->remote = remote;
7512 ref->tracked = tracked;
7513 string_copy_rev(ref->id, id);
7515 if (head)
7516 refs_head = ref;
7517 return OK;
7518 }
7520 static int
7521 load_refs(void)
7522 {
7523 const char *head_argv[] = {
7524 "git", "symbolic-ref", "HEAD", NULL
7525 };
7526 static const char *ls_remote_argv[SIZEOF_ARG] = {
7527 "git", "ls-remote", opt_git_dir, NULL
7528 };
7529 static bool init = FALSE;
7530 size_t i;
7532 if (!init) {
7533 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7534 die("TIG_LS_REMOTE contains too many arguments");
7535 init = TRUE;
7536 }
7538 if (!*opt_git_dir)
7539 return OK;
7541 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7542 !prefixcmp(opt_head, "refs/heads/")) {
7543 char *offset = opt_head + STRING_SIZE("refs/heads/");
7545 memmove(opt_head, offset, strlen(offset) + 1);
7546 }
7548 refs_head = NULL;
7549 for (i = 0; i < refs_size; i++)
7550 refs[i]->id[0] = 0;
7552 if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7553 return ERR;
7555 /* Update the ref lists to reflect changes. */
7556 for (i = 0; i < ref_lists_size; i++) {
7557 struct ref_list *list = ref_lists[i];
7558 size_t old, new;
7560 for (old = new = 0; old < list->size; old++)
7561 if (!strcmp(list->id, list->refs[old]->id))
7562 list->refs[new++] = list->refs[old];
7563 list->size = new;
7564 }
7566 return OK;
7567 }
7569 static void
7570 set_remote_branch(const char *name, const char *value, size_t valuelen)
7571 {
7572 if (!strcmp(name, ".remote")) {
7573 string_ncopy(opt_remote, value, valuelen);
7575 } else if (*opt_remote && !strcmp(name, ".merge")) {
7576 size_t from = strlen(opt_remote);
7578 if (!prefixcmp(value, "refs/heads/"))
7579 value += STRING_SIZE("refs/heads/");
7581 if (!string_format_from(opt_remote, &from, "/%s", value))
7582 opt_remote[0] = 0;
7583 }
7584 }
7586 static void
7587 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7588 {
7589 const char *argv[SIZEOF_ARG] = { name, "=" };
7590 int argc = 1 + (cmd == option_set_command);
7591 enum option_code error;
7593 if (!argv_from_string(argv, &argc, value))
7594 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7595 else
7596 error = cmd(argc, argv);
7598 if (error != OPT_OK)
7599 warn("Option 'tig.%s': %s", name, option_errors[error]);
7600 }
7602 static bool
7603 set_environment_variable(const char *name, const char *value)
7604 {
7605 size_t len = strlen(name) + 1 + strlen(value) + 1;
7606 char *env = malloc(len);
7608 if (env &&
7609 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7610 putenv(env) == 0)
7611 return TRUE;
7612 free(env);
7613 return FALSE;
7614 }
7616 static void
7617 set_work_tree(const char *value)
7618 {
7619 char cwd[SIZEOF_STR];
7621 if (!getcwd(cwd, sizeof(cwd)))
7622 die("Failed to get cwd path: %s", strerror(errno));
7623 if (chdir(opt_git_dir) < 0)
7624 die("Failed to chdir(%s): %s", strerror(errno));
7625 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7626 die("Failed to get git path: %s", strerror(errno));
7627 if (chdir(cwd) < 0)
7628 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7629 if (chdir(value) < 0)
7630 die("Failed to chdir(%s): %s", value, strerror(errno));
7631 if (!getcwd(cwd, sizeof(cwd)))
7632 die("Failed to get cwd path: %s", strerror(errno));
7633 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7634 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7635 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7636 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7637 opt_is_inside_work_tree = TRUE;
7638 }
7640 static int
7641 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7642 {
7643 if (!strcmp(name, "i18n.commitencoding"))
7644 string_ncopy(opt_encoding, value, valuelen);
7646 else if (!strcmp(name, "core.editor"))
7647 string_ncopy(opt_editor, value, valuelen);
7649 else if (!strcmp(name, "core.worktree"))
7650 set_work_tree(value);
7652 else if (!prefixcmp(name, "tig.color."))
7653 set_repo_config_option(name + 10, value, option_color_command);
7655 else if (!prefixcmp(name, "tig.bind."))
7656 set_repo_config_option(name + 9, value, option_bind_command);
7658 else if (!prefixcmp(name, "tig."))
7659 set_repo_config_option(name + 4, value, option_set_command);
7661 else if (*opt_head && !prefixcmp(name, "branch.") &&
7662 !strncmp(name + 7, opt_head, strlen(opt_head)))
7663 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7665 return OK;
7666 }
7668 static int
7669 load_git_config(void)
7670 {
7671 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7673 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7674 }
7676 static int
7677 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7678 {
7679 if (!opt_git_dir[0]) {
7680 string_ncopy(opt_git_dir, name, namelen);
7682 } else if (opt_is_inside_work_tree == -1) {
7683 /* This can be 3 different values depending on the
7684 * version of git being used. If git-rev-parse does not
7685 * understand --is-inside-work-tree it will simply echo
7686 * the option else either "true" or "false" is printed.
7687 * Default to true for the unknown case. */
7688 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7690 } else if (*name == '.') {
7691 string_ncopy(opt_cdup, name, namelen);
7693 } else {
7694 string_ncopy(opt_prefix, name, namelen);
7695 }
7697 return OK;
7698 }
7700 static int
7701 load_repo_info(void)
7702 {
7703 const char *rev_parse_argv[] = {
7704 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7705 "--show-cdup", "--show-prefix", NULL
7706 };
7708 return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7709 }
7712 /*
7713 * Main
7714 */
7716 static const char usage[] =
7717 "tig " TIG_VERSION " (" __DATE__ ")\n"
7718 "\n"
7719 "Usage: tig [options] [revs] [--] [paths]\n"
7720 " or: tig show [options] [revs] [--] [paths]\n"
7721 " or: tig blame [rev] path\n"
7722 " or: tig status\n"
7723 " or: tig < [git command output]\n"
7724 "\n"
7725 "Options:\n"
7726 " -v, --version Show version and exit\n"
7727 " -h, --help Show help message and exit";
7729 static void __NORETURN
7730 quit(int sig)
7731 {
7732 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7733 if (cursed)
7734 endwin();
7735 exit(0);
7736 }
7738 static void __NORETURN
7739 die(const char *err, ...)
7740 {
7741 va_list args;
7743 endwin();
7745 va_start(args, err);
7746 fputs("tig: ", stderr);
7747 vfprintf(stderr, err, args);
7748 fputs("\n", stderr);
7749 va_end(args);
7751 exit(1);
7752 }
7754 static void
7755 warn(const char *msg, ...)
7756 {
7757 va_list args;
7759 va_start(args, msg);
7760 fputs("tig warning: ", stderr);
7761 vfprintf(stderr, msg, args);
7762 fputs("\n", stderr);
7763 va_end(args);
7764 }
7766 static int
7767 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7768 {
7769 const char ***filter_args = data;
7771 return argv_append(filter_args, name) ? OK : ERR;
7772 }
7774 static void
7775 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7776 {
7777 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7778 const char **all_argv = NULL;
7780 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7781 !argv_append_array(&all_argv, argv) ||
7782 !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7783 die("Failed to split arguments");
7784 argv_free(all_argv);
7785 free(all_argv);
7786 }
7788 static void
7789 filter_options(const char *argv[])
7790 {
7791 filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7792 filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7793 filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7794 }
7796 static enum request
7797 parse_options(int argc, const char *argv[])
7798 {
7799 enum request request = REQ_VIEW_MAIN;
7800 const char *subcommand;
7801 bool seen_dashdash = FALSE;
7802 const char **filter_argv = NULL;
7803 int i;
7805 if (!isatty(STDIN_FILENO))
7806 return REQ_VIEW_PAGER;
7808 if (argc <= 1)
7809 return REQ_VIEW_MAIN;
7811 subcommand = argv[1];
7812 if (!strcmp(subcommand, "status")) {
7813 if (argc > 2)
7814 warn("ignoring arguments after `%s'", subcommand);
7815 return REQ_VIEW_STATUS;
7817 } else if (!strcmp(subcommand, "blame")) {
7818 if (argc <= 2 || argc > 4)
7819 die("invalid number of options to blame\n\n%s", usage);
7821 i = 2;
7822 if (argc == 4) {
7823 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7824 i++;
7825 }
7827 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7828 return REQ_VIEW_BLAME;
7830 } else if (!strcmp(subcommand, "show")) {
7831 request = REQ_VIEW_DIFF;
7833 } else {
7834 subcommand = NULL;
7835 }
7837 for (i = 1 + !!subcommand; i < argc; i++) {
7838 const char *opt = argv[i];
7840 if (seen_dashdash) {
7841 argv_append(&opt_file_argv, opt);
7842 continue;
7844 } else if (!strcmp(opt, "--")) {
7845 seen_dashdash = TRUE;
7846 continue;
7848 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7849 printf("tig version %s\n", TIG_VERSION);
7850 quit(0);
7852 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7853 printf("%s\n", usage);
7854 quit(0);
7856 } else if (!strcmp(opt, "--all")) {
7857 argv_append(&opt_rev_argv, opt);
7858 continue;
7859 }
7861 if (!argv_append(&filter_argv, opt))
7862 die("command too long");
7863 }
7865 if (filter_argv)
7866 filter_options(filter_argv);
7868 return request;
7869 }
7871 int
7872 main(int argc, const char *argv[])
7873 {
7874 const char *codeset = "UTF-8";
7875 enum request request = parse_options(argc, argv);
7876 struct view *view;
7877 size_t i;
7879 signal(SIGINT, quit);
7880 signal(SIGPIPE, SIG_IGN);
7882 if (setlocale(LC_ALL, "")) {
7883 codeset = nl_langinfo(CODESET);
7884 }
7886 if (load_repo_info() == ERR)
7887 die("Failed to load repo info.");
7889 if (load_options() == ERR)
7890 die("Failed to load user config.");
7892 if (load_git_config() == ERR)
7893 die("Failed to load repo config.");
7895 /* Require a git repository unless when running in pager mode. */
7896 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7897 die("Not a git repository");
7899 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7900 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7901 if (opt_iconv_in == ICONV_NONE)
7902 die("Failed to initialize character set conversion");
7903 }
7905 if (codeset && strcmp(codeset, "UTF-8")) {
7906 opt_iconv_out = iconv_open(codeset, "UTF-8");
7907 if (opt_iconv_out == ICONV_NONE)
7908 die("Failed to initialize character set conversion");
7909 }
7911 if (load_refs() == ERR)
7912 die("Failed to load refs.");
7914 foreach_view (view, i) {
7915 if (getenv(view->cmd_env))
7916 warn("Use of the %s environment variable is deprecated,"
7917 " use options or TIG_DIFF_ARGS instead",
7918 view->cmd_env);
7919 if (!argv_from_env(view->ops->argv, view->cmd_env))
7920 die("Too many arguments in the `%s` environment variable",
7921 view->cmd_env);
7922 }
7924 init_display();
7926 while (view_driver(display[current_view], request)) {
7927 int key = get_input(0);
7929 view = display[current_view];
7930 request = get_keybinding(view->keymap, key);
7932 /* Some low-level request handling. This keeps access to
7933 * status_win restricted. */
7934 switch (request) {
7935 case REQ_NONE:
7936 report("Unknown key, press %s for help",
7937 get_key(view->keymap, REQ_VIEW_HELP));
7938 break;
7939 case REQ_PROMPT:
7940 {
7941 char *cmd = read_prompt(":");
7943 if (cmd && isdigit(*cmd)) {
7944 int lineno = view->lineno + 1;
7946 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7947 select_view_line(view, lineno - 1);
7948 report("");
7949 } else {
7950 report("Unable to parse '%s' as a line number", cmd);
7951 }
7953 } else if (cmd) {
7954 struct view *next = VIEW(REQ_VIEW_PAGER);
7955 const char *argv[SIZEOF_ARG] = { "git" };
7956 int argc = 1;
7958 /* When running random commands, initially show the
7959 * command in the title. However, it maybe later be
7960 * overwritten if a commit line is selected. */
7961 string_ncopy(next->ref, cmd, strlen(cmd));
7963 if (!argv_from_string(argv, &argc, cmd)) {
7964 report("Too many arguments");
7965 } else if (!prepare_update(next, argv, NULL)) {
7966 report("Failed to format command");
7967 } else {
7968 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7969 }
7970 }
7972 request = REQ_NONE;
7973 break;
7974 }
7975 case REQ_SEARCH:
7976 case REQ_SEARCH_BACK:
7977 {
7978 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7979 char *search = read_prompt(prompt);
7981 if (search)
7982 string_ncopy(opt_search, search, strlen(search));
7983 else if (*opt_search)
7984 request = request == REQ_SEARCH ?
7985 REQ_FIND_NEXT :
7986 REQ_FIND_PREV;
7987 else
7988 request = REQ_NONE;
7989 break;
7990 }
7991 default:
7992 break;
7993 }
7994 }
7996 quit(0);
7998 return 0;
7999 }