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 static int
1048 io_load(struct io *io, const char *separators,
1049 int (*read_property)(char *, size_t, char *, size_t))
1050 {
1051 char *name;
1052 int state = OK;
1054 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1055 char *value;
1056 size_t namelen;
1057 size_t valuelen;
1059 name = chomp_string(name);
1060 namelen = strcspn(name, separators);
1062 if (name[namelen]) {
1063 name[namelen] = 0;
1064 value = chomp_string(name + namelen + 1);
1065 valuelen = strlen(value);
1067 } else {
1068 value = "";
1069 valuelen = 0;
1070 }
1072 state = read_property(name, namelen, value, valuelen);
1073 }
1075 if (state != ERR && io_error(io))
1076 state = ERR;
1077 io_done(io);
1079 return state;
1080 }
1082 static int
1083 io_run_load(const char **argv, const char *separators,
1084 int (*read_property)(char *, size_t, char *, size_t))
1085 {
1086 struct io io;
1088 if (!io_run(&io, IO_RD, NULL, argv))
1089 return ERR;
1090 return io_load(&io, separators, read_property);
1091 }
1094 /*
1095 * User requests
1096 */
1098 #define REQ_INFO \
1099 /* XXX: Keep the view request first and in sync with views[]. */ \
1100 REQ_GROUP("View switching") \
1101 REQ_(VIEW_MAIN, "Show main view"), \
1102 REQ_(VIEW_DIFF, "Show diff view"), \
1103 REQ_(VIEW_LOG, "Show log view"), \
1104 REQ_(VIEW_TREE, "Show tree view"), \
1105 REQ_(VIEW_BLOB, "Show blob view"), \
1106 REQ_(VIEW_BLAME, "Show blame view"), \
1107 REQ_(VIEW_BRANCH, "Show branch view"), \
1108 REQ_(VIEW_HELP, "Show help page"), \
1109 REQ_(VIEW_PAGER, "Show pager view"), \
1110 REQ_(VIEW_STATUS, "Show status view"), \
1111 REQ_(VIEW_STAGE, "Show stage view"), \
1112 \
1113 REQ_GROUP("View manipulation") \
1114 REQ_(ENTER, "Enter current line and scroll"), \
1115 REQ_(NEXT, "Move to next"), \
1116 REQ_(PREVIOUS, "Move to previous"), \
1117 REQ_(PARENT, "Move to parent"), \
1118 REQ_(VIEW_NEXT, "Move focus to next view"), \
1119 REQ_(REFRESH, "Reload and refresh"), \
1120 REQ_(MAXIMIZE, "Maximize the current view"), \
1121 REQ_(VIEW_CLOSE, "Close the current view"), \
1122 REQ_(QUIT, "Close all views and quit"), \
1123 \
1124 REQ_GROUP("View specific requests") \
1125 REQ_(STATUS_UPDATE, "Update file status"), \
1126 REQ_(STATUS_REVERT, "Revert file changes"), \
1127 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1128 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1129 \
1130 REQ_GROUP("Cursor navigation") \
1131 REQ_(MOVE_UP, "Move cursor one line up"), \
1132 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1133 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1134 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1135 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1136 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1137 \
1138 REQ_GROUP("Scrolling") \
1139 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1140 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1141 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1142 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1143 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1144 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1145 \
1146 REQ_GROUP("Searching") \
1147 REQ_(SEARCH, "Search the view"), \
1148 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1149 REQ_(FIND_NEXT, "Find next search match"), \
1150 REQ_(FIND_PREV, "Find previous search match"), \
1151 \
1152 REQ_GROUP("Option manipulation") \
1153 REQ_(OPTIONS, "Open option menu"), \
1154 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1155 REQ_(TOGGLE_DATE, "Toggle date display"), \
1156 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1157 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1158 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1159 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1160 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1161 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1162 \
1163 REQ_GROUP("Misc") \
1164 REQ_(PROMPT, "Bring up the prompt"), \
1165 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1166 REQ_(SHOW_VERSION, "Show version information"), \
1167 REQ_(STOP_LOADING, "Stop all loading views"), \
1168 REQ_(EDIT, "Open in editor"), \
1169 REQ_(NONE, "Do nothing")
1172 /* User action requests. */
1173 enum request {
1174 #define REQ_GROUP(help)
1175 #define REQ_(req, help) REQ_##req
1177 /* Offset all requests to avoid conflicts with ncurses getch values. */
1178 REQ_UNKNOWN = KEY_MAX + 1,
1179 REQ_OFFSET,
1180 REQ_INFO
1182 #undef REQ_GROUP
1183 #undef REQ_
1184 };
1186 struct request_info {
1187 enum request request;
1188 const char *name;
1189 int namelen;
1190 const char *help;
1191 };
1193 static const struct request_info req_info[] = {
1194 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1195 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1196 REQ_INFO
1197 #undef REQ_GROUP
1198 #undef REQ_
1199 };
1201 static enum request
1202 get_request(const char *name)
1203 {
1204 int namelen = strlen(name);
1205 int i;
1207 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1208 if (enum_equals(req_info[i], name, namelen))
1209 return req_info[i].request;
1211 return REQ_UNKNOWN;
1212 }
1215 /*
1216 * Options
1217 */
1219 /* Option and state variables. */
1220 static enum date opt_date = DATE_DEFAULT;
1221 static enum author opt_author = AUTHOR_DEFAULT;
1222 static bool opt_line_number = FALSE;
1223 static bool opt_line_graphics = TRUE;
1224 static bool opt_rev_graph = FALSE;
1225 static bool opt_show_refs = TRUE;
1226 static int opt_num_interval = 5;
1227 static double opt_hscroll = 0.50;
1228 static double opt_scale_split_view = 2.0 / 3.0;
1229 static int opt_tab_size = 8;
1230 static int opt_author_cols = AUTHOR_COLS;
1231 static char opt_path[SIZEOF_STR] = "";
1232 static char opt_file[SIZEOF_STR] = "";
1233 static char opt_ref[SIZEOF_REF] = "";
1234 static char opt_head[SIZEOF_REF] = "";
1235 static char opt_remote[SIZEOF_REF] = "";
1236 static char opt_encoding[20] = "UTF-8";
1237 static iconv_t opt_iconv_in = ICONV_NONE;
1238 static iconv_t opt_iconv_out = ICONV_NONE;
1239 static char opt_search[SIZEOF_STR] = "";
1240 static char opt_cdup[SIZEOF_STR] = "";
1241 static char opt_prefix[SIZEOF_STR] = "";
1242 static char opt_git_dir[SIZEOF_STR] = "";
1243 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1244 static char opt_editor[SIZEOF_STR] = "";
1245 static FILE *opt_tty = NULL;
1246 static const char **opt_diff_args = NULL;
1247 static const char **opt_rev_args = NULL;
1248 static const char **opt_file_args = NULL;
1250 #define is_initial_commit() (!get_ref_head())
1251 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1254 /*
1255 * Line-oriented content detection.
1256 */
1258 #define LINE_INFO \
1259 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1274 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1275 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1276 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1280 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1281 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1282 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1283 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1284 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1285 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1290 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1291 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1292 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1293 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1294 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1297 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1298 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1299 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1300 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1303 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1304 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1305 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1306 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1307 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1308 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1309 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1310 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1311 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1312 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1313 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1315 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1316 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1317 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1319 enum line_type {
1320 #define LINE(type, line, fg, bg, attr) \
1321 LINE_##type
1322 LINE_INFO,
1323 LINE_NONE
1324 #undef LINE
1325 };
1327 struct line_info {
1328 const char *name; /* Option name. */
1329 int namelen; /* Size of option name. */
1330 const char *line; /* The start of line to match. */
1331 int linelen; /* Size of string to match. */
1332 int fg, bg, attr; /* Color and text attributes for the lines. */
1333 };
1335 static struct line_info line_info[] = {
1336 #define LINE(type, line, fg, bg, attr) \
1337 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1338 LINE_INFO
1339 #undef LINE
1340 };
1342 static enum line_type
1343 get_line_type(const char *line)
1344 {
1345 int linelen = strlen(line);
1346 enum line_type type;
1348 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1349 /* Case insensitive search matches Signed-off-by lines better. */
1350 if (linelen >= line_info[type].linelen &&
1351 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1352 return type;
1354 return LINE_DEFAULT;
1355 }
1357 static inline int
1358 get_line_attr(enum line_type type)
1359 {
1360 assert(type < ARRAY_SIZE(line_info));
1361 return COLOR_PAIR(type) | line_info[type].attr;
1362 }
1364 static struct line_info *
1365 get_line_info(const char *name)
1366 {
1367 size_t namelen = strlen(name);
1368 enum line_type type;
1370 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1371 if (enum_equals(line_info[type], name, namelen))
1372 return &line_info[type];
1374 return NULL;
1375 }
1377 static void
1378 init_colors(void)
1379 {
1380 int default_bg = line_info[LINE_DEFAULT].bg;
1381 int default_fg = line_info[LINE_DEFAULT].fg;
1382 enum line_type type;
1384 start_color();
1386 if (assume_default_colors(default_fg, default_bg) == ERR) {
1387 default_bg = COLOR_BLACK;
1388 default_fg = COLOR_WHITE;
1389 }
1391 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1392 struct line_info *info = &line_info[type];
1393 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1394 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1396 init_pair(type, fg, bg);
1397 }
1398 }
1400 struct line {
1401 enum line_type type;
1403 /* State flags */
1404 unsigned int selected:1;
1405 unsigned int dirty:1;
1406 unsigned int cleareol:1;
1407 unsigned int other:16;
1409 void *data; /* User data */
1410 };
1413 /*
1414 * Keys
1415 */
1417 struct keybinding {
1418 int alias;
1419 enum request request;
1420 };
1422 static struct keybinding default_keybindings[] = {
1423 /* View switching */
1424 { 'm', REQ_VIEW_MAIN },
1425 { 'd', REQ_VIEW_DIFF },
1426 { 'l', REQ_VIEW_LOG },
1427 { 't', REQ_VIEW_TREE },
1428 { 'f', REQ_VIEW_BLOB },
1429 { 'B', REQ_VIEW_BLAME },
1430 { 'H', REQ_VIEW_BRANCH },
1431 { 'p', REQ_VIEW_PAGER },
1432 { 'h', REQ_VIEW_HELP },
1433 { 'S', REQ_VIEW_STATUS },
1434 { 'c', REQ_VIEW_STAGE },
1436 /* View manipulation */
1437 { 'q', REQ_VIEW_CLOSE },
1438 { KEY_TAB, REQ_VIEW_NEXT },
1439 { KEY_RETURN, REQ_ENTER },
1440 { KEY_UP, REQ_PREVIOUS },
1441 { KEY_CTL('P'), REQ_PREVIOUS },
1442 { KEY_DOWN, REQ_NEXT },
1443 { KEY_CTL('N'), REQ_NEXT },
1444 { 'R', REQ_REFRESH },
1445 { KEY_F(5), REQ_REFRESH },
1446 { 'O', REQ_MAXIMIZE },
1448 /* Cursor navigation */
1449 { 'k', REQ_MOVE_UP },
1450 { 'j', REQ_MOVE_DOWN },
1451 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1452 { KEY_END, REQ_MOVE_LAST_LINE },
1453 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1454 { ' ', REQ_MOVE_PAGE_DOWN },
1455 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1456 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1457 { 'b', REQ_MOVE_PAGE_UP },
1458 { '-', REQ_MOVE_PAGE_UP },
1460 /* Scrolling */
1461 { KEY_LEFT, REQ_SCROLL_LEFT },
1462 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1463 { KEY_IC, REQ_SCROLL_LINE_UP },
1464 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1465 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1466 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1467 { 'w', REQ_SCROLL_PAGE_UP },
1468 { 's', REQ_SCROLL_PAGE_DOWN },
1470 /* Searching */
1471 { '/', REQ_SEARCH },
1472 { '?', REQ_SEARCH_BACK },
1473 { 'n', REQ_FIND_NEXT },
1474 { 'N', REQ_FIND_PREV },
1476 /* Misc */
1477 { 'Q', REQ_QUIT },
1478 { 'z', REQ_STOP_LOADING },
1479 { 'v', REQ_SHOW_VERSION },
1480 { 'r', REQ_SCREEN_REDRAW },
1481 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1482 { 'o', REQ_OPTIONS },
1483 { '.', REQ_TOGGLE_LINENO },
1484 { 'D', REQ_TOGGLE_DATE },
1485 { 'A', REQ_TOGGLE_AUTHOR },
1486 { 'g', REQ_TOGGLE_REV_GRAPH },
1487 { 'F', REQ_TOGGLE_REFS },
1488 { 'I', REQ_TOGGLE_SORT_ORDER },
1489 { 'i', REQ_TOGGLE_SORT_FIELD },
1490 { ':', REQ_PROMPT },
1491 { 'u', REQ_STATUS_UPDATE },
1492 { '!', REQ_STATUS_REVERT },
1493 { 'M', REQ_STATUS_MERGE },
1494 { '@', REQ_STAGE_NEXT },
1495 { ',', REQ_PARENT },
1496 { 'e', REQ_EDIT },
1497 };
1499 #define KEYMAP_INFO \
1500 KEYMAP_(GENERIC), \
1501 KEYMAP_(MAIN), \
1502 KEYMAP_(DIFF), \
1503 KEYMAP_(LOG), \
1504 KEYMAP_(TREE), \
1505 KEYMAP_(BLOB), \
1506 KEYMAP_(BLAME), \
1507 KEYMAP_(BRANCH), \
1508 KEYMAP_(PAGER), \
1509 KEYMAP_(HELP), \
1510 KEYMAP_(STATUS), \
1511 KEYMAP_(STAGE)
1513 enum keymap {
1514 #define KEYMAP_(name) KEYMAP_##name
1515 KEYMAP_INFO
1516 #undef KEYMAP_
1517 };
1519 static const struct enum_map keymap_table[] = {
1520 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1521 KEYMAP_INFO
1522 #undef KEYMAP_
1523 };
1525 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1527 struct keybinding_table {
1528 struct keybinding *data;
1529 size_t size;
1530 };
1532 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1534 static void
1535 add_keybinding(enum keymap keymap, enum request request, int key)
1536 {
1537 struct keybinding_table *table = &keybindings[keymap];
1538 size_t i;
1540 for (i = 0; i < keybindings[keymap].size; i++) {
1541 if (keybindings[keymap].data[i].alias == key) {
1542 keybindings[keymap].data[i].request = request;
1543 return;
1544 }
1545 }
1547 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1548 if (!table->data)
1549 die("Failed to allocate keybinding");
1550 table->data[table->size].alias = key;
1551 table->data[table->size++].request = request;
1553 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1554 int i;
1556 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1557 if (default_keybindings[i].alias == key)
1558 default_keybindings[i].request = REQ_NONE;
1559 }
1560 }
1562 /* Looks for a key binding first in the given map, then in the generic map, and
1563 * lastly in the default keybindings. */
1564 static enum request
1565 get_keybinding(enum keymap keymap, int key)
1566 {
1567 size_t i;
1569 for (i = 0; i < keybindings[keymap].size; i++)
1570 if (keybindings[keymap].data[i].alias == key)
1571 return keybindings[keymap].data[i].request;
1573 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1574 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1575 return keybindings[KEYMAP_GENERIC].data[i].request;
1577 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1578 if (default_keybindings[i].alias == key)
1579 return default_keybindings[i].request;
1581 return (enum request) key;
1582 }
1585 struct key {
1586 const char *name;
1587 int value;
1588 };
1590 static const struct key key_table[] = {
1591 { "Enter", KEY_RETURN },
1592 { "Space", ' ' },
1593 { "Backspace", KEY_BACKSPACE },
1594 { "Tab", KEY_TAB },
1595 { "Escape", KEY_ESC },
1596 { "Left", KEY_LEFT },
1597 { "Right", KEY_RIGHT },
1598 { "Up", KEY_UP },
1599 { "Down", KEY_DOWN },
1600 { "Insert", KEY_IC },
1601 { "Delete", KEY_DC },
1602 { "Hash", '#' },
1603 { "Home", KEY_HOME },
1604 { "End", KEY_END },
1605 { "PageUp", KEY_PPAGE },
1606 { "PageDown", KEY_NPAGE },
1607 { "F1", KEY_F(1) },
1608 { "F2", KEY_F(2) },
1609 { "F3", KEY_F(3) },
1610 { "F4", KEY_F(4) },
1611 { "F5", KEY_F(5) },
1612 { "F6", KEY_F(6) },
1613 { "F7", KEY_F(7) },
1614 { "F8", KEY_F(8) },
1615 { "F9", KEY_F(9) },
1616 { "F10", KEY_F(10) },
1617 { "F11", KEY_F(11) },
1618 { "F12", KEY_F(12) },
1619 };
1621 static int
1622 get_key_value(const char *name)
1623 {
1624 int i;
1626 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1627 if (!strcasecmp(key_table[i].name, name))
1628 return key_table[i].value;
1630 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1631 return (int)name[1] & 0x1f;
1632 if (strlen(name) == 1 && isprint(*name))
1633 return (int) *name;
1634 return ERR;
1635 }
1637 static const char *
1638 get_key_name(int key_value)
1639 {
1640 static char key_char[] = "'X'\0";
1641 const char *seq = NULL;
1642 int key;
1644 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1645 if (key_table[key].value == key_value)
1646 seq = key_table[key].name;
1648 if (seq == NULL && key_value < 0x7f) {
1649 char *s = key_char + 1;
1651 if (key_value >= 0x20) {
1652 *s++ = key_value;
1653 } else {
1654 *s++ = '^';
1655 *s++ = 0x40 | (key_value & 0x1f);
1656 }
1657 *s++ = '\'';
1658 *s++ = '\0';
1659 seq = key_char;
1660 }
1662 return seq ? seq : "(no key)";
1663 }
1665 static bool
1666 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1667 {
1668 const char *sep = *pos > 0 ? ", " : "";
1669 const char *keyname = get_key_name(keybinding->alias);
1671 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1672 }
1674 static bool
1675 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1676 enum keymap keymap, bool all)
1677 {
1678 int i;
1680 for (i = 0; i < keybindings[keymap].size; i++) {
1681 if (keybindings[keymap].data[i].request == request) {
1682 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1683 return FALSE;
1684 if (!all)
1685 break;
1686 }
1687 }
1689 return TRUE;
1690 }
1692 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1694 static const char *
1695 get_keys(enum keymap keymap, enum request request, bool all)
1696 {
1697 static char buf[BUFSIZ];
1698 size_t pos = 0;
1699 int i;
1701 buf[pos] = 0;
1703 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1704 return "Too many keybindings!";
1705 if (pos > 0 && !all)
1706 return buf;
1708 if (keymap != KEYMAP_GENERIC) {
1709 /* Only the generic keymap includes the default keybindings when
1710 * listing all keys. */
1711 if (all)
1712 return buf;
1714 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1715 return "Too many keybindings!";
1716 if (pos)
1717 return buf;
1718 }
1720 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1721 if (default_keybindings[i].request == request) {
1722 if (!append_key(buf, &pos, &default_keybindings[i]))
1723 return "Too many keybindings!";
1724 if (!all)
1725 return buf;
1726 }
1727 }
1729 return buf;
1730 }
1732 struct run_request {
1733 enum keymap keymap;
1734 int key;
1735 const char **argv;
1736 };
1738 static struct run_request *run_request;
1739 static size_t run_requests;
1741 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1743 static enum request
1744 add_run_request(enum keymap keymap, int key, const char **argv)
1745 {
1746 struct run_request *req;
1748 if (!realloc_run_requests(&run_request, run_requests, 1))
1749 return REQ_NONE;
1751 req = &run_request[run_requests];
1752 req->keymap = keymap;
1753 req->key = key;
1754 req->argv = NULL;
1756 if (!argv_copy(&req->argv, argv))
1757 return REQ_NONE;
1759 return REQ_NONE + ++run_requests;
1760 }
1762 static struct run_request *
1763 get_run_request(enum request request)
1764 {
1765 if (request <= REQ_NONE)
1766 return NULL;
1767 return &run_request[request - REQ_NONE - 1];
1768 }
1770 static void
1771 add_builtin_run_requests(void)
1772 {
1773 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1774 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1775 const char *commit[] = { "git", "commit", NULL };
1776 const char *gc[] = { "git", "gc", NULL };
1777 struct run_request reqs[] = {
1778 { KEYMAP_MAIN, 'C', cherry_pick },
1779 { KEYMAP_STATUS, 'C', commit },
1780 { KEYMAP_BRANCH, 'C', checkout },
1781 { KEYMAP_GENERIC, 'G', gc },
1782 };
1783 int i;
1785 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1786 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1788 if (req != reqs[i].key)
1789 continue;
1790 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1791 if (req != REQ_NONE)
1792 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1793 }
1794 }
1796 /*
1797 * User config file handling.
1798 */
1800 static int config_lineno;
1801 static bool config_errors;
1802 static const char *config_msg;
1804 static const struct enum_map color_map[] = {
1805 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1806 COLOR_MAP(DEFAULT),
1807 COLOR_MAP(BLACK),
1808 COLOR_MAP(BLUE),
1809 COLOR_MAP(CYAN),
1810 COLOR_MAP(GREEN),
1811 COLOR_MAP(MAGENTA),
1812 COLOR_MAP(RED),
1813 COLOR_MAP(WHITE),
1814 COLOR_MAP(YELLOW),
1815 };
1817 static const struct enum_map attr_map[] = {
1818 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1819 ATTR_MAP(NORMAL),
1820 ATTR_MAP(BLINK),
1821 ATTR_MAP(BOLD),
1822 ATTR_MAP(DIM),
1823 ATTR_MAP(REVERSE),
1824 ATTR_MAP(STANDOUT),
1825 ATTR_MAP(UNDERLINE),
1826 };
1828 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1830 static int parse_step(double *opt, const char *arg)
1831 {
1832 *opt = atoi(arg);
1833 if (!strchr(arg, '%'))
1834 return OK;
1836 /* "Shift down" so 100% and 1 does not conflict. */
1837 *opt = (*opt - 1) / 100;
1838 if (*opt >= 1.0) {
1839 *opt = 0.99;
1840 config_msg = "Step value larger than 100%";
1841 return ERR;
1842 }
1843 if (*opt < 0.0) {
1844 *opt = 1;
1845 config_msg = "Invalid step value";
1846 return ERR;
1847 }
1848 return OK;
1849 }
1851 static int
1852 parse_int(int *opt, const char *arg, int min, int max)
1853 {
1854 int value = atoi(arg);
1856 if (min <= value && value <= max) {
1857 *opt = value;
1858 return OK;
1859 }
1861 config_msg = "Integer value out of bound";
1862 return ERR;
1863 }
1865 static bool
1866 set_color(int *color, const char *name)
1867 {
1868 if (map_enum(color, color_map, name))
1869 return TRUE;
1870 if (!prefixcmp(name, "color"))
1871 return parse_int(color, name + 5, 0, 255) == OK;
1872 return FALSE;
1873 }
1875 /* Wants: object fgcolor bgcolor [attribute] */
1876 static int
1877 option_color_command(int argc, const char *argv[])
1878 {
1879 struct line_info *info;
1881 if (argc < 3) {
1882 config_msg = "Wrong number of arguments given to color command";
1883 return ERR;
1884 }
1886 info = get_line_info(argv[0]);
1887 if (!info) {
1888 static const struct enum_map obsolete[] = {
1889 ENUM_MAP("main-delim", LINE_DELIMITER),
1890 ENUM_MAP("main-date", LINE_DATE),
1891 ENUM_MAP("main-author", LINE_AUTHOR),
1892 };
1893 int index;
1895 if (!map_enum(&index, obsolete, argv[0])) {
1896 config_msg = "Unknown color name";
1897 return ERR;
1898 }
1899 info = &line_info[index];
1900 }
1902 if (!set_color(&info->fg, argv[1]) ||
1903 !set_color(&info->bg, argv[2])) {
1904 config_msg = "Unknown color";
1905 return ERR;
1906 }
1908 info->attr = 0;
1909 while (argc-- > 3) {
1910 int attr;
1912 if (!set_attribute(&attr, argv[argc])) {
1913 config_msg = "Unknown attribute";
1914 return ERR;
1915 }
1916 info->attr |= attr;
1917 }
1919 return OK;
1920 }
1922 static int parse_bool(bool *opt, const char *arg)
1923 {
1924 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1925 ? TRUE : FALSE;
1926 return OK;
1927 }
1929 static int parse_enum_do(unsigned int *opt, const char *arg,
1930 const struct enum_map *map, size_t map_size)
1931 {
1932 bool is_true;
1934 assert(map_size > 1);
1936 if (map_enum_do(map, map_size, (int *) opt, arg))
1937 return OK;
1939 if (parse_bool(&is_true, arg) != OK)
1940 return ERR;
1942 *opt = is_true ? map[1].value : map[0].value;
1943 return OK;
1944 }
1946 #define parse_enum(opt, arg, map) \
1947 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1949 static int
1950 parse_string(char *opt, const char *arg, size_t optsize)
1951 {
1952 int arglen = strlen(arg);
1954 switch (arg[0]) {
1955 case '\"':
1956 case '\'':
1957 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1958 config_msg = "Unmatched quotation";
1959 return ERR;
1960 }
1961 arg += 1; arglen -= 2;
1962 default:
1963 string_ncopy_do(opt, optsize, arg, arglen);
1964 return OK;
1965 }
1966 }
1968 /* Wants: name = value */
1969 static int
1970 option_set_command(int argc, const char *argv[])
1971 {
1972 if (argc != 3) {
1973 config_msg = "Wrong number of arguments given to set command";
1974 return ERR;
1975 }
1977 if (strcmp(argv[1], "=")) {
1978 config_msg = "No value assigned";
1979 return ERR;
1980 }
1982 if (!strcmp(argv[0], "show-author"))
1983 return parse_enum(&opt_author, argv[2], author_map);
1985 if (!strcmp(argv[0], "show-date"))
1986 return parse_enum(&opt_date, argv[2], date_map);
1988 if (!strcmp(argv[0], "show-rev-graph"))
1989 return parse_bool(&opt_rev_graph, argv[2]);
1991 if (!strcmp(argv[0], "show-refs"))
1992 return parse_bool(&opt_show_refs, argv[2]);
1994 if (!strcmp(argv[0], "show-line-numbers"))
1995 return parse_bool(&opt_line_number, argv[2]);
1997 if (!strcmp(argv[0], "line-graphics"))
1998 return parse_bool(&opt_line_graphics, argv[2]);
2000 if (!strcmp(argv[0], "line-number-interval"))
2001 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2003 if (!strcmp(argv[0], "author-width"))
2004 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2006 if (!strcmp(argv[0], "horizontal-scroll"))
2007 return parse_step(&opt_hscroll, argv[2]);
2009 if (!strcmp(argv[0], "split-view-height"))
2010 return parse_step(&opt_scale_split_view, argv[2]);
2012 if (!strcmp(argv[0], "tab-size"))
2013 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2015 if (!strcmp(argv[0], "commit-encoding"))
2016 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2018 config_msg = "Unknown variable name";
2019 return ERR;
2020 }
2022 /* Wants: mode request key */
2023 static int
2024 option_bind_command(int argc, const char *argv[])
2025 {
2026 enum request request;
2027 int keymap = -1;
2028 int key;
2030 if (argc < 3) {
2031 config_msg = "Wrong number of arguments given to bind command";
2032 return ERR;
2033 }
2035 if (!set_keymap(&keymap, argv[0])) {
2036 config_msg = "Unknown key map";
2037 return ERR;
2038 }
2040 key = get_key_value(argv[1]);
2041 if (key == ERR) {
2042 config_msg = "Unknown key";
2043 return ERR;
2044 }
2046 request = get_request(argv[2]);
2047 if (request == REQ_UNKNOWN) {
2048 static const struct enum_map obsolete[] = {
2049 ENUM_MAP("cherry-pick", REQ_NONE),
2050 ENUM_MAP("screen-resize", REQ_NONE),
2051 ENUM_MAP("tree-parent", REQ_PARENT),
2052 };
2053 int alias;
2055 if (map_enum(&alias, obsolete, argv[2])) {
2056 if (alias != REQ_NONE)
2057 add_keybinding(keymap, alias, key);
2058 config_msg = "Obsolete request name";
2059 return ERR;
2060 }
2061 }
2062 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2063 request = add_run_request(keymap, key, argv + 2);
2064 if (request == REQ_UNKNOWN) {
2065 config_msg = "Unknown request name";
2066 return ERR;
2067 }
2069 add_keybinding(keymap, request, key);
2071 return OK;
2072 }
2074 static int
2075 set_option(const char *opt, char *value)
2076 {
2077 const char *argv[SIZEOF_ARG];
2078 int argc = 0;
2080 if (!argv_from_string(argv, &argc, value)) {
2081 config_msg = "Too many option arguments";
2082 return ERR;
2083 }
2085 if (!strcmp(opt, "color"))
2086 return option_color_command(argc, argv);
2088 if (!strcmp(opt, "set"))
2089 return option_set_command(argc, argv);
2091 if (!strcmp(opt, "bind"))
2092 return option_bind_command(argc, argv);
2094 config_msg = "Unknown option command";
2095 return ERR;
2096 }
2098 static int
2099 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2100 {
2101 int status = OK;
2103 config_lineno++;
2104 config_msg = "Internal error";
2106 /* Check for comment markers, since read_properties() will
2107 * only ensure opt and value are split at first " \t". */
2108 optlen = strcspn(opt, "#");
2109 if (optlen == 0)
2110 return OK;
2112 if (opt[optlen] != 0) {
2113 config_msg = "No option value";
2114 status = ERR;
2116 } else {
2117 /* Look for comment endings in the value. */
2118 size_t len = strcspn(value, "#");
2120 if (len < valuelen) {
2121 valuelen = len;
2122 value[valuelen] = 0;
2123 }
2125 status = set_option(opt, value);
2126 }
2128 if (status == ERR) {
2129 warn("Error on line %d, near '%.*s': %s",
2130 config_lineno, (int) optlen, opt, config_msg);
2131 config_errors = TRUE;
2132 }
2134 /* Always keep going if errors are encountered. */
2135 return OK;
2136 }
2138 static void
2139 load_option_file(const char *path)
2140 {
2141 struct io io;
2143 /* It's OK that the file doesn't exist. */
2144 if (!io_open(&io, "%s", path))
2145 return;
2147 config_lineno = 0;
2148 config_errors = FALSE;
2150 if (io_load(&io, " \t", read_option) == ERR ||
2151 config_errors == TRUE)
2152 warn("Errors while loading %s.", path);
2153 }
2155 static int
2156 load_options(void)
2157 {
2158 const char *home = getenv("HOME");
2159 const char *tigrc_user = getenv("TIGRC_USER");
2160 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2161 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2162 char buf[SIZEOF_STR];
2164 if (!tigrc_system)
2165 tigrc_system = SYSCONFDIR "/tigrc";
2166 load_option_file(tigrc_system);
2168 if (!tigrc_user) {
2169 if (!home || !string_format(buf, "%s/.tigrc", home))
2170 return ERR;
2171 tigrc_user = buf;
2172 }
2173 load_option_file(tigrc_user);
2175 /* Add _after_ loading config files to avoid adding run requests
2176 * that conflict with keybindings. */
2177 add_builtin_run_requests();
2179 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2180 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2181 int argc = 0;
2183 if (!string_format(buf, "%s", tig_diff_opts) ||
2184 !argv_from_string(diff_opts, &argc, buf))
2185 die("TIG_DIFF_OPTS contains too many arguments");
2186 else if (!argv_copy(&opt_diff_args, diff_opts))
2187 die("Failed to format TIG_DIFF_OPTS arguments");
2188 }
2190 return OK;
2191 }
2194 /*
2195 * The viewer
2196 */
2198 struct view;
2199 struct view_ops;
2201 /* The display array of active views and the index of the current view. */
2202 static struct view *display[2];
2203 static unsigned int current_view;
2205 #define foreach_displayed_view(view, i) \
2206 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2208 #define displayed_views() (display[1] != NULL ? 2 : 1)
2210 /* Current head and commit ID */
2211 static char ref_blob[SIZEOF_REF] = "";
2212 static char ref_commit[SIZEOF_REF] = "HEAD";
2213 static char ref_head[SIZEOF_REF] = "HEAD";
2214 static char ref_branch[SIZEOF_REF] = "";
2216 enum view_type {
2217 VIEW_MAIN,
2218 VIEW_DIFF,
2219 VIEW_LOG,
2220 VIEW_TREE,
2221 VIEW_BLOB,
2222 VIEW_BLAME,
2223 VIEW_BRANCH,
2224 VIEW_HELP,
2225 VIEW_PAGER,
2226 VIEW_STATUS,
2227 VIEW_STAGE,
2228 };
2230 struct view {
2231 enum view_type type; /* View type */
2232 const char *name; /* View name */
2233 const char *cmd_env; /* Command line set via environment */
2234 const char *id; /* Points to either of ref_{head,commit,blob} */
2236 struct view_ops *ops; /* View operations */
2238 enum keymap keymap; /* What keymap does this view have */
2239 bool git_dir; /* Whether the view requires a git directory. */
2241 char ref[SIZEOF_REF]; /* Hovered commit reference */
2242 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2244 int height, width; /* The width and height of the main window */
2245 WINDOW *win; /* The main window */
2246 WINDOW *title; /* The title window living below the main window */
2248 /* Navigation */
2249 unsigned long offset; /* Offset of the window top */
2250 unsigned long yoffset; /* Offset from the window side. */
2251 unsigned long lineno; /* Current line number */
2252 unsigned long p_offset; /* Previous offset of the window top */
2253 unsigned long p_yoffset;/* Previous offset from the window side */
2254 unsigned long p_lineno; /* Previous current line number */
2255 bool p_restore; /* Should the previous position be restored. */
2257 /* Searching */
2258 char grep[SIZEOF_STR]; /* Search string */
2259 regex_t *regex; /* Pre-compiled regexp */
2261 /* If non-NULL, points to the view that opened this view. If this view
2262 * is closed tig will switch back to the parent view. */
2263 struct view *parent;
2264 struct view *prev;
2266 /* Buffering */
2267 size_t lines; /* Total number of lines */
2268 struct line *line; /* Line index */
2269 unsigned int digits; /* Number of digits in the lines member. */
2271 /* Drawing */
2272 struct line *curline; /* Line currently being drawn. */
2273 enum line_type curtype; /* Attribute currently used for drawing. */
2274 unsigned long col; /* Column when drawing. */
2275 bool has_scrolled; /* View was scrolled. */
2277 /* Loading */
2278 const char **argv; /* Shell command arguments. */
2279 const char *dir; /* Directory from which to execute. */
2280 struct io io;
2281 struct io *pipe;
2282 time_t start_time;
2283 time_t update_secs;
2284 };
2286 struct view_ops {
2287 /* What type of content being displayed. Used in the title bar. */
2288 const char *type;
2289 /* Default command arguments. */
2290 const char **argv;
2291 /* Open and reads in all view content. */
2292 bool (*open)(struct view *view);
2293 /* Read one line; updates view->line. */
2294 bool (*read)(struct view *view, char *data);
2295 /* Draw one line; @lineno must be < view->height. */
2296 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2297 /* Depending on view handle a special requests. */
2298 enum request (*request)(struct view *view, enum request request, struct line *line);
2299 /* Search for regexp in a line. */
2300 bool (*grep)(struct view *view, struct line *line);
2301 /* Select line */
2302 void (*select)(struct view *view, struct line *line);
2303 /* Prepare view for loading */
2304 bool (*prepare)(struct view *view);
2305 };
2307 static struct view_ops blame_ops;
2308 static struct view_ops blob_ops;
2309 static struct view_ops diff_ops;
2310 static struct view_ops help_ops;
2311 static struct view_ops log_ops;
2312 static struct view_ops main_ops;
2313 static struct view_ops pager_ops;
2314 static struct view_ops stage_ops;
2315 static struct view_ops status_ops;
2316 static struct view_ops tree_ops;
2317 static struct view_ops branch_ops;
2319 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2320 { type, name, #env, ref, ops, map, git }
2322 #define VIEW_(id, name, ops, git, ref) \
2323 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2325 static struct view views[] = {
2326 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2327 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2328 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2329 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2330 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2331 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2332 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2333 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2334 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2335 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2336 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2337 };
2339 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2341 #define foreach_view(view, i) \
2342 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2344 #define view_is_displayed(view) \
2345 (view == display[0] || view == display[1])
2347 static enum request
2348 view_request(struct view *view, enum request request)
2349 {
2350 if (!view || !view->lines)
2351 return request;
2352 return view->ops->request(view, request, &view->line[view->lineno]);
2353 }
2356 /*
2357 * View drawing.
2358 */
2360 static inline void
2361 set_view_attr(struct view *view, enum line_type type)
2362 {
2363 if (!view->curline->selected && view->curtype != type) {
2364 (void) wattrset(view->win, get_line_attr(type));
2365 wchgat(view->win, -1, 0, type, NULL);
2366 view->curtype = type;
2367 }
2368 }
2370 static int
2371 draw_chars(struct view *view, enum line_type type, const char *string,
2372 int max_len, bool use_tilde)
2373 {
2374 static char out_buffer[BUFSIZ * 2];
2375 int len = 0;
2376 int col = 0;
2377 int trimmed = FALSE;
2378 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2380 if (max_len <= 0)
2381 return 0;
2383 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2385 set_view_attr(view, type);
2386 if (len > 0) {
2387 if (opt_iconv_out != ICONV_NONE) {
2388 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2389 size_t inlen = len + 1;
2391 char *outbuf = out_buffer;
2392 size_t outlen = sizeof(out_buffer);
2394 size_t ret;
2396 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2397 if (ret != (size_t) -1) {
2398 string = out_buffer;
2399 len = sizeof(out_buffer) - outlen;
2400 }
2401 }
2403 waddnstr(view->win, string, len);
2404 }
2405 if (trimmed && use_tilde) {
2406 set_view_attr(view, LINE_DELIMITER);
2407 waddch(view->win, '~');
2408 col++;
2409 }
2411 return col;
2412 }
2414 static int
2415 draw_space(struct view *view, enum line_type type, int max, int spaces)
2416 {
2417 static char space[] = " ";
2418 int col = 0;
2420 spaces = MIN(max, spaces);
2422 while (spaces > 0) {
2423 int len = MIN(spaces, sizeof(space) - 1);
2425 col += draw_chars(view, type, space, len, FALSE);
2426 spaces -= len;
2427 }
2429 return col;
2430 }
2432 static bool
2433 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2434 {
2435 char text[SIZEOF_STR];
2437 do {
2438 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2440 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2441 string += pos;
2442 } while (*string && view->width + view->yoffset > view->col);
2444 return view->width + view->yoffset <= view->col;
2445 }
2447 static bool
2448 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2449 {
2450 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2451 int max = view->width + view->yoffset - view->col;
2452 int i;
2454 if (max < size)
2455 size = max;
2457 set_view_attr(view, type);
2458 /* Using waddch() instead of waddnstr() ensures that
2459 * they'll be rendered correctly for the cursor line. */
2460 for (i = skip; i < size; i++)
2461 waddch(view->win, graphic[i]);
2463 view->col += size;
2464 if (size < max && skip <= size)
2465 waddch(view->win, ' ');
2466 view->col++;
2468 return view->width + view->yoffset <= view->col;
2469 }
2471 static bool
2472 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2473 {
2474 int max = MIN(view->width + view->yoffset - view->col, len);
2475 int col;
2477 if (text)
2478 col = draw_chars(view, type, text, max - 1, trim);
2479 else
2480 col = draw_space(view, type, max - 1, max - 1);
2482 view->col += col;
2483 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2484 return view->width + view->yoffset <= view->col;
2485 }
2487 static bool
2488 draw_date(struct view *view, struct time *time)
2489 {
2490 const char *date = mkdate(time, opt_date);
2491 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2493 return draw_field(view, LINE_DATE, date, cols, FALSE);
2494 }
2496 static bool
2497 draw_author(struct view *view, const char *author)
2498 {
2499 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2500 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2502 if (abbreviate && author)
2503 author = get_author_initials(author);
2505 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2506 }
2508 static bool
2509 draw_mode(struct view *view, mode_t mode)
2510 {
2511 const char *str;
2513 if (S_ISDIR(mode))
2514 str = "drwxr-xr-x";
2515 else if (S_ISLNK(mode))
2516 str = "lrwxrwxrwx";
2517 else if (S_ISGITLINK(mode))
2518 str = "m---------";
2519 else if (S_ISREG(mode) && mode & S_IXUSR)
2520 str = "-rwxr-xr-x";
2521 else if (S_ISREG(mode))
2522 str = "-rw-r--r--";
2523 else
2524 str = "----------";
2526 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2527 }
2529 static bool
2530 draw_lineno(struct view *view, unsigned int lineno)
2531 {
2532 char number[10];
2533 int digits3 = view->digits < 3 ? 3 : view->digits;
2534 int max = MIN(view->width + view->yoffset - view->col, digits3);
2535 char *text = NULL;
2536 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2538 lineno += view->offset + 1;
2539 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2540 static char fmt[] = "%1ld";
2542 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2543 if (string_format(number, fmt, lineno))
2544 text = number;
2545 }
2546 if (text)
2547 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2548 else
2549 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2550 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2551 }
2553 static bool
2554 draw_view_line(struct view *view, unsigned int lineno)
2555 {
2556 struct line *line;
2557 bool selected = (view->offset + lineno == view->lineno);
2559 assert(view_is_displayed(view));
2561 if (view->offset + lineno >= view->lines)
2562 return FALSE;
2564 line = &view->line[view->offset + lineno];
2566 wmove(view->win, lineno, 0);
2567 if (line->cleareol)
2568 wclrtoeol(view->win);
2569 view->col = 0;
2570 view->curline = line;
2571 view->curtype = LINE_NONE;
2572 line->selected = FALSE;
2573 line->dirty = line->cleareol = 0;
2575 if (selected) {
2576 set_view_attr(view, LINE_CURSOR);
2577 line->selected = TRUE;
2578 view->ops->select(view, line);
2579 }
2581 return view->ops->draw(view, line, lineno);
2582 }
2584 static void
2585 redraw_view_dirty(struct view *view)
2586 {
2587 bool dirty = FALSE;
2588 int lineno;
2590 for (lineno = 0; lineno < view->height; lineno++) {
2591 if (view->offset + lineno >= view->lines)
2592 break;
2593 if (!view->line[view->offset + lineno].dirty)
2594 continue;
2595 dirty = TRUE;
2596 if (!draw_view_line(view, lineno))
2597 break;
2598 }
2600 if (!dirty)
2601 return;
2602 wnoutrefresh(view->win);
2603 }
2605 static void
2606 redraw_view_from(struct view *view, int lineno)
2607 {
2608 assert(0 <= lineno && lineno < view->height);
2610 for (; lineno < view->height; lineno++) {
2611 if (!draw_view_line(view, lineno))
2612 break;
2613 }
2615 wnoutrefresh(view->win);
2616 }
2618 static void
2619 redraw_view(struct view *view)
2620 {
2621 werase(view->win);
2622 redraw_view_from(view, 0);
2623 }
2626 static void
2627 update_view_title(struct view *view)
2628 {
2629 char buf[SIZEOF_STR];
2630 char state[SIZEOF_STR];
2631 size_t bufpos = 0, statelen = 0;
2633 assert(view_is_displayed(view));
2635 if (view->type != VIEW_STATUS && view->lines) {
2636 unsigned int view_lines = view->offset + view->height;
2637 unsigned int lines = view->lines
2638 ? MIN(view_lines, view->lines) * 100 / view->lines
2639 : 0;
2641 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2642 view->ops->type,
2643 view->lineno + 1,
2644 view->lines,
2645 lines);
2647 }
2649 if (view->pipe) {
2650 time_t secs = time(NULL) - view->start_time;
2652 /* Three git seconds are a long time ... */
2653 if (secs > 2)
2654 string_format_from(state, &statelen, " loading %lds", secs);
2655 }
2657 string_format_from(buf, &bufpos, "[%s]", view->name);
2658 if (*view->ref && bufpos < view->width) {
2659 size_t refsize = strlen(view->ref);
2660 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2662 if (minsize < view->width)
2663 refsize = view->width - minsize + 7;
2664 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2665 }
2667 if (statelen && bufpos < view->width) {
2668 string_format_from(buf, &bufpos, "%s", state);
2669 }
2671 if (view == display[current_view])
2672 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2673 else
2674 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2676 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2677 wclrtoeol(view->title);
2678 wnoutrefresh(view->title);
2679 }
2681 static int
2682 apply_step(double step, int value)
2683 {
2684 if (step >= 1)
2685 return (int) step;
2686 value *= step + 0.01;
2687 return value ? value : 1;
2688 }
2690 static void
2691 resize_display(void)
2692 {
2693 int offset, i;
2694 struct view *base = display[0];
2695 struct view *view = display[1] ? display[1] : display[0];
2697 /* Setup window dimensions */
2699 getmaxyx(stdscr, base->height, base->width);
2701 /* Make room for the status window. */
2702 base->height -= 1;
2704 if (view != base) {
2705 /* Horizontal split. */
2706 view->width = base->width;
2707 view->height = apply_step(opt_scale_split_view, base->height);
2708 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2709 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2710 base->height -= view->height;
2712 /* Make room for the title bar. */
2713 view->height -= 1;
2714 }
2716 /* Make room for the title bar. */
2717 base->height -= 1;
2719 offset = 0;
2721 foreach_displayed_view (view, i) {
2722 if (!view->win) {
2723 view->win = newwin(view->height, 0, offset, 0);
2724 if (!view->win)
2725 die("Failed to create %s view", view->name);
2727 scrollok(view->win, FALSE);
2729 view->title = newwin(1, 0, offset + view->height, 0);
2730 if (!view->title)
2731 die("Failed to create title window");
2733 } else {
2734 wresize(view->win, view->height, view->width);
2735 mvwin(view->win, offset, 0);
2736 mvwin(view->title, offset + view->height, 0);
2737 }
2739 offset += view->height + 1;
2740 }
2741 }
2743 static void
2744 redraw_display(bool clear)
2745 {
2746 struct view *view;
2747 int i;
2749 foreach_displayed_view (view, i) {
2750 if (clear)
2751 wclear(view->win);
2752 redraw_view(view);
2753 update_view_title(view);
2754 }
2755 }
2758 /*
2759 * Option management
2760 */
2762 static void
2763 toggle_enum_option_do(unsigned int *opt, const char *help,
2764 const struct enum_map *map, size_t size)
2765 {
2766 *opt = (*opt + 1) % size;
2767 redraw_display(FALSE);
2768 report("Displaying %s %s", enum_name(map[*opt]), help);
2769 }
2771 #define toggle_enum_option(opt, help, map) \
2772 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2774 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2775 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2777 static void
2778 toggle_view_option(bool *option, const char *help)
2779 {
2780 *option = !*option;
2781 redraw_display(FALSE);
2782 report("%sabling %s", *option ? "En" : "Dis", help);
2783 }
2785 static void
2786 open_option_menu(void)
2787 {
2788 const struct menu_item menu[] = {
2789 { '.', "line numbers", &opt_line_number },
2790 { 'D', "date display", &opt_date },
2791 { 'A', "author display", &opt_author },
2792 { 'g', "revision graph display", &opt_rev_graph },
2793 { 'F', "reference display", &opt_show_refs },
2794 { 0 }
2795 };
2796 int selected = 0;
2798 if (prompt_menu("Toggle option", menu, &selected)) {
2799 if (menu[selected].data == &opt_date)
2800 toggle_date();
2801 else if (menu[selected].data == &opt_author)
2802 toggle_author();
2803 else
2804 toggle_view_option(menu[selected].data, menu[selected].text);
2805 }
2806 }
2808 static void
2809 maximize_view(struct view *view)
2810 {
2811 memset(display, 0, sizeof(display));
2812 current_view = 0;
2813 display[current_view] = view;
2814 resize_display();
2815 redraw_display(FALSE);
2816 report("");
2817 }
2820 /*
2821 * Navigation
2822 */
2824 static bool
2825 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2826 {
2827 if (lineno >= view->lines)
2828 lineno = view->lines > 0 ? view->lines - 1 : 0;
2830 if (offset > lineno || offset + view->height <= lineno) {
2831 unsigned long half = view->height / 2;
2833 if (lineno > half)
2834 offset = lineno - half;
2835 else
2836 offset = 0;
2837 }
2839 if (offset != view->offset || lineno != view->lineno) {
2840 view->offset = offset;
2841 view->lineno = lineno;
2842 return TRUE;
2843 }
2845 return FALSE;
2846 }
2848 /* Scrolling backend */
2849 static void
2850 do_scroll_view(struct view *view, int lines)
2851 {
2852 bool redraw_current_line = FALSE;
2854 /* The rendering expects the new offset. */
2855 view->offset += lines;
2857 assert(0 <= view->offset && view->offset < view->lines);
2858 assert(lines);
2860 /* Move current line into the view. */
2861 if (view->lineno < view->offset) {
2862 view->lineno = view->offset;
2863 redraw_current_line = TRUE;
2864 } else if (view->lineno >= view->offset + view->height) {
2865 view->lineno = view->offset + view->height - 1;
2866 redraw_current_line = TRUE;
2867 }
2869 assert(view->offset <= view->lineno && view->lineno < view->lines);
2871 /* Redraw the whole screen if scrolling is pointless. */
2872 if (view->height < ABS(lines)) {
2873 redraw_view(view);
2875 } else {
2876 int line = lines > 0 ? view->height - lines : 0;
2877 int end = line + ABS(lines);
2879 scrollok(view->win, TRUE);
2880 wscrl(view->win, lines);
2881 scrollok(view->win, FALSE);
2883 while (line < end && draw_view_line(view, line))
2884 line++;
2886 if (redraw_current_line)
2887 draw_view_line(view, view->lineno - view->offset);
2888 wnoutrefresh(view->win);
2889 }
2891 view->has_scrolled = TRUE;
2892 report("");
2893 }
2895 /* Scroll frontend */
2896 static void
2897 scroll_view(struct view *view, enum request request)
2898 {
2899 int lines = 1;
2901 assert(view_is_displayed(view));
2903 switch (request) {
2904 case REQ_SCROLL_LEFT:
2905 if (view->yoffset == 0) {
2906 report("Cannot scroll beyond the first column");
2907 return;
2908 }
2909 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2910 view->yoffset = 0;
2911 else
2912 view->yoffset -= apply_step(opt_hscroll, view->width);
2913 redraw_view_from(view, 0);
2914 report("");
2915 return;
2916 case REQ_SCROLL_RIGHT:
2917 view->yoffset += apply_step(opt_hscroll, view->width);
2918 redraw_view(view);
2919 report("");
2920 return;
2921 case REQ_SCROLL_PAGE_DOWN:
2922 lines = view->height;
2923 case REQ_SCROLL_LINE_DOWN:
2924 if (view->offset + lines > view->lines)
2925 lines = view->lines - view->offset;
2927 if (lines == 0 || view->offset + view->height >= view->lines) {
2928 report("Cannot scroll beyond the last line");
2929 return;
2930 }
2931 break;
2933 case REQ_SCROLL_PAGE_UP:
2934 lines = view->height;
2935 case REQ_SCROLL_LINE_UP:
2936 if (lines > view->offset)
2937 lines = view->offset;
2939 if (lines == 0) {
2940 report("Cannot scroll beyond the first line");
2941 return;
2942 }
2944 lines = -lines;
2945 break;
2947 default:
2948 die("request %d not handled in switch", request);
2949 }
2951 do_scroll_view(view, lines);
2952 }
2954 /* Cursor moving */
2955 static void
2956 move_view(struct view *view, enum request request)
2957 {
2958 int scroll_steps = 0;
2959 int steps;
2961 switch (request) {
2962 case REQ_MOVE_FIRST_LINE:
2963 steps = -view->lineno;
2964 break;
2966 case REQ_MOVE_LAST_LINE:
2967 steps = view->lines - view->lineno - 1;
2968 break;
2970 case REQ_MOVE_PAGE_UP:
2971 steps = view->height > view->lineno
2972 ? -view->lineno : -view->height;
2973 break;
2975 case REQ_MOVE_PAGE_DOWN:
2976 steps = view->lineno + view->height >= view->lines
2977 ? view->lines - view->lineno - 1 : view->height;
2978 break;
2980 case REQ_MOVE_UP:
2981 steps = -1;
2982 break;
2984 case REQ_MOVE_DOWN:
2985 steps = 1;
2986 break;
2988 default:
2989 die("request %d not handled in switch", request);
2990 }
2992 if (steps <= 0 && view->lineno == 0) {
2993 report("Cannot move beyond the first line");
2994 return;
2996 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2997 report("Cannot move beyond the last line");
2998 return;
2999 }
3001 /* Move the current line */
3002 view->lineno += steps;
3003 assert(0 <= view->lineno && view->lineno < view->lines);
3005 /* Check whether the view needs to be scrolled */
3006 if (view->lineno < view->offset ||
3007 view->lineno >= view->offset + view->height) {
3008 scroll_steps = steps;
3009 if (steps < 0 && -steps > view->offset) {
3010 scroll_steps = -view->offset;
3012 } else if (steps > 0) {
3013 if (view->lineno == view->lines - 1 &&
3014 view->lines > view->height) {
3015 scroll_steps = view->lines - view->offset - 1;
3016 if (scroll_steps >= view->height)
3017 scroll_steps -= view->height - 1;
3018 }
3019 }
3020 }
3022 if (!view_is_displayed(view)) {
3023 view->offset += scroll_steps;
3024 assert(0 <= view->offset && view->offset < view->lines);
3025 view->ops->select(view, &view->line[view->lineno]);
3026 return;
3027 }
3029 /* Repaint the old "current" line if we be scrolling */
3030 if (ABS(steps) < view->height)
3031 draw_view_line(view, view->lineno - steps - view->offset);
3033 if (scroll_steps) {
3034 do_scroll_view(view, scroll_steps);
3035 return;
3036 }
3038 /* Draw the current line */
3039 draw_view_line(view, view->lineno - view->offset);
3041 wnoutrefresh(view->win);
3042 report("");
3043 }
3046 /*
3047 * Searching
3048 */
3050 static void search_view(struct view *view, enum request request);
3052 static bool
3053 grep_text(struct view *view, const char *text[])
3054 {
3055 regmatch_t pmatch;
3056 size_t i;
3058 for (i = 0; text[i]; i++)
3059 if (*text[i] &&
3060 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3061 return TRUE;
3062 return FALSE;
3063 }
3065 static void
3066 select_view_line(struct view *view, unsigned long lineno)
3067 {
3068 unsigned long old_lineno = view->lineno;
3069 unsigned long old_offset = view->offset;
3071 if (goto_view_line(view, view->offset, lineno)) {
3072 if (view_is_displayed(view)) {
3073 if (old_offset != view->offset) {
3074 redraw_view(view);
3075 } else {
3076 draw_view_line(view, old_lineno - view->offset);
3077 draw_view_line(view, view->lineno - view->offset);
3078 wnoutrefresh(view->win);
3079 }
3080 } else {
3081 view->ops->select(view, &view->line[view->lineno]);
3082 }
3083 }
3084 }
3086 static void
3087 find_next(struct view *view, enum request request)
3088 {
3089 unsigned long lineno = view->lineno;
3090 int direction;
3092 if (!*view->grep) {
3093 if (!*opt_search)
3094 report("No previous search");
3095 else
3096 search_view(view, request);
3097 return;
3098 }
3100 switch (request) {
3101 case REQ_SEARCH:
3102 case REQ_FIND_NEXT:
3103 direction = 1;
3104 break;
3106 case REQ_SEARCH_BACK:
3107 case REQ_FIND_PREV:
3108 direction = -1;
3109 break;
3111 default:
3112 return;
3113 }
3115 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3116 lineno += direction;
3118 /* Note, lineno is unsigned long so will wrap around in which case it
3119 * will become bigger than view->lines. */
3120 for (; lineno < view->lines; lineno += direction) {
3121 if (view->ops->grep(view, &view->line[lineno])) {
3122 select_view_line(view, lineno);
3123 report("Line %ld matches '%s'", lineno + 1, view->grep);
3124 return;
3125 }
3126 }
3128 report("No match found for '%s'", view->grep);
3129 }
3131 static void
3132 search_view(struct view *view, enum request request)
3133 {
3134 int regex_err;
3136 if (view->regex) {
3137 regfree(view->regex);
3138 *view->grep = 0;
3139 } else {
3140 view->regex = calloc(1, sizeof(*view->regex));
3141 if (!view->regex)
3142 return;
3143 }
3145 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3146 if (regex_err != 0) {
3147 char buf[SIZEOF_STR] = "unknown error";
3149 regerror(regex_err, view->regex, buf, sizeof(buf));
3150 report("Search failed: %s", buf);
3151 return;
3152 }
3154 string_copy(view->grep, opt_search);
3156 find_next(view, request);
3157 }
3159 /*
3160 * Incremental updating
3161 */
3163 static void
3164 reset_view(struct view *view)
3165 {
3166 int i;
3168 for (i = 0; i < view->lines; i++)
3169 free(view->line[i].data);
3170 free(view->line);
3172 view->p_offset = view->offset;
3173 view->p_yoffset = view->yoffset;
3174 view->p_lineno = view->lineno;
3176 view->line = NULL;
3177 view->offset = 0;
3178 view->yoffset = 0;
3179 view->lines = 0;
3180 view->lineno = 0;
3181 view->vid[0] = 0;
3182 view->update_secs = 0;
3183 }
3185 static const char *
3186 format_arg(const char *name)
3187 {
3188 static struct {
3189 const char *name;
3190 size_t namelen;
3191 const char *value;
3192 const char *value_if_empty;
3193 } vars[] = {
3194 #define FORMAT_VAR(name, value, value_if_empty) \
3195 { name, STRING_SIZE(name), value, value_if_empty }
3196 FORMAT_VAR("%(directory)", opt_path, ""),
3197 FORMAT_VAR("%(file)", opt_file, ""),
3198 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3199 FORMAT_VAR("%(head)", ref_head, ""),
3200 FORMAT_VAR("%(commit)", ref_commit, ""),
3201 FORMAT_VAR("%(blob)", ref_blob, ""),
3202 FORMAT_VAR("%(branch)", ref_branch, ""),
3203 };
3204 int i;
3206 for (i = 0; i < ARRAY_SIZE(vars); i++)
3207 if (!strncmp(name, vars[i].name, vars[i].namelen))
3208 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3210 report("Unknown replacement: `%s`", name);
3211 return NULL;
3212 }
3214 static bool
3215 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3216 {
3217 char buf[SIZEOF_STR];
3218 int argc;
3220 argv_free(*dst_argv);
3222 for (argc = 0; src_argv[argc]; argc++) {
3223 const char *arg = src_argv[argc];
3224 size_t bufpos = 0;
3226 if (!strcmp(arg, "%(fileargs)")) {
3227 if (!argv_append_array(dst_argv, opt_file_args))
3228 break;
3229 continue;
3231 } else if (!strcmp(arg, "%(diffargs)")) {
3232 if (!argv_append_array(dst_argv, opt_diff_args))
3233 break;
3234 continue;
3236 } else if (!strcmp(arg, "%(revargs)")) {
3237 if (!argv_append_array(dst_argv, opt_rev_args))
3238 break;
3239 continue;
3240 }
3242 while (arg) {
3243 char *next = strstr(arg, "%(");
3244 int len = next - arg;
3245 const char *value;
3247 if (!next || !replace) {
3248 len = strlen(arg);
3249 value = "";
3251 } else {
3252 value = format_arg(next);
3254 if (!value) {
3255 return FALSE;
3256 }
3257 }
3259 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3260 return FALSE;
3262 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3263 }
3265 if (!argv_append(dst_argv, buf))
3266 break;
3267 }
3269 return src_argv[argc] == NULL;
3270 }
3272 static bool
3273 restore_view_position(struct view *view)
3274 {
3275 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3276 return FALSE;
3278 /* Changing the view position cancels the restoring. */
3279 /* FIXME: Changing back to the first line is not detected. */
3280 if (view->offset != 0 || view->lineno != 0) {
3281 view->p_restore = FALSE;
3282 return FALSE;
3283 }
3285 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3286 view_is_displayed(view))
3287 werase(view->win);
3289 view->yoffset = view->p_yoffset;
3290 view->p_restore = FALSE;
3292 return TRUE;
3293 }
3295 static void
3296 end_update(struct view *view, bool force)
3297 {
3298 if (!view->pipe)
3299 return;
3300 while (!view->ops->read(view, NULL))
3301 if (!force)
3302 return;
3303 if (force)
3304 io_kill(view->pipe);
3305 io_done(view->pipe);
3306 view->pipe = NULL;
3307 }
3309 static void
3310 setup_update(struct view *view, const char *vid)
3311 {
3312 reset_view(view);
3313 string_copy_rev(view->vid, vid);
3314 view->pipe = &view->io;
3315 view->start_time = time(NULL);
3316 }
3318 static bool
3319 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3320 {
3321 view->dir = dir;
3322 return format_argv(&view->argv, argv, replace);
3323 }
3325 static bool
3326 prepare_update(struct view *view, const char *argv[], const char *dir)
3327 {
3328 if (view->pipe)
3329 end_update(view, TRUE);
3330 return prepare_io(view, dir, argv, FALSE);
3331 }
3333 static bool
3334 start_update(struct view *view, const char **argv, const char *dir)
3335 {
3336 if (view->pipe)
3337 io_done(view->pipe);
3338 return prepare_io(view, dir, argv, FALSE) &&
3339 io_run(&view->io, IO_RD, dir, view->argv);
3340 }
3342 static bool
3343 prepare_update_file(struct view *view, const char *name)
3344 {
3345 if (view->pipe)
3346 end_update(view, TRUE);
3347 argv_free(view->argv);
3348 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3349 }
3351 static bool
3352 begin_update(struct view *view, bool refresh)
3353 {
3354 if (view->pipe)
3355 end_update(view, TRUE);
3357 if (!refresh) {
3358 if (view->ops->prepare) {
3359 if (!view->ops->prepare(view))
3360 return FALSE;
3361 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3362 return FALSE;
3363 }
3365 /* Put the current ref_* value to the view title ref
3366 * member. This is needed by the blob view. Most other
3367 * views sets it automatically after loading because the
3368 * first line is a commit line. */
3369 string_copy_rev(view->ref, view->id);
3370 }
3372 if (view->argv && view->argv[0] &&
3373 !io_run(&view->io, IO_RD, view->dir, view->argv))
3374 return FALSE;
3376 setup_update(view, view->id);
3378 return TRUE;
3379 }
3381 static bool
3382 update_view(struct view *view)
3383 {
3384 char out_buffer[BUFSIZ * 2];
3385 char *line;
3386 /* Clear the view and redraw everything since the tree sorting
3387 * might have rearranged things. */
3388 bool redraw = view->lines == 0;
3389 bool can_read = TRUE;
3391 if (!view->pipe)
3392 return TRUE;
3394 if (!io_can_read(view->pipe)) {
3395 if (view->lines == 0 && view_is_displayed(view)) {
3396 time_t secs = time(NULL) - view->start_time;
3398 if (secs > 1 && secs > view->update_secs) {
3399 if (view->update_secs == 0)
3400 redraw_view(view);
3401 update_view_title(view);
3402 view->update_secs = secs;
3403 }
3404 }
3405 return TRUE;
3406 }
3408 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3409 if (opt_iconv_in != ICONV_NONE) {
3410 ICONV_CONST char *inbuf = line;
3411 size_t inlen = strlen(line) + 1;
3413 char *outbuf = out_buffer;
3414 size_t outlen = sizeof(out_buffer);
3416 size_t ret;
3418 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3419 if (ret != (size_t) -1)
3420 line = out_buffer;
3421 }
3423 if (!view->ops->read(view, line)) {
3424 report("Allocation failure");
3425 end_update(view, TRUE);
3426 return FALSE;
3427 }
3428 }
3430 {
3431 unsigned long lines = view->lines;
3432 int digits;
3434 for (digits = 0; lines; digits++)
3435 lines /= 10;
3437 /* Keep the displayed view in sync with line number scaling. */
3438 if (digits != view->digits) {
3439 view->digits = digits;
3440 if (opt_line_number || view->type == VIEW_BLAME)
3441 redraw = TRUE;
3442 }
3443 }
3445 if (io_error(view->pipe)) {
3446 report("Failed to read: %s", io_strerror(view->pipe));
3447 end_update(view, TRUE);
3449 } else if (io_eof(view->pipe)) {
3450 if (view_is_displayed(view))
3451 report("");
3452 end_update(view, FALSE);
3453 }
3455 if (restore_view_position(view))
3456 redraw = TRUE;
3458 if (!view_is_displayed(view))
3459 return TRUE;
3461 if (redraw)
3462 redraw_view_from(view, 0);
3463 else
3464 redraw_view_dirty(view);
3466 /* Update the title _after_ the redraw so that if the redraw picks up a
3467 * commit reference in view->ref it'll be available here. */
3468 update_view_title(view);
3469 return TRUE;
3470 }
3472 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3474 static struct line *
3475 add_line_data(struct view *view, void *data, enum line_type type)
3476 {
3477 struct line *line;
3479 if (!realloc_lines(&view->line, view->lines, 1))
3480 return NULL;
3482 line = &view->line[view->lines++];
3483 memset(line, 0, sizeof(*line));
3484 line->type = type;
3485 line->data = data;
3486 line->dirty = 1;
3488 return line;
3489 }
3491 static struct line *
3492 add_line_text(struct view *view, const char *text, enum line_type type)
3493 {
3494 char *data = text ? strdup(text) : NULL;
3496 return data ? add_line_data(view, data, type) : NULL;
3497 }
3499 static struct line *
3500 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3501 {
3502 char buf[SIZEOF_STR];
3503 va_list args;
3505 va_start(args, fmt);
3506 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3507 buf[0] = 0;
3508 va_end(args);
3510 return buf[0] ? add_line_text(view, buf, type) : NULL;
3511 }
3513 /*
3514 * View opening
3515 */
3517 enum open_flags {
3518 OPEN_DEFAULT = 0, /* Use default view switching. */
3519 OPEN_SPLIT = 1, /* Split current view. */
3520 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3521 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3522 OPEN_PREPARED = 32, /* Open already prepared command. */
3523 };
3525 static void
3526 open_view(struct view *prev, enum request request, enum open_flags flags)
3527 {
3528 bool split = !!(flags & OPEN_SPLIT);
3529 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3530 bool nomaximize = !!(flags & OPEN_REFRESH);
3531 struct view *view = VIEW(request);
3532 int nviews = displayed_views();
3533 struct view *base_view = display[0];
3535 if (view == prev && nviews == 1 && !reload) {
3536 report("Already in %s view", view->name);
3537 return;
3538 }
3540 if (view->git_dir && !opt_git_dir[0]) {
3541 report("The %s view is disabled in pager view", view->name);
3542 return;
3543 }
3545 if (split) {
3546 display[1] = view;
3547 current_view = 1;
3548 view->parent = prev;
3549 } else if (!nomaximize) {
3550 /* Maximize the current view. */
3551 memset(display, 0, sizeof(display));
3552 current_view = 0;
3553 display[current_view] = view;
3554 }
3556 /* No prev signals that this is the first loaded view. */
3557 if (prev && view != prev) {
3558 view->prev = prev;
3559 }
3561 /* Resize the view when switching between split- and full-screen,
3562 * or when switching between two different full-screen views. */
3563 if (nviews != displayed_views() ||
3564 (nviews == 1 && base_view != display[0]))
3565 resize_display();
3567 if (view->ops->open) {
3568 if (view->pipe)
3569 end_update(view, TRUE);
3570 if (!view->ops->open(view)) {
3571 report("Failed to load %s view", view->name);
3572 return;
3573 }
3574 restore_view_position(view);
3576 } else if ((reload || strcmp(view->vid, view->id)) &&
3577 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3578 report("Failed to load %s view", view->name);
3579 return;
3580 }
3582 if (split && prev->lineno - prev->offset >= prev->height) {
3583 /* Take the title line into account. */
3584 int lines = prev->lineno - prev->offset - prev->height + 1;
3586 /* Scroll the view that was split if the current line is
3587 * outside the new limited view. */
3588 do_scroll_view(prev, lines);
3589 }
3591 if (prev && view != prev && split && view_is_displayed(prev)) {
3592 /* "Blur" the previous view. */
3593 update_view_title(prev);
3594 }
3596 if (view->pipe && view->lines == 0) {
3597 /* Clear the old view and let the incremental updating refill
3598 * the screen. */
3599 werase(view->win);
3600 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3601 report("");
3602 } else if (view_is_displayed(view)) {
3603 redraw_view(view);
3604 report("");
3605 }
3606 }
3608 static void
3609 open_external_viewer(const char *argv[], const char *dir)
3610 {
3611 def_prog_mode(); /* save current tty modes */
3612 endwin(); /* restore original tty modes */
3613 io_run_fg(argv, dir);
3614 fprintf(stderr, "Press Enter to continue");
3615 getc(opt_tty);
3616 reset_prog_mode();
3617 redraw_display(TRUE);
3618 }
3620 static void
3621 open_mergetool(const char *file)
3622 {
3623 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3625 open_external_viewer(mergetool_argv, opt_cdup);
3626 }
3628 static void
3629 open_editor(const char *file)
3630 {
3631 const char *editor_argv[] = { "vi", file, NULL };
3632 const char *editor;
3634 editor = getenv("GIT_EDITOR");
3635 if (!editor && *opt_editor)
3636 editor = opt_editor;
3637 if (!editor)
3638 editor = getenv("VISUAL");
3639 if (!editor)
3640 editor = getenv("EDITOR");
3641 if (!editor)
3642 editor = "vi";
3644 editor_argv[0] = editor;
3645 open_external_viewer(editor_argv, opt_cdup);
3646 }
3648 static void
3649 open_run_request(enum request request)
3650 {
3651 struct run_request *req = get_run_request(request);
3652 const char **argv = NULL;
3654 if (!req) {
3655 report("Unknown run request");
3656 return;
3657 }
3659 if (format_argv(&argv, req->argv, TRUE))
3660 open_external_viewer(argv, NULL);
3661 if (argv)
3662 argv_free(argv);
3663 free(argv);
3664 }
3666 /*
3667 * User request switch noodle
3668 */
3670 static int
3671 view_driver(struct view *view, enum request request)
3672 {
3673 int i;
3675 if (request == REQ_NONE)
3676 return TRUE;
3678 if (request > REQ_NONE) {
3679 open_run_request(request);
3680 view_request(view, REQ_REFRESH);
3681 return TRUE;
3682 }
3684 request = view_request(view, request);
3685 if (request == REQ_NONE)
3686 return TRUE;
3688 switch (request) {
3689 case REQ_MOVE_UP:
3690 case REQ_MOVE_DOWN:
3691 case REQ_MOVE_PAGE_UP:
3692 case REQ_MOVE_PAGE_DOWN:
3693 case REQ_MOVE_FIRST_LINE:
3694 case REQ_MOVE_LAST_LINE:
3695 move_view(view, request);
3696 break;
3698 case REQ_SCROLL_LEFT:
3699 case REQ_SCROLL_RIGHT:
3700 case REQ_SCROLL_LINE_DOWN:
3701 case REQ_SCROLL_LINE_UP:
3702 case REQ_SCROLL_PAGE_DOWN:
3703 case REQ_SCROLL_PAGE_UP:
3704 scroll_view(view, request);
3705 break;
3707 case REQ_VIEW_BLAME:
3708 if (!opt_file[0]) {
3709 report("No file chosen, press %s to open tree view",
3710 get_key(view->keymap, REQ_VIEW_TREE));
3711 break;
3712 }
3713 open_view(view, request, OPEN_DEFAULT);
3714 break;
3716 case REQ_VIEW_BLOB:
3717 if (!ref_blob[0]) {
3718 report("No file chosen, press %s to open tree view",
3719 get_key(view->keymap, REQ_VIEW_TREE));
3720 break;
3721 }
3722 open_view(view, request, OPEN_DEFAULT);
3723 break;
3725 case REQ_VIEW_PAGER:
3726 if (view == NULL) {
3727 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3728 die("Failed to open stdin");
3729 open_view(view, request, OPEN_PREPARED);
3730 break;
3731 }
3733 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3734 report("No pager content, press %s to run command from prompt",
3735 get_key(view->keymap, REQ_PROMPT));
3736 break;
3737 }
3738 open_view(view, request, OPEN_DEFAULT);
3739 break;
3741 case REQ_VIEW_STAGE:
3742 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3743 report("No stage content, press %s to open the status view and choose file",
3744 get_key(view->keymap, REQ_VIEW_STATUS));
3745 break;
3746 }
3747 open_view(view, request, OPEN_DEFAULT);
3748 break;
3750 case REQ_VIEW_STATUS:
3751 if (opt_is_inside_work_tree == FALSE) {
3752 report("The status view requires a working tree");
3753 break;
3754 }
3755 open_view(view, request, OPEN_DEFAULT);
3756 break;
3758 case REQ_VIEW_MAIN:
3759 case REQ_VIEW_DIFF:
3760 case REQ_VIEW_LOG:
3761 case REQ_VIEW_TREE:
3762 case REQ_VIEW_HELP:
3763 case REQ_VIEW_BRANCH:
3764 open_view(view, request, OPEN_DEFAULT);
3765 break;
3767 case REQ_NEXT:
3768 case REQ_PREVIOUS:
3769 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3771 if (view->parent) {
3772 int line;
3774 view = view->parent;
3775 line = view->lineno;
3776 move_view(view, request);
3777 if (view_is_displayed(view))
3778 update_view_title(view);
3779 if (line != view->lineno)
3780 view_request(view, REQ_ENTER);
3781 } else {
3782 move_view(view, request);
3783 }
3784 break;
3786 case REQ_VIEW_NEXT:
3787 {
3788 int nviews = displayed_views();
3789 int next_view = (current_view + 1) % nviews;
3791 if (next_view == current_view) {
3792 report("Only one view is displayed");
3793 break;
3794 }
3796 current_view = next_view;
3797 /* Blur out the title of the previous view. */
3798 update_view_title(view);
3799 report("");
3800 break;
3801 }
3802 case REQ_REFRESH:
3803 report("Refreshing is not yet supported for the %s view", view->name);
3804 break;
3806 case REQ_MAXIMIZE:
3807 if (displayed_views() == 2)
3808 maximize_view(view);
3809 break;
3811 case REQ_OPTIONS:
3812 open_option_menu();
3813 break;
3815 case REQ_TOGGLE_LINENO:
3816 toggle_view_option(&opt_line_number, "line numbers");
3817 break;
3819 case REQ_TOGGLE_DATE:
3820 toggle_date();
3821 break;
3823 case REQ_TOGGLE_AUTHOR:
3824 toggle_author();
3825 break;
3827 case REQ_TOGGLE_REV_GRAPH:
3828 toggle_view_option(&opt_rev_graph, "revision graph display");
3829 break;
3831 case REQ_TOGGLE_REFS:
3832 toggle_view_option(&opt_show_refs, "reference display");
3833 break;
3835 case REQ_TOGGLE_SORT_FIELD:
3836 case REQ_TOGGLE_SORT_ORDER:
3837 report("Sorting is not yet supported for the %s view", view->name);
3838 break;
3840 case REQ_SEARCH:
3841 case REQ_SEARCH_BACK:
3842 search_view(view, request);
3843 break;
3845 case REQ_FIND_NEXT:
3846 case REQ_FIND_PREV:
3847 find_next(view, request);
3848 break;
3850 case REQ_STOP_LOADING:
3851 foreach_view(view, i) {
3852 if (view->pipe)
3853 report("Stopped loading the %s view", view->name),
3854 end_update(view, TRUE);
3855 }
3856 break;
3858 case REQ_SHOW_VERSION:
3859 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3860 return TRUE;
3862 case REQ_SCREEN_REDRAW:
3863 redraw_display(TRUE);
3864 break;
3866 case REQ_EDIT:
3867 report("Nothing to edit");
3868 break;
3870 case REQ_ENTER:
3871 report("Nothing to enter");
3872 break;
3874 case REQ_VIEW_CLOSE:
3875 /* XXX: Mark closed views by letting view->prev point to the
3876 * view itself. Parents to closed view should never be
3877 * followed. */
3878 if (view->prev && view->prev != view) {
3879 maximize_view(view->prev);
3880 view->prev = view;
3881 break;
3882 }
3883 /* Fall-through */
3884 case REQ_QUIT:
3885 return FALSE;
3887 default:
3888 report("Unknown key, press %s for help",
3889 get_key(view->keymap, REQ_VIEW_HELP));
3890 return TRUE;
3891 }
3893 return TRUE;
3894 }
3897 /*
3898 * View backend utilities
3899 */
3901 enum sort_field {
3902 ORDERBY_NAME,
3903 ORDERBY_DATE,
3904 ORDERBY_AUTHOR,
3905 };
3907 struct sort_state {
3908 const enum sort_field *fields;
3909 size_t size, current;
3910 bool reverse;
3911 };
3913 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3914 #define get_sort_field(state) ((state).fields[(state).current])
3915 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3917 static void
3918 sort_view(struct view *view, enum request request, struct sort_state *state,
3919 int (*compare)(const void *, const void *))
3920 {
3921 switch (request) {
3922 case REQ_TOGGLE_SORT_FIELD:
3923 state->current = (state->current + 1) % state->size;
3924 break;
3926 case REQ_TOGGLE_SORT_ORDER:
3927 state->reverse = !state->reverse;
3928 break;
3929 default:
3930 die("Not a sort request");
3931 }
3933 qsort(view->line, view->lines, sizeof(*view->line), compare);
3934 redraw_view(view);
3935 }
3937 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3939 /* Small author cache to reduce memory consumption. It uses binary
3940 * search to lookup or find place to position new entries. No entries
3941 * are ever freed. */
3942 static const char *
3943 get_author(const char *name)
3944 {
3945 static const char **authors;
3946 static size_t authors_size;
3947 int from = 0, to = authors_size - 1;
3949 while (from <= to) {
3950 size_t pos = (to + from) / 2;
3951 int cmp = strcmp(name, authors[pos]);
3953 if (!cmp)
3954 return authors[pos];
3956 if (cmp < 0)
3957 to = pos - 1;
3958 else
3959 from = pos + 1;
3960 }
3962 if (!realloc_authors(&authors, authors_size, 1))
3963 return NULL;
3964 name = strdup(name);
3965 if (!name)
3966 return NULL;
3968 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3969 authors[from] = name;
3970 authors_size++;
3972 return name;
3973 }
3975 static void
3976 parse_timesec(struct time *time, const char *sec)
3977 {
3978 time->sec = (time_t) atol(sec);
3979 }
3981 static void
3982 parse_timezone(struct time *time, const char *zone)
3983 {
3984 long tz;
3986 tz = ('0' - zone[1]) * 60 * 60 * 10;
3987 tz += ('0' - zone[2]) * 60 * 60;
3988 tz += ('0' - zone[3]) * 60 * 10;
3989 tz += ('0' - zone[4]) * 60;
3991 if (zone[0] == '-')
3992 tz = -tz;
3994 time->tz = tz;
3995 time->sec -= tz;
3996 }
3998 /* Parse author lines where the name may be empty:
3999 * author <email@address.tld> 1138474660 +0100
4000 */
4001 static void
4002 parse_author_line(char *ident, const char **author, struct time *time)
4003 {
4004 char *nameend = strchr(ident, '<');
4005 char *emailend = strchr(ident, '>');
4007 if (nameend && emailend)
4008 *nameend = *emailend = 0;
4009 ident = chomp_string(ident);
4010 if (!*ident) {
4011 if (nameend)
4012 ident = chomp_string(nameend + 1);
4013 if (!*ident)
4014 ident = "Unknown";
4015 }
4017 *author = get_author(ident);
4019 /* Parse epoch and timezone */
4020 if (emailend && emailend[1] == ' ') {
4021 char *secs = emailend + 2;
4022 char *zone = strchr(secs, ' ');
4024 parse_timesec(time, secs);
4026 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4027 parse_timezone(time, zone + 1);
4028 }
4029 }
4031 /*
4032 * Pager backend
4033 */
4035 static bool
4036 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4037 {
4038 if (opt_line_number && draw_lineno(view, lineno))
4039 return TRUE;
4041 draw_text(view, line->type, line->data, TRUE);
4042 return TRUE;
4043 }
4045 static bool
4046 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4047 {
4048 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4049 char ref[SIZEOF_STR];
4051 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4052 return TRUE;
4054 /* This is the only fatal call, since it can "corrupt" the buffer. */
4055 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4056 return FALSE;
4058 return TRUE;
4059 }
4061 static void
4062 add_pager_refs(struct view *view, struct line *line)
4063 {
4064 char buf[SIZEOF_STR];
4065 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4066 struct ref_list *list;
4067 size_t bufpos = 0, i;
4068 const char *sep = "Refs: ";
4069 bool is_tag = FALSE;
4071 assert(line->type == LINE_COMMIT);
4073 list = get_ref_list(commit_id);
4074 if (!list) {
4075 if (view->type == VIEW_DIFF)
4076 goto try_add_describe_ref;
4077 return;
4078 }
4080 for (i = 0; i < list->size; i++) {
4081 struct ref *ref = list->refs[i];
4082 const char *fmt = ref->tag ? "%s[%s]" :
4083 ref->remote ? "%s<%s>" : "%s%s";
4085 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4086 return;
4087 sep = ", ";
4088 if (ref->tag)
4089 is_tag = TRUE;
4090 }
4092 if (!is_tag && view->type == VIEW_DIFF) {
4093 try_add_describe_ref:
4094 /* Add <tag>-g<commit_id> "fake" reference. */
4095 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4096 return;
4097 }
4099 if (bufpos == 0)
4100 return;
4102 add_line_text(view, buf, LINE_PP_REFS);
4103 }
4105 static bool
4106 pager_read(struct view *view, char *data)
4107 {
4108 struct line *line;
4110 if (!data)
4111 return TRUE;
4113 line = add_line_text(view, data, get_line_type(data));
4114 if (!line)
4115 return FALSE;
4117 if (line->type == LINE_COMMIT &&
4118 (view->type == VIEW_DIFF ||
4119 view->type == VIEW_LOG))
4120 add_pager_refs(view, line);
4122 return TRUE;
4123 }
4125 static enum request
4126 pager_request(struct view *view, enum request request, struct line *line)
4127 {
4128 int split = 0;
4130 if (request != REQ_ENTER)
4131 return request;
4133 if (line->type == LINE_COMMIT &&
4134 (view->type == VIEW_LOG ||
4135 view->type == VIEW_PAGER)) {
4136 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4137 split = 1;
4138 }
4140 /* Always scroll the view even if it was split. That way
4141 * you can use Enter to scroll through the log view and
4142 * split open each commit diff. */
4143 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4145 /* FIXME: A minor workaround. Scrolling the view will call report("")
4146 * but if we are scrolling a non-current view this won't properly
4147 * update the view title. */
4148 if (split)
4149 update_view_title(view);
4151 return REQ_NONE;
4152 }
4154 static bool
4155 pager_grep(struct view *view, struct line *line)
4156 {
4157 const char *text[] = { line->data, NULL };
4159 return grep_text(view, text);
4160 }
4162 static void
4163 pager_select(struct view *view, struct line *line)
4164 {
4165 if (line->type == LINE_COMMIT) {
4166 char *text = (char *)line->data + STRING_SIZE("commit ");
4168 if (view->type != VIEW_PAGER)
4169 string_copy_rev(view->ref, text);
4170 string_copy_rev(ref_commit, text);
4171 }
4172 }
4174 static struct view_ops pager_ops = {
4175 "line",
4176 NULL,
4177 NULL,
4178 pager_read,
4179 pager_draw,
4180 pager_request,
4181 pager_grep,
4182 pager_select,
4183 };
4185 static const char *log_argv[SIZEOF_ARG] = {
4186 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4187 };
4189 static enum request
4190 log_request(struct view *view, enum request request, struct line *line)
4191 {
4192 switch (request) {
4193 case REQ_REFRESH:
4194 load_refs();
4195 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4196 return REQ_NONE;
4197 default:
4198 return pager_request(view, request, line);
4199 }
4200 }
4202 static struct view_ops log_ops = {
4203 "line",
4204 log_argv,
4205 NULL,
4206 pager_read,
4207 pager_draw,
4208 log_request,
4209 pager_grep,
4210 pager_select,
4211 };
4213 static const char *diff_argv[SIZEOF_ARG] = {
4214 "git", "show", "--pretty=fuller", "--no-color", "--root",
4215 "--patch-with-stat", "--find-copies-harder", "-C",
4216 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4217 };
4219 static bool
4220 diff_read(struct view *view, char *data)
4221 {
4222 if (!data) {
4223 /* Fall back to retry if no diff will be shown. */
4224 if (view->lines == 0 && opt_file_args) {
4225 int pos = argv_size(view->argv)
4226 - argv_size(opt_file_args) - 1;
4228 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4229 for (; view->argv[pos]; pos++) {
4230 free((void *) view->argv[pos]);
4231 view->argv[pos] = NULL;
4232 }
4234 if (view->pipe)
4235 io_done(view->pipe);
4236 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4237 return FALSE;
4238 }
4239 }
4240 return TRUE;
4241 }
4243 return pager_read(view, data);
4244 }
4246 static struct view_ops diff_ops = {
4247 "line",
4248 diff_argv,
4249 NULL,
4250 diff_read,
4251 pager_draw,
4252 pager_request,
4253 pager_grep,
4254 pager_select,
4255 };
4257 /*
4258 * Help backend
4259 */
4261 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4263 static bool
4264 help_open_keymap_title(struct view *view, enum keymap keymap)
4265 {
4266 struct line *line;
4268 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4269 help_keymap_hidden[keymap] ? '+' : '-',
4270 enum_name(keymap_table[keymap]));
4271 if (line)
4272 line->other = keymap;
4274 return help_keymap_hidden[keymap];
4275 }
4277 static void
4278 help_open_keymap(struct view *view, enum keymap keymap)
4279 {
4280 const char *group = NULL;
4281 char buf[SIZEOF_STR];
4282 size_t bufpos;
4283 bool add_title = TRUE;
4284 int i;
4286 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4287 const char *key = NULL;
4289 if (req_info[i].request == REQ_NONE)
4290 continue;
4292 if (!req_info[i].request) {
4293 group = req_info[i].help;
4294 continue;
4295 }
4297 key = get_keys(keymap, req_info[i].request, TRUE);
4298 if (!key || !*key)
4299 continue;
4301 if (add_title && help_open_keymap_title(view, keymap))
4302 return;
4303 add_title = FALSE;
4305 if (group) {
4306 add_line_text(view, group, LINE_HELP_GROUP);
4307 group = NULL;
4308 }
4310 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4311 enum_name(req_info[i]), req_info[i].help);
4312 }
4314 group = "External commands:";
4316 for (i = 0; i < run_requests; i++) {
4317 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4318 const char *key;
4319 int argc;
4321 if (!req || req->keymap != keymap)
4322 continue;
4324 key = get_key_name(req->key);
4325 if (!*key)
4326 key = "(no key defined)";
4328 if (add_title && help_open_keymap_title(view, keymap))
4329 return;
4330 if (group) {
4331 add_line_text(view, group, LINE_HELP_GROUP);
4332 group = NULL;
4333 }
4335 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4336 if (!string_format_from(buf, &bufpos, "%s%s",
4337 argc ? " " : "", req->argv[argc]))
4338 return;
4340 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4341 }
4342 }
4344 static bool
4345 help_open(struct view *view)
4346 {
4347 enum keymap keymap;
4349 reset_view(view);
4350 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4351 add_line_text(view, "", LINE_DEFAULT);
4353 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4354 help_open_keymap(view, keymap);
4356 return TRUE;
4357 }
4359 static enum request
4360 help_request(struct view *view, enum request request, struct line *line)
4361 {
4362 switch (request) {
4363 case REQ_ENTER:
4364 if (line->type == LINE_HELP_KEYMAP) {
4365 help_keymap_hidden[line->other] =
4366 !help_keymap_hidden[line->other];
4367 view->p_restore = TRUE;
4368 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4369 }
4371 return REQ_NONE;
4372 default:
4373 return pager_request(view, request, line);
4374 }
4375 }
4377 static struct view_ops help_ops = {
4378 "line",
4379 NULL,
4380 help_open,
4381 NULL,
4382 pager_draw,
4383 help_request,
4384 pager_grep,
4385 pager_select,
4386 };
4389 /*
4390 * Tree backend
4391 */
4393 struct tree_stack_entry {
4394 struct tree_stack_entry *prev; /* Entry below this in the stack */
4395 unsigned long lineno; /* Line number to restore */
4396 char *name; /* Position of name in opt_path */
4397 };
4399 /* The top of the path stack. */
4400 static struct tree_stack_entry *tree_stack = NULL;
4401 unsigned long tree_lineno = 0;
4403 static void
4404 pop_tree_stack_entry(void)
4405 {
4406 struct tree_stack_entry *entry = tree_stack;
4408 tree_lineno = entry->lineno;
4409 entry->name[0] = 0;
4410 tree_stack = entry->prev;
4411 free(entry);
4412 }
4414 static void
4415 push_tree_stack_entry(const char *name, unsigned long lineno)
4416 {
4417 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4418 size_t pathlen = strlen(opt_path);
4420 if (!entry)
4421 return;
4423 entry->prev = tree_stack;
4424 entry->name = opt_path + pathlen;
4425 tree_stack = entry;
4427 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4428 pop_tree_stack_entry();
4429 return;
4430 }
4432 /* Move the current line to the first tree entry. */
4433 tree_lineno = 1;
4434 entry->lineno = lineno;
4435 }
4437 /* Parse output from git-ls-tree(1):
4438 *
4439 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4440 */
4442 #define SIZEOF_TREE_ATTR \
4443 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4445 #define SIZEOF_TREE_MODE \
4446 STRING_SIZE("100644 ")
4448 #define TREE_ID_OFFSET \
4449 STRING_SIZE("100644 blob ")
4451 struct tree_entry {
4452 char id[SIZEOF_REV];
4453 mode_t mode;
4454 struct time time; /* Date from the author ident. */
4455 const char *author; /* Author of the commit. */
4456 char name[1];
4457 };
4459 static const char *
4460 tree_path(const struct line *line)
4461 {
4462 return ((struct tree_entry *) line->data)->name;
4463 }
4465 static int
4466 tree_compare_entry(const struct line *line1, const struct line *line2)
4467 {
4468 if (line1->type != line2->type)
4469 return line1->type == LINE_TREE_DIR ? -1 : 1;
4470 return strcmp(tree_path(line1), tree_path(line2));
4471 }
4473 static const enum sort_field tree_sort_fields[] = {
4474 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4475 };
4476 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4478 static int
4479 tree_compare(const void *l1, const void *l2)
4480 {
4481 const struct line *line1 = (const struct line *) l1;
4482 const struct line *line2 = (const struct line *) l2;
4483 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4484 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4486 if (line1->type == LINE_TREE_HEAD)
4487 return -1;
4488 if (line2->type == LINE_TREE_HEAD)
4489 return 1;
4491 switch (get_sort_field(tree_sort_state)) {
4492 case ORDERBY_DATE:
4493 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4495 case ORDERBY_AUTHOR:
4496 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4498 case ORDERBY_NAME:
4499 default:
4500 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4501 }
4502 }
4505 static struct line *
4506 tree_entry(struct view *view, enum line_type type, const char *path,
4507 const char *mode, const char *id)
4508 {
4509 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4510 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4512 if (!entry || !line) {
4513 free(entry);
4514 return NULL;
4515 }
4517 strncpy(entry->name, path, strlen(path));
4518 if (mode)
4519 entry->mode = strtoul(mode, NULL, 8);
4520 if (id)
4521 string_copy_rev(entry->id, id);
4523 return line;
4524 }
4526 static bool
4527 tree_read_date(struct view *view, char *text, bool *read_date)
4528 {
4529 static const char *author_name;
4530 static struct time author_time;
4532 if (!text && *read_date) {
4533 *read_date = FALSE;
4534 return TRUE;
4536 } else if (!text) {
4537 char *path = *opt_path ? opt_path : ".";
4538 /* Find next entry to process */
4539 const char *log_file[] = {
4540 "git", "log", "--no-color", "--pretty=raw",
4541 "--cc", "--raw", view->id, "--", path, NULL
4542 };
4544 if (!view->lines) {
4545 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4546 report("Tree is empty");
4547 return TRUE;
4548 }
4550 if (!start_update(view, log_file, opt_cdup)) {
4551 report("Failed to load tree data");
4552 return TRUE;
4553 }
4555 *read_date = TRUE;
4556 return FALSE;
4558 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4559 parse_author_line(text + STRING_SIZE("author "),
4560 &author_name, &author_time);
4562 } else if (*text == ':') {
4563 char *pos;
4564 size_t annotated = 1;
4565 size_t i;
4567 pos = strchr(text, '\t');
4568 if (!pos)
4569 return TRUE;
4570 text = pos + 1;
4571 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4572 text += strlen(opt_path);
4573 pos = strchr(text, '/');
4574 if (pos)
4575 *pos = 0;
4577 for (i = 1; i < view->lines; i++) {
4578 struct line *line = &view->line[i];
4579 struct tree_entry *entry = line->data;
4581 annotated += !!entry->author;
4582 if (entry->author || strcmp(entry->name, text))
4583 continue;
4585 entry->author = author_name;
4586 entry->time = author_time;
4587 line->dirty = 1;
4588 break;
4589 }
4591 if (annotated == view->lines)
4592 io_kill(view->pipe);
4593 }
4594 return TRUE;
4595 }
4597 static bool
4598 tree_read(struct view *view, char *text)
4599 {
4600 static bool read_date = FALSE;
4601 struct tree_entry *data;
4602 struct line *entry, *line;
4603 enum line_type type;
4604 size_t textlen = text ? strlen(text) : 0;
4605 char *path = text + SIZEOF_TREE_ATTR;
4607 if (read_date || !text)
4608 return tree_read_date(view, text, &read_date);
4610 if (textlen <= SIZEOF_TREE_ATTR)
4611 return FALSE;
4612 if (view->lines == 0 &&
4613 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4614 return FALSE;
4616 /* Strip the path part ... */
4617 if (*opt_path) {
4618 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4619 size_t striplen = strlen(opt_path);
4621 if (pathlen > striplen)
4622 memmove(path, path + striplen,
4623 pathlen - striplen + 1);
4625 /* Insert "link" to parent directory. */
4626 if (view->lines == 1 &&
4627 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4628 return FALSE;
4629 }
4631 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4632 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4633 if (!entry)
4634 return FALSE;
4635 data = entry->data;
4637 /* Skip "Directory ..." and ".." line. */
4638 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4639 if (tree_compare_entry(line, entry) <= 0)
4640 continue;
4642 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4644 line->data = data;
4645 line->type = type;
4646 for (; line <= entry; line++)
4647 line->dirty = line->cleareol = 1;
4648 return TRUE;
4649 }
4651 if (tree_lineno > view->lineno) {
4652 view->lineno = tree_lineno;
4653 tree_lineno = 0;
4654 }
4656 return TRUE;
4657 }
4659 static bool
4660 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4661 {
4662 struct tree_entry *entry = line->data;
4664 if (line->type == LINE_TREE_HEAD) {
4665 if (draw_text(view, line->type, "Directory path /", TRUE))
4666 return TRUE;
4667 } else {
4668 if (draw_mode(view, entry->mode))
4669 return TRUE;
4671 if (opt_author && draw_author(view, entry->author))
4672 return TRUE;
4674 if (opt_date && draw_date(view, &entry->time))
4675 return TRUE;
4676 }
4677 if (draw_text(view, line->type, entry->name, TRUE))
4678 return TRUE;
4679 return TRUE;
4680 }
4682 static void
4683 open_blob_editor(const char *id)
4684 {
4685 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4686 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4687 int fd = mkstemp(file);
4689 if (fd == -1)
4690 report("Failed to create temporary file");
4691 else if (!io_run_append(blob_argv, fd))
4692 report("Failed to save blob data to file");
4693 else
4694 open_editor(file);
4695 if (fd != -1)
4696 unlink(file);
4697 }
4699 static enum request
4700 tree_request(struct view *view, enum request request, struct line *line)
4701 {
4702 enum open_flags flags;
4703 struct tree_entry *entry = line->data;
4705 switch (request) {
4706 case REQ_VIEW_BLAME:
4707 if (line->type != LINE_TREE_FILE) {
4708 report("Blame only supported for files");
4709 return REQ_NONE;
4710 }
4712 string_copy(opt_ref, view->vid);
4713 return request;
4715 case REQ_EDIT:
4716 if (line->type != LINE_TREE_FILE) {
4717 report("Edit only supported for files");
4718 } else if (!is_head_commit(view->vid)) {
4719 open_blob_editor(entry->id);
4720 } else {
4721 open_editor(opt_file);
4722 }
4723 return REQ_NONE;
4725 case REQ_TOGGLE_SORT_FIELD:
4726 case REQ_TOGGLE_SORT_ORDER:
4727 sort_view(view, request, &tree_sort_state, tree_compare);
4728 return REQ_NONE;
4730 case REQ_PARENT:
4731 if (!*opt_path) {
4732 /* quit view if at top of tree */
4733 return REQ_VIEW_CLOSE;
4734 }
4735 /* fake 'cd ..' */
4736 line = &view->line[1];
4737 break;
4739 case REQ_ENTER:
4740 break;
4742 default:
4743 return request;
4744 }
4746 /* Cleanup the stack if the tree view is at a different tree. */
4747 while (!*opt_path && tree_stack)
4748 pop_tree_stack_entry();
4750 switch (line->type) {
4751 case LINE_TREE_DIR:
4752 /* Depending on whether it is a subdirectory or parent link
4753 * mangle the path buffer. */
4754 if (line == &view->line[1] && *opt_path) {
4755 pop_tree_stack_entry();
4757 } else {
4758 const char *basename = tree_path(line);
4760 push_tree_stack_entry(basename, view->lineno);
4761 }
4763 /* Trees and subtrees share the same ID, so they are not not
4764 * unique like blobs. */
4765 flags = OPEN_RELOAD;
4766 request = REQ_VIEW_TREE;
4767 break;
4769 case LINE_TREE_FILE:
4770 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4771 request = REQ_VIEW_BLOB;
4772 break;
4774 default:
4775 return REQ_NONE;
4776 }
4778 open_view(view, request, flags);
4779 if (request == REQ_VIEW_TREE)
4780 view->lineno = tree_lineno;
4782 return REQ_NONE;
4783 }
4785 static bool
4786 tree_grep(struct view *view, struct line *line)
4787 {
4788 struct tree_entry *entry = line->data;
4789 const char *text[] = {
4790 entry->name,
4791 opt_author ? entry->author : "",
4792 mkdate(&entry->time, opt_date),
4793 NULL
4794 };
4796 return grep_text(view, text);
4797 }
4799 static void
4800 tree_select(struct view *view, struct line *line)
4801 {
4802 struct tree_entry *entry = line->data;
4804 if (line->type == LINE_TREE_FILE) {
4805 string_copy_rev(ref_blob, entry->id);
4806 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4808 } else if (line->type != LINE_TREE_DIR) {
4809 return;
4810 }
4812 string_copy_rev(view->ref, entry->id);
4813 }
4815 static bool
4816 tree_prepare(struct view *view)
4817 {
4818 if (view->lines == 0 && opt_prefix[0]) {
4819 char *pos = opt_prefix;
4821 while (pos && *pos) {
4822 char *end = strchr(pos, '/');
4824 if (end)
4825 *end = 0;
4826 push_tree_stack_entry(pos, 0);
4827 pos = end;
4828 if (end) {
4829 *end = '/';
4830 pos++;
4831 }
4832 }
4834 } else if (strcmp(view->vid, view->id)) {
4835 opt_path[0] = 0;
4836 }
4838 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4839 }
4841 static const char *tree_argv[SIZEOF_ARG] = {
4842 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4843 };
4845 static struct view_ops tree_ops = {
4846 "file",
4847 tree_argv,
4848 NULL,
4849 tree_read,
4850 tree_draw,
4851 tree_request,
4852 tree_grep,
4853 tree_select,
4854 tree_prepare,
4855 };
4857 static bool
4858 blob_read(struct view *view, char *line)
4859 {
4860 if (!line)
4861 return TRUE;
4862 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4863 }
4865 static enum request
4866 blob_request(struct view *view, enum request request, struct line *line)
4867 {
4868 switch (request) {
4869 case REQ_EDIT:
4870 open_blob_editor(view->vid);
4871 return REQ_NONE;
4872 default:
4873 return pager_request(view, request, line);
4874 }
4875 }
4877 static const char *blob_argv[SIZEOF_ARG] = {
4878 "git", "cat-file", "blob", "%(blob)", NULL
4879 };
4881 static struct view_ops blob_ops = {
4882 "line",
4883 blob_argv,
4884 NULL,
4885 blob_read,
4886 pager_draw,
4887 blob_request,
4888 pager_grep,
4889 pager_select,
4890 };
4892 /*
4893 * Blame backend
4894 *
4895 * Loading the blame view is a two phase job:
4896 *
4897 * 1. File content is read either using opt_file from the
4898 * filesystem or using git-cat-file.
4899 * 2. Then blame information is incrementally added by
4900 * reading output from git-blame.
4901 */
4903 struct blame_commit {
4904 char id[SIZEOF_REV]; /* SHA1 ID. */
4905 char title[128]; /* First line of the commit message. */
4906 const char *author; /* Author of the commit. */
4907 struct time time; /* Date from the author ident. */
4908 char filename[128]; /* Name of file. */
4909 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4910 char parent_filename[128]; /* Parent/previous name of file. */
4911 };
4913 struct blame {
4914 struct blame_commit *commit;
4915 unsigned long lineno;
4916 char text[1];
4917 };
4919 static bool
4920 blame_open(struct view *view)
4921 {
4922 char path[SIZEOF_STR];
4923 size_t i;
4925 if (!view->prev && *opt_prefix) {
4926 string_copy(path, opt_file);
4927 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4928 return FALSE;
4929 }
4931 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4932 const char *blame_cat_file_argv[] = {
4933 "git", "cat-file", "blob", path, NULL
4934 };
4936 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4937 !start_update(view, blame_cat_file_argv, opt_cdup))
4938 return FALSE;
4939 }
4941 /* First pass: remove multiple references to the same commit. */
4942 for (i = 0; i < view->lines; i++) {
4943 struct blame *blame = view->line[i].data;
4945 if (blame->commit && blame->commit->id[0])
4946 blame->commit->id[0] = 0;
4947 else
4948 blame->commit = NULL;
4949 }
4951 /* Second pass: free existing references. */
4952 for (i = 0; i < view->lines; i++) {
4953 struct blame *blame = view->line[i].data;
4955 if (blame->commit)
4956 free(blame->commit);
4957 }
4959 setup_update(view, opt_file);
4960 string_format(view->ref, "%s ...", opt_file);
4962 return TRUE;
4963 }
4965 static struct blame_commit *
4966 get_blame_commit(struct view *view, const char *id)
4967 {
4968 size_t i;
4970 for (i = 0; i < view->lines; i++) {
4971 struct blame *blame = view->line[i].data;
4973 if (!blame->commit)
4974 continue;
4976 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4977 return blame->commit;
4978 }
4980 {
4981 struct blame_commit *commit = calloc(1, sizeof(*commit));
4983 if (commit)
4984 string_ncopy(commit->id, id, SIZEOF_REV);
4985 return commit;
4986 }
4987 }
4989 static bool
4990 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4991 {
4992 const char *pos = *posref;
4994 *posref = NULL;
4995 pos = strchr(pos + 1, ' ');
4996 if (!pos || !isdigit(pos[1]))
4997 return FALSE;
4998 *number = atoi(pos + 1);
4999 if (*number < min || *number > max)
5000 return FALSE;
5002 *posref = pos;
5003 return TRUE;
5004 }
5006 static struct blame_commit *
5007 parse_blame_commit(struct view *view, const char *text, int *blamed)
5008 {
5009 struct blame_commit *commit;
5010 struct blame *blame;
5011 const char *pos = text + SIZEOF_REV - 2;
5012 size_t orig_lineno = 0;
5013 size_t lineno;
5014 size_t group;
5016 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5017 return NULL;
5019 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5020 !parse_number(&pos, &lineno, 1, view->lines) ||
5021 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5022 return NULL;
5024 commit = get_blame_commit(view, text);
5025 if (!commit)
5026 return NULL;
5028 *blamed += group;
5029 while (group--) {
5030 struct line *line = &view->line[lineno + group - 1];
5032 blame = line->data;
5033 blame->commit = commit;
5034 blame->lineno = orig_lineno + group - 1;
5035 line->dirty = 1;
5036 }
5038 return commit;
5039 }
5041 static bool
5042 blame_read_file(struct view *view, const char *line, bool *read_file)
5043 {
5044 if (!line) {
5045 const char *blame_argv[] = {
5046 "git", "blame", "--incremental",
5047 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5048 };
5050 if (view->lines == 0 && !view->prev)
5051 die("No blame exist for %s", view->vid);
5053 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5054 report("Failed to load blame data");
5055 return TRUE;
5056 }
5058 *read_file = FALSE;
5059 return FALSE;
5061 } else {
5062 size_t linelen = strlen(line);
5063 struct blame *blame = malloc(sizeof(*blame) + linelen);
5065 if (!blame)
5066 return FALSE;
5068 blame->commit = NULL;
5069 strncpy(blame->text, line, linelen);
5070 blame->text[linelen] = 0;
5071 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5072 }
5073 }
5075 static bool
5076 match_blame_header(const char *name, char **line)
5077 {
5078 size_t namelen = strlen(name);
5079 bool matched = !strncmp(name, *line, namelen);
5081 if (matched)
5082 *line += namelen;
5084 return matched;
5085 }
5087 static bool
5088 blame_read(struct view *view, char *line)
5089 {
5090 static struct blame_commit *commit = NULL;
5091 static int blamed = 0;
5092 static bool read_file = TRUE;
5094 if (read_file)
5095 return blame_read_file(view, line, &read_file);
5097 if (!line) {
5098 /* Reset all! */
5099 commit = NULL;
5100 blamed = 0;
5101 read_file = TRUE;
5102 string_format(view->ref, "%s", view->vid);
5103 if (view_is_displayed(view)) {
5104 update_view_title(view);
5105 redraw_view_from(view, 0);
5106 }
5107 return TRUE;
5108 }
5110 if (!commit) {
5111 commit = parse_blame_commit(view, line, &blamed);
5112 string_format(view->ref, "%s %2d%%", view->vid,
5113 view->lines ? blamed * 100 / view->lines : 0);
5115 } else if (match_blame_header("author ", &line)) {
5116 commit->author = get_author(line);
5118 } else if (match_blame_header("author-time ", &line)) {
5119 parse_timesec(&commit->time, line);
5121 } else if (match_blame_header("author-tz ", &line)) {
5122 parse_timezone(&commit->time, line);
5124 } else if (match_blame_header("summary ", &line)) {
5125 string_ncopy(commit->title, line, strlen(line));
5127 } else if (match_blame_header("previous ", &line)) {
5128 if (strlen(line) <= SIZEOF_REV)
5129 return FALSE;
5130 string_copy_rev(commit->parent_id, line);
5131 line += SIZEOF_REV;
5132 string_ncopy(commit->parent_filename, line, strlen(line));
5134 } else if (match_blame_header("filename ", &line)) {
5135 string_ncopy(commit->filename, line, strlen(line));
5136 commit = NULL;
5137 }
5139 return TRUE;
5140 }
5142 static bool
5143 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5144 {
5145 struct blame *blame = line->data;
5146 struct time *time = NULL;
5147 const char *id = NULL, *author = NULL;
5149 if (blame->commit && *blame->commit->filename) {
5150 id = blame->commit->id;
5151 author = blame->commit->author;
5152 time = &blame->commit->time;
5153 }
5155 if (opt_date && draw_date(view, time))
5156 return TRUE;
5158 if (opt_author && draw_author(view, author))
5159 return TRUE;
5161 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5162 return TRUE;
5164 if (draw_lineno(view, lineno))
5165 return TRUE;
5167 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5168 return TRUE;
5169 }
5171 static bool
5172 check_blame_commit(struct blame *blame, bool check_null_id)
5173 {
5174 if (!blame->commit)
5175 report("Commit data not loaded yet");
5176 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5177 report("No commit exist for the selected line");
5178 else
5179 return TRUE;
5180 return FALSE;
5181 }
5183 static void
5184 setup_blame_parent_line(struct view *view, struct blame *blame)
5185 {
5186 char from[SIZEOF_REF + SIZEOF_STR];
5187 char to[SIZEOF_REF + SIZEOF_STR];
5188 const char *diff_tree_argv[] = {
5189 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5190 "-U0", from, to, "--", NULL
5191 };
5192 struct io io;
5193 int parent_lineno = -1;
5194 int blamed_lineno = -1;
5195 char *line;
5197 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5198 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5199 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5200 return;
5202 while ((line = io_get(&io, '\n', TRUE))) {
5203 if (*line == '@') {
5204 char *pos = strchr(line, '+');
5206 parent_lineno = atoi(line + 4);
5207 if (pos)
5208 blamed_lineno = atoi(pos + 1);
5210 } else if (*line == '+' && parent_lineno != -1) {
5211 if (blame->lineno == blamed_lineno - 1 &&
5212 !strcmp(blame->text, line + 1)) {
5213 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5214 break;
5215 }
5216 blamed_lineno++;
5217 }
5218 }
5220 io_done(&io);
5221 }
5223 static enum request
5224 blame_request(struct view *view, enum request request, struct line *line)
5225 {
5226 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5227 struct blame *blame = line->data;
5229 switch (request) {
5230 case REQ_VIEW_BLAME:
5231 if (check_blame_commit(blame, TRUE)) {
5232 string_copy(opt_ref, blame->commit->id);
5233 string_copy(opt_file, blame->commit->filename);
5234 if (blame->lineno)
5235 view->lineno = blame->lineno;
5236 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5237 }
5238 break;
5240 case REQ_PARENT:
5241 if (!check_blame_commit(blame, TRUE))
5242 break;
5243 if (!*blame->commit->parent_id) {
5244 report("The selected commit has no parents");
5245 } else {
5246 string_copy_rev(opt_ref, blame->commit->parent_id);
5247 string_copy(opt_file, blame->commit->parent_filename);
5248 setup_blame_parent_line(view, blame);
5249 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5250 }
5251 break;
5253 case REQ_ENTER:
5254 if (!check_blame_commit(blame, FALSE))
5255 break;
5257 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5258 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5259 break;
5261 if (!strcmp(blame->commit->id, NULL_ID)) {
5262 struct view *diff = VIEW(REQ_VIEW_DIFF);
5263 const char *diff_index_argv[] = {
5264 "git", "diff-index", "--root", "--patch-with-stat",
5265 "-C", "-M", "HEAD", "--", view->vid, NULL
5266 };
5268 if (!*blame->commit->parent_id) {
5269 diff_index_argv[1] = "diff";
5270 diff_index_argv[2] = "--no-color";
5271 diff_index_argv[6] = "--";
5272 diff_index_argv[7] = "/dev/null";
5273 }
5275 if (!prepare_update(diff, diff_index_argv, NULL)) {
5276 report("Failed to allocate diff command");
5277 break;
5278 }
5279 flags |= OPEN_PREPARED;
5280 }
5282 open_view(view, REQ_VIEW_DIFF, flags);
5283 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5284 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5285 break;
5287 default:
5288 return request;
5289 }
5291 return REQ_NONE;
5292 }
5294 static bool
5295 blame_grep(struct view *view, struct line *line)
5296 {
5297 struct blame *blame = line->data;
5298 struct blame_commit *commit = blame->commit;
5299 const char *text[] = {
5300 blame->text,
5301 commit ? commit->title : "",
5302 commit ? commit->id : "",
5303 commit && opt_author ? commit->author : "",
5304 commit ? mkdate(&commit->time, opt_date) : "",
5305 NULL
5306 };
5308 return grep_text(view, text);
5309 }
5311 static void
5312 blame_select(struct view *view, struct line *line)
5313 {
5314 struct blame *blame = line->data;
5315 struct blame_commit *commit = blame->commit;
5317 if (!commit)
5318 return;
5320 if (!strcmp(commit->id, NULL_ID))
5321 string_ncopy(ref_commit, "HEAD", 4);
5322 else
5323 string_copy_rev(ref_commit, commit->id);
5324 }
5326 static struct view_ops blame_ops = {
5327 "line",
5328 NULL,
5329 blame_open,
5330 blame_read,
5331 blame_draw,
5332 blame_request,
5333 blame_grep,
5334 blame_select,
5335 };
5337 /*
5338 * Branch backend
5339 */
5341 struct branch {
5342 const char *author; /* Author of the last commit. */
5343 struct time time; /* Date of the last activity. */
5344 const struct ref *ref; /* Name and commit ID information. */
5345 };
5347 static const struct ref branch_all;
5349 static const enum sort_field branch_sort_fields[] = {
5350 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5351 };
5352 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5354 static int
5355 branch_compare(const void *l1, const void *l2)
5356 {
5357 const struct branch *branch1 = ((const struct line *) l1)->data;
5358 const struct branch *branch2 = ((const struct line *) l2)->data;
5360 switch (get_sort_field(branch_sort_state)) {
5361 case ORDERBY_DATE:
5362 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5364 case ORDERBY_AUTHOR:
5365 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5367 case ORDERBY_NAME:
5368 default:
5369 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5370 }
5371 }
5373 static bool
5374 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5375 {
5376 struct branch *branch = line->data;
5377 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5379 if (opt_date && draw_date(view, &branch->time))
5380 return TRUE;
5382 if (opt_author && draw_author(view, branch->author))
5383 return TRUE;
5385 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5386 return TRUE;
5387 }
5389 static enum request
5390 branch_request(struct view *view, enum request request, struct line *line)
5391 {
5392 struct branch *branch = line->data;
5394 switch (request) {
5395 case REQ_REFRESH:
5396 load_refs();
5397 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5398 return REQ_NONE;
5400 case REQ_TOGGLE_SORT_FIELD:
5401 case REQ_TOGGLE_SORT_ORDER:
5402 sort_view(view, request, &branch_sort_state, branch_compare);
5403 return REQ_NONE;
5405 case REQ_ENTER:
5406 {
5407 const struct ref *ref = branch->ref;
5408 const char *all_branches_argv[] = {
5409 "git", "log", "--no-color", "--pretty=raw", "--parents",
5410 "--topo-order",
5411 ref == &branch_all ? "--all" : ref->name, NULL
5412 };
5413 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5415 if (!prepare_update(main_view, all_branches_argv, NULL))
5416 report("Failed to load view of all branches");
5417 else
5418 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5419 return REQ_NONE;
5420 }
5421 default:
5422 return request;
5423 }
5424 }
5426 static bool
5427 branch_read(struct view *view, char *line)
5428 {
5429 static char id[SIZEOF_REV];
5430 struct branch *reference;
5431 size_t i;
5433 if (!line)
5434 return TRUE;
5436 switch (get_line_type(line)) {
5437 case LINE_COMMIT:
5438 string_copy_rev(id, line + STRING_SIZE("commit "));
5439 return TRUE;
5441 case LINE_AUTHOR:
5442 for (i = 0, reference = NULL; i < view->lines; i++) {
5443 struct branch *branch = view->line[i].data;
5445 if (strcmp(branch->ref->id, id))
5446 continue;
5448 view->line[i].dirty = TRUE;
5449 if (reference) {
5450 branch->author = reference->author;
5451 branch->time = reference->time;
5452 continue;
5453 }
5455 parse_author_line(line + STRING_SIZE("author "),
5456 &branch->author, &branch->time);
5457 reference = branch;
5458 }
5459 return TRUE;
5461 default:
5462 return TRUE;
5463 }
5465 }
5467 static bool
5468 branch_open_visitor(void *data, const struct ref *ref)
5469 {
5470 struct view *view = data;
5471 struct branch *branch;
5473 if (ref->tag || ref->ltag || ref->remote)
5474 return TRUE;
5476 branch = calloc(1, sizeof(*branch));
5477 if (!branch)
5478 return FALSE;
5480 branch->ref = ref;
5481 return !!add_line_data(view, branch, LINE_DEFAULT);
5482 }
5484 static bool
5485 branch_open(struct view *view)
5486 {
5487 const char *branch_log[] = {
5488 "git", "log", "--no-color", "--pretty=raw",
5489 "--simplify-by-decoration", "--all", NULL
5490 };
5492 if (!start_update(view, branch_log, NULL)) {
5493 report("Failed to load branch data");
5494 return TRUE;
5495 }
5497 setup_update(view, view->id);
5498 branch_open_visitor(view, &branch_all);
5499 foreach_ref(branch_open_visitor, view);
5500 view->p_restore = TRUE;
5502 return TRUE;
5503 }
5505 static bool
5506 branch_grep(struct view *view, struct line *line)
5507 {
5508 struct branch *branch = line->data;
5509 const char *text[] = {
5510 branch->ref->name,
5511 branch->author,
5512 NULL
5513 };
5515 return grep_text(view, text);
5516 }
5518 static void
5519 branch_select(struct view *view, struct line *line)
5520 {
5521 struct branch *branch = line->data;
5523 string_copy_rev(view->ref, branch->ref->id);
5524 string_copy_rev(ref_commit, branch->ref->id);
5525 string_copy_rev(ref_head, branch->ref->id);
5526 string_copy_rev(ref_branch, branch->ref->name);
5527 }
5529 static struct view_ops branch_ops = {
5530 "branch",
5531 NULL,
5532 branch_open,
5533 branch_read,
5534 branch_draw,
5535 branch_request,
5536 branch_grep,
5537 branch_select,
5538 };
5540 /*
5541 * Status backend
5542 */
5544 struct status {
5545 char status;
5546 struct {
5547 mode_t mode;
5548 char rev[SIZEOF_REV];
5549 char name[SIZEOF_STR];
5550 } old;
5551 struct {
5552 mode_t mode;
5553 char rev[SIZEOF_REV];
5554 char name[SIZEOF_STR];
5555 } new;
5556 };
5558 static char status_onbranch[SIZEOF_STR];
5559 static struct status stage_status;
5560 static enum line_type stage_line_type;
5561 static size_t stage_chunks;
5562 static int *stage_chunk;
5564 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5566 /* This should work even for the "On branch" line. */
5567 static inline bool
5568 status_has_none(struct view *view, struct line *line)
5569 {
5570 return line < view->line + view->lines && !line[1].data;
5571 }
5573 /* Get fields from the diff line:
5574 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5575 */
5576 static inline bool
5577 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5578 {
5579 const char *old_mode = buf + 1;
5580 const char *new_mode = buf + 8;
5581 const char *old_rev = buf + 15;
5582 const char *new_rev = buf + 56;
5583 const char *status = buf + 97;
5585 if (bufsize < 98 ||
5586 old_mode[-1] != ':' ||
5587 new_mode[-1] != ' ' ||
5588 old_rev[-1] != ' ' ||
5589 new_rev[-1] != ' ' ||
5590 status[-1] != ' ')
5591 return FALSE;
5593 file->status = *status;
5595 string_copy_rev(file->old.rev, old_rev);
5596 string_copy_rev(file->new.rev, new_rev);
5598 file->old.mode = strtoul(old_mode, NULL, 8);
5599 file->new.mode = strtoul(new_mode, NULL, 8);
5601 file->old.name[0] = file->new.name[0] = 0;
5603 return TRUE;
5604 }
5606 static bool
5607 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5608 {
5609 struct status *unmerged = NULL;
5610 char *buf;
5611 struct io io;
5613 if (!io_run(&io, IO_RD, opt_cdup, argv))
5614 return FALSE;
5616 add_line_data(view, NULL, type);
5618 while ((buf = io_get(&io, 0, TRUE))) {
5619 struct status *file = unmerged;
5621 if (!file) {
5622 file = calloc(1, sizeof(*file));
5623 if (!file || !add_line_data(view, file, type))
5624 goto error_out;
5625 }
5627 /* Parse diff info part. */
5628 if (status) {
5629 file->status = status;
5630 if (status == 'A')
5631 string_copy(file->old.rev, NULL_ID);
5633 } else if (!file->status || file == unmerged) {
5634 if (!status_get_diff(file, buf, strlen(buf)))
5635 goto error_out;
5637 buf = io_get(&io, 0, TRUE);
5638 if (!buf)
5639 break;
5641 /* Collapse all modified entries that follow an
5642 * associated unmerged entry. */
5643 if (unmerged == file) {
5644 unmerged->status = 'U';
5645 unmerged = NULL;
5646 } else if (file->status == 'U') {
5647 unmerged = file;
5648 }
5649 }
5651 /* Grab the old name for rename/copy. */
5652 if (!*file->old.name &&
5653 (file->status == 'R' || file->status == 'C')) {
5654 string_ncopy(file->old.name, buf, strlen(buf));
5656 buf = io_get(&io, 0, TRUE);
5657 if (!buf)
5658 break;
5659 }
5661 /* git-ls-files just delivers a NUL separated list of
5662 * file names similar to the second half of the
5663 * git-diff-* output. */
5664 string_ncopy(file->new.name, buf, strlen(buf));
5665 if (!*file->old.name)
5666 string_copy(file->old.name, file->new.name);
5667 file = NULL;
5668 }
5670 if (io_error(&io)) {
5671 error_out:
5672 io_done(&io);
5673 return FALSE;
5674 }
5676 if (!view->line[view->lines - 1].data)
5677 add_line_data(view, NULL, LINE_STAT_NONE);
5679 io_done(&io);
5680 return TRUE;
5681 }
5683 /* Don't show unmerged entries in the staged section. */
5684 static const char *status_diff_index_argv[] = {
5685 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5686 "--cached", "-M", "HEAD", NULL
5687 };
5689 static const char *status_diff_files_argv[] = {
5690 "git", "diff-files", "-z", NULL
5691 };
5693 static const char *status_list_other_argv[] = {
5694 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5695 };
5697 static const char *status_list_no_head_argv[] = {
5698 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5699 };
5701 static const char *update_index_argv[] = {
5702 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5703 };
5705 /* Restore the previous line number to stay in the context or select a
5706 * line with something that can be updated. */
5707 static void
5708 status_restore(struct view *view)
5709 {
5710 if (view->p_lineno >= view->lines)
5711 view->p_lineno = view->lines - 1;
5712 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5713 view->p_lineno++;
5714 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5715 view->p_lineno--;
5717 /* If the above fails, always skip the "On branch" line. */
5718 if (view->p_lineno < view->lines)
5719 view->lineno = view->p_lineno;
5720 else
5721 view->lineno = 1;
5723 if (view->lineno < view->offset)
5724 view->offset = view->lineno;
5725 else if (view->offset + view->height <= view->lineno)
5726 view->offset = view->lineno - view->height + 1;
5728 view->p_restore = FALSE;
5729 }
5731 static void
5732 status_update_onbranch(void)
5733 {
5734 static const char *paths[][2] = {
5735 { "rebase-apply/rebasing", "Rebasing" },
5736 { "rebase-apply/applying", "Applying mailbox" },
5737 { "rebase-apply/", "Rebasing mailbox" },
5738 { "rebase-merge/interactive", "Interactive rebase" },
5739 { "rebase-merge/", "Rebase merge" },
5740 { "MERGE_HEAD", "Merging" },
5741 { "BISECT_LOG", "Bisecting" },
5742 { "HEAD", "On branch" },
5743 };
5744 char buf[SIZEOF_STR];
5745 struct stat stat;
5746 int i;
5748 if (is_initial_commit()) {
5749 string_copy(status_onbranch, "Initial commit");
5750 return;
5751 }
5753 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5754 char *head = opt_head;
5756 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5757 lstat(buf, &stat) < 0)
5758 continue;
5760 if (!*opt_head) {
5761 struct io io;
5763 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5764 io_read_buf(&io, buf, sizeof(buf))) {
5765 head = buf;
5766 if (!prefixcmp(head, "refs/heads/"))
5767 head += STRING_SIZE("refs/heads/");
5768 }
5769 }
5771 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5772 string_copy(status_onbranch, opt_head);
5773 return;
5774 }
5776 string_copy(status_onbranch, "Not currently on any branch");
5777 }
5779 /* First parse staged info using git-diff-index(1), then parse unstaged
5780 * info using git-diff-files(1), and finally untracked files using
5781 * git-ls-files(1). */
5782 static bool
5783 status_open(struct view *view)
5784 {
5785 reset_view(view);
5787 add_line_data(view, NULL, LINE_STAT_HEAD);
5788 status_update_onbranch();
5790 io_run_bg(update_index_argv);
5792 if (is_initial_commit()) {
5793 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5794 return FALSE;
5795 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5796 return FALSE;
5797 }
5799 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5800 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5801 return FALSE;
5803 /* Restore the exact position or use the specialized restore
5804 * mode? */
5805 if (!view->p_restore)
5806 status_restore(view);
5807 return TRUE;
5808 }
5810 static bool
5811 status_draw(struct view *view, struct line *line, unsigned int lineno)
5812 {
5813 struct status *status = line->data;
5814 enum line_type type;
5815 const char *text;
5817 if (!status) {
5818 switch (line->type) {
5819 case LINE_STAT_STAGED:
5820 type = LINE_STAT_SECTION;
5821 text = "Changes to be committed:";
5822 break;
5824 case LINE_STAT_UNSTAGED:
5825 type = LINE_STAT_SECTION;
5826 text = "Changed but not updated:";
5827 break;
5829 case LINE_STAT_UNTRACKED:
5830 type = LINE_STAT_SECTION;
5831 text = "Untracked files:";
5832 break;
5834 case LINE_STAT_NONE:
5835 type = LINE_DEFAULT;
5836 text = " (no files)";
5837 break;
5839 case LINE_STAT_HEAD:
5840 type = LINE_STAT_HEAD;
5841 text = status_onbranch;
5842 break;
5844 default:
5845 return FALSE;
5846 }
5847 } else {
5848 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5850 buf[0] = status->status;
5851 if (draw_text(view, line->type, buf, TRUE))
5852 return TRUE;
5853 type = LINE_DEFAULT;
5854 text = status->new.name;
5855 }
5857 draw_text(view, type, text, TRUE);
5858 return TRUE;
5859 }
5861 static enum request
5862 status_load_error(struct view *view, struct view *stage, const char *path)
5863 {
5864 if (displayed_views() == 2 || display[current_view] != view)
5865 maximize_view(view);
5866 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5867 return REQ_NONE;
5868 }
5870 static enum request
5871 status_enter(struct view *view, struct line *line)
5872 {
5873 struct status *status = line->data;
5874 const char *oldpath = status ? status->old.name : NULL;
5875 /* Diffs for unmerged entries are empty when passing the new
5876 * path, so leave it empty. */
5877 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5878 const char *info;
5879 enum open_flags split;
5880 struct view *stage = VIEW(REQ_VIEW_STAGE);
5882 if (line->type == LINE_STAT_NONE ||
5883 (!status && line[1].type == LINE_STAT_NONE)) {
5884 report("No file to diff");
5885 return REQ_NONE;
5886 }
5888 switch (line->type) {
5889 case LINE_STAT_STAGED:
5890 if (is_initial_commit()) {
5891 const char *no_head_diff_argv[] = {
5892 "git", "diff", "--no-color", "--patch-with-stat",
5893 "--", "/dev/null", newpath, NULL
5894 };
5896 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5897 return status_load_error(view, stage, newpath);
5898 } else {
5899 const char *index_show_argv[] = {
5900 "git", "diff-index", "--root", "--patch-with-stat",
5901 "-C", "-M", "--cached", "HEAD", "--",
5902 oldpath, newpath, NULL
5903 };
5905 if (!prepare_update(stage, index_show_argv, opt_cdup))
5906 return status_load_error(view, stage, newpath);
5907 }
5909 if (status)
5910 info = "Staged changes to %s";
5911 else
5912 info = "Staged changes";
5913 break;
5915 case LINE_STAT_UNSTAGED:
5916 {
5917 const char *files_show_argv[] = {
5918 "git", "diff-files", "--root", "--patch-with-stat",
5919 "-C", "-M", "--", oldpath, newpath, NULL
5920 };
5922 if (!prepare_update(stage, files_show_argv, opt_cdup))
5923 return status_load_error(view, stage, newpath);
5924 if (status)
5925 info = "Unstaged changes to %s";
5926 else
5927 info = "Unstaged changes";
5928 break;
5929 }
5930 case LINE_STAT_UNTRACKED:
5931 if (!newpath) {
5932 report("No file to show");
5933 return REQ_NONE;
5934 }
5936 if (!suffixcmp(status->new.name, -1, "/")) {
5937 report("Cannot display a directory");
5938 return REQ_NONE;
5939 }
5941 if (!prepare_update_file(stage, newpath))
5942 return status_load_error(view, stage, newpath);
5943 info = "Untracked file %s";
5944 break;
5946 case LINE_STAT_HEAD:
5947 return REQ_NONE;
5949 default:
5950 die("line type %d not handled in switch", line->type);
5951 }
5953 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5954 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5955 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5956 if (status) {
5957 stage_status = *status;
5958 } else {
5959 memset(&stage_status, 0, sizeof(stage_status));
5960 }
5962 stage_line_type = line->type;
5963 stage_chunks = 0;
5964 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5965 }
5967 return REQ_NONE;
5968 }
5970 static bool
5971 status_exists(struct status *status, enum line_type type)
5972 {
5973 struct view *view = VIEW(REQ_VIEW_STATUS);
5974 unsigned long lineno;
5976 for (lineno = 0; lineno < view->lines; lineno++) {
5977 struct line *line = &view->line[lineno];
5978 struct status *pos = line->data;
5980 if (line->type != type)
5981 continue;
5982 if (!pos && (!status || !status->status) && line[1].data) {
5983 select_view_line(view, lineno);
5984 return TRUE;
5985 }
5986 if (pos && !strcmp(status->new.name, pos->new.name)) {
5987 select_view_line(view, lineno);
5988 return TRUE;
5989 }
5990 }
5992 return FALSE;
5993 }
5996 static bool
5997 status_update_prepare(struct io *io, enum line_type type)
5998 {
5999 const char *staged_argv[] = {
6000 "git", "update-index", "-z", "--index-info", NULL
6001 };
6002 const char *others_argv[] = {
6003 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6004 };
6006 switch (type) {
6007 case LINE_STAT_STAGED:
6008 return io_run(io, IO_WR, opt_cdup, staged_argv);
6010 case LINE_STAT_UNSTAGED:
6011 case LINE_STAT_UNTRACKED:
6012 return io_run(io, IO_WR, opt_cdup, others_argv);
6014 default:
6015 die("line type %d not handled in switch", type);
6016 return FALSE;
6017 }
6018 }
6020 static bool
6021 status_update_write(struct io *io, struct status *status, enum line_type type)
6022 {
6023 char buf[SIZEOF_STR];
6024 size_t bufsize = 0;
6026 switch (type) {
6027 case LINE_STAT_STAGED:
6028 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6029 status->old.mode,
6030 status->old.rev,
6031 status->old.name, 0))
6032 return FALSE;
6033 break;
6035 case LINE_STAT_UNSTAGED:
6036 case LINE_STAT_UNTRACKED:
6037 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6038 return FALSE;
6039 break;
6041 default:
6042 die("line type %d not handled in switch", type);
6043 }
6045 return io_write(io, buf, bufsize);
6046 }
6048 static bool
6049 status_update_file(struct status *status, enum line_type type)
6050 {
6051 struct io io;
6052 bool result;
6054 if (!status_update_prepare(&io, type))
6055 return FALSE;
6057 result = status_update_write(&io, status, type);
6058 return io_done(&io) && result;
6059 }
6061 static bool
6062 status_update_files(struct view *view, struct line *line)
6063 {
6064 char buf[sizeof(view->ref)];
6065 struct io io;
6066 bool result = TRUE;
6067 struct line *pos = view->line + view->lines;
6068 int files = 0;
6069 int file, done;
6070 int cursor_y = -1, cursor_x = -1;
6072 if (!status_update_prepare(&io, line->type))
6073 return FALSE;
6075 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6076 files++;
6078 string_copy(buf, view->ref);
6079 getsyx(cursor_y, cursor_x);
6080 for (file = 0, done = 5; result && file < files; line++, file++) {
6081 int almost_done = file * 100 / files;
6083 if (almost_done > done) {
6084 done = almost_done;
6085 string_format(view->ref, "updating file %u of %u (%d%% done)",
6086 file, files, done);
6087 update_view_title(view);
6088 setsyx(cursor_y, cursor_x);
6089 doupdate();
6090 }
6091 result = status_update_write(&io, line->data, line->type);
6092 }
6093 string_copy(view->ref, buf);
6095 return io_done(&io) && result;
6096 }
6098 static bool
6099 status_update(struct view *view)
6100 {
6101 struct line *line = &view->line[view->lineno];
6103 assert(view->lines);
6105 if (!line->data) {
6106 /* This should work even for the "On branch" line. */
6107 if (line < view->line + view->lines && !line[1].data) {
6108 report("Nothing to update");
6109 return FALSE;
6110 }
6112 if (!status_update_files(view, line + 1)) {
6113 report("Failed to update file status");
6114 return FALSE;
6115 }
6117 } else if (!status_update_file(line->data, line->type)) {
6118 report("Failed to update file status");
6119 return FALSE;
6120 }
6122 return TRUE;
6123 }
6125 static bool
6126 status_revert(struct status *status, enum line_type type, bool has_none)
6127 {
6128 if (!status || type != LINE_STAT_UNSTAGED) {
6129 if (type == LINE_STAT_STAGED) {
6130 report("Cannot revert changes to staged files");
6131 } else if (type == LINE_STAT_UNTRACKED) {
6132 report("Cannot revert changes to untracked files");
6133 } else if (has_none) {
6134 report("Nothing to revert");
6135 } else {
6136 report("Cannot revert changes to multiple files");
6137 }
6139 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6140 char mode[10] = "100644";
6141 const char *reset_argv[] = {
6142 "git", "update-index", "--cacheinfo", mode,
6143 status->old.rev, status->old.name, NULL
6144 };
6145 const char *checkout_argv[] = {
6146 "git", "checkout", "--", status->old.name, NULL
6147 };
6149 if (status->status == 'U') {
6150 string_format(mode, "%5o", status->old.mode);
6152 if (status->old.mode == 0 && status->new.mode == 0) {
6153 reset_argv[2] = "--force-remove";
6154 reset_argv[3] = status->old.name;
6155 reset_argv[4] = NULL;
6156 }
6158 if (!io_run_fg(reset_argv, opt_cdup))
6159 return FALSE;
6160 if (status->old.mode == 0 && status->new.mode == 0)
6161 return TRUE;
6162 }
6164 return io_run_fg(checkout_argv, opt_cdup);
6165 }
6167 return FALSE;
6168 }
6170 static enum request
6171 status_request(struct view *view, enum request request, struct line *line)
6172 {
6173 struct status *status = line->data;
6175 switch (request) {
6176 case REQ_STATUS_UPDATE:
6177 if (!status_update(view))
6178 return REQ_NONE;
6179 break;
6181 case REQ_STATUS_REVERT:
6182 if (!status_revert(status, line->type, status_has_none(view, line)))
6183 return REQ_NONE;
6184 break;
6186 case REQ_STATUS_MERGE:
6187 if (!status || status->status != 'U') {
6188 report("Merging only possible for files with unmerged status ('U').");
6189 return REQ_NONE;
6190 }
6191 open_mergetool(status->new.name);
6192 break;
6194 case REQ_EDIT:
6195 if (!status)
6196 return request;
6197 if (status->status == 'D') {
6198 report("File has been deleted.");
6199 return REQ_NONE;
6200 }
6202 open_editor(status->new.name);
6203 break;
6205 case REQ_VIEW_BLAME:
6206 if (status)
6207 opt_ref[0] = 0;
6208 return request;
6210 case REQ_ENTER:
6211 /* After returning the status view has been split to
6212 * show the stage view. No further reloading is
6213 * necessary. */
6214 return status_enter(view, line);
6216 case REQ_REFRESH:
6217 /* Simply reload the view. */
6218 break;
6220 default:
6221 return request;
6222 }
6224 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6226 return REQ_NONE;
6227 }
6229 static void
6230 status_select(struct view *view, struct line *line)
6231 {
6232 struct status *status = line->data;
6233 char file[SIZEOF_STR] = "all files";
6234 const char *text;
6235 const char *key;
6237 if (status && !string_format(file, "'%s'", status->new.name))
6238 return;
6240 if (!status && line[1].type == LINE_STAT_NONE)
6241 line++;
6243 switch (line->type) {
6244 case LINE_STAT_STAGED:
6245 text = "Press %s to unstage %s for commit";
6246 break;
6248 case LINE_STAT_UNSTAGED:
6249 text = "Press %s to stage %s for commit";
6250 break;
6252 case LINE_STAT_UNTRACKED:
6253 text = "Press %s to stage %s for addition";
6254 break;
6256 case LINE_STAT_HEAD:
6257 case LINE_STAT_NONE:
6258 text = "Nothing to update";
6259 break;
6261 default:
6262 die("line type %d not handled in switch", line->type);
6263 }
6265 if (status && status->status == 'U') {
6266 text = "Press %s to resolve conflict in %s";
6267 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6269 } else {
6270 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6271 }
6273 string_format(view->ref, text, key, file);
6274 if (status)
6275 string_copy(opt_file, status->new.name);
6276 }
6278 static bool
6279 status_grep(struct view *view, struct line *line)
6280 {
6281 struct status *status = line->data;
6283 if (status) {
6284 const char buf[2] = { status->status, 0 };
6285 const char *text[] = { status->new.name, buf, NULL };
6287 return grep_text(view, text);
6288 }
6290 return FALSE;
6291 }
6293 static struct view_ops status_ops = {
6294 "file",
6295 NULL,
6296 status_open,
6297 NULL,
6298 status_draw,
6299 status_request,
6300 status_grep,
6301 status_select,
6302 };
6305 static bool
6306 stage_diff_write(struct io *io, struct line *line, struct line *end)
6307 {
6308 while (line < end) {
6309 if (!io_write(io, line->data, strlen(line->data)) ||
6310 !io_write(io, "\n", 1))
6311 return FALSE;
6312 line++;
6313 if (line->type == LINE_DIFF_CHUNK ||
6314 line->type == LINE_DIFF_HEADER)
6315 break;
6316 }
6318 return TRUE;
6319 }
6321 static struct line *
6322 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6323 {
6324 for (; view->line < line; line--)
6325 if (line->type == type)
6326 return line;
6328 return NULL;
6329 }
6331 static bool
6332 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6333 {
6334 const char *apply_argv[SIZEOF_ARG] = {
6335 "git", "apply", "--whitespace=nowarn", NULL
6336 };
6337 struct line *diff_hdr;
6338 struct io io;
6339 int argc = 3;
6341 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6342 if (!diff_hdr)
6343 return FALSE;
6345 if (!revert)
6346 apply_argv[argc++] = "--cached";
6347 if (revert || stage_line_type == LINE_STAT_STAGED)
6348 apply_argv[argc++] = "-R";
6349 apply_argv[argc++] = "-";
6350 apply_argv[argc++] = NULL;
6351 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6352 return FALSE;
6354 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6355 !stage_diff_write(&io, chunk, view->line + view->lines))
6356 chunk = NULL;
6358 io_done(&io);
6359 io_run_bg(update_index_argv);
6361 return chunk ? TRUE : FALSE;
6362 }
6364 static bool
6365 stage_update(struct view *view, struct line *line)
6366 {
6367 struct line *chunk = NULL;
6369 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6370 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6372 if (chunk) {
6373 if (!stage_apply_chunk(view, chunk, FALSE)) {
6374 report("Failed to apply chunk");
6375 return FALSE;
6376 }
6378 } else if (!stage_status.status) {
6379 view = VIEW(REQ_VIEW_STATUS);
6381 for (line = view->line; line < view->line + view->lines; line++)
6382 if (line->type == stage_line_type)
6383 break;
6385 if (!status_update_files(view, line + 1)) {
6386 report("Failed to update files");
6387 return FALSE;
6388 }
6390 } else if (!status_update_file(&stage_status, stage_line_type)) {
6391 report("Failed to update file");
6392 return FALSE;
6393 }
6395 return TRUE;
6396 }
6398 static bool
6399 stage_revert(struct view *view, struct line *line)
6400 {
6401 struct line *chunk = NULL;
6403 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6404 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6406 if (chunk) {
6407 if (!prompt_yesno("Are you sure you want to revert changes?"))
6408 return FALSE;
6410 if (!stage_apply_chunk(view, chunk, TRUE)) {
6411 report("Failed to revert chunk");
6412 return FALSE;
6413 }
6414 return TRUE;
6416 } else {
6417 return status_revert(stage_status.status ? &stage_status : NULL,
6418 stage_line_type, FALSE);
6419 }
6420 }
6423 static void
6424 stage_next(struct view *view, struct line *line)
6425 {
6426 int i;
6428 if (!stage_chunks) {
6429 for (line = view->line; line < view->line + view->lines; line++) {
6430 if (line->type != LINE_DIFF_CHUNK)
6431 continue;
6433 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6434 report("Allocation failure");
6435 return;
6436 }
6438 stage_chunk[stage_chunks++] = line - view->line;
6439 }
6440 }
6442 for (i = 0; i < stage_chunks; i++) {
6443 if (stage_chunk[i] > view->lineno) {
6444 do_scroll_view(view, stage_chunk[i] - view->lineno);
6445 report("Chunk %d of %d", i + 1, stage_chunks);
6446 return;
6447 }
6448 }
6450 report("No next chunk found");
6451 }
6453 static enum request
6454 stage_request(struct view *view, enum request request, struct line *line)
6455 {
6456 switch (request) {
6457 case REQ_STATUS_UPDATE:
6458 if (!stage_update(view, line))
6459 return REQ_NONE;
6460 break;
6462 case REQ_STATUS_REVERT:
6463 if (!stage_revert(view, line))
6464 return REQ_NONE;
6465 break;
6467 case REQ_STAGE_NEXT:
6468 if (stage_line_type == LINE_STAT_UNTRACKED) {
6469 report("File is untracked; press %s to add",
6470 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6471 return REQ_NONE;
6472 }
6473 stage_next(view, line);
6474 return REQ_NONE;
6476 case REQ_EDIT:
6477 if (!stage_status.new.name[0])
6478 return request;
6479 if (stage_status.status == 'D') {
6480 report("File has been deleted.");
6481 return REQ_NONE;
6482 }
6484 open_editor(stage_status.new.name);
6485 break;
6487 case REQ_REFRESH:
6488 /* Reload everything ... */
6489 break;
6491 case REQ_VIEW_BLAME:
6492 if (stage_status.new.name[0]) {
6493 string_copy(opt_file, stage_status.new.name);
6494 opt_ref[0] = 0;
6495 }
6496 return request;
6498 case REQ_ENTER:
6499 return pager_request(view, request, line);
6501 default:
6502 return request;
6503 }
6505 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6506 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6508 /* Check whether the staged entry still exists, and close the
6509 * stage view if it doesn't. */
6510 if (!status_exists(&stage_status, stage_line_type)) {
6511 status_restore(VIEW(REQ_VIEW_STATUS));
6512 return REQ_VIEW_CLOSE;
6513 }
6515 if (stage_line_type == LINE_STAT_UNTRACKED) {
6516 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6517 report("Cannot display a directory");
6518 return REQ_NONE;
6519 }
6521 if (!prepare_update_file(view, stage_status.new.name)) {
6522 report("Failed to open file: %s", strerror(errno));
6523 return REQ_NONE;
6524 }
6525 }
6526 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6528 return REQ_NONE;
6529 }
6531 static struct view_ops stage_ops = {
6532 "line",
6533 NULL,
6534 NULL,
6535 pager_read,
6536 pager_draw,
6537 stage_request,
6538 pager_grep,
6539 pager_select,
6540 };
6543 /*
6544 * Revision graph
6545 */
6547 struct commit {
6548 char id[SIZEOF_REV]; /* SHA1 ID. */
6549 char title[128]; /* First line of the commit message. */
6550 const char *author; /* Author of the commit. */
6551 struct time time; /* Date from the author ident. */
6552 struct ref_list *refs; /* Repository references. */
6553 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6554 size_t graph_size; /* The width of the graph array. */
6555 bool has_parents; /* Rewritten --parents seen. */
6556 };
6558 /* Size of rev graph with no "padding" columns */
6559 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6561 struct rev_graph {
6562 struct rev_graph *prev, *next, *parents;
6563 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6564 size_t size;
6565 struct commit *commit;
6566 size_t pos;
6567 unsigned int boundary:1;
6568 };
6570 /* Parents of the commit being visualized. */
6571 static struct rev_graph graph_parents[4];
6573 /* The current stack of revisions on the graph. */
6574 static struct rev_graph graph_stacks[4] = {
6575 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6576 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6577 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6578 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6579 };
6581 static inline bool
6582 graph_parent_is_merge(struct rev_graph *graph)
6583 {
6584 return graph->parents->size > 1;
6585 }
6587 static inline void
6588 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6589 {
6590 struct commit *commit = graph->commit;
6592 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6593 commit->graph[commit->graph_size++] = symbol;
6594 }
6596 static void
6597 clear_rev_graph(struct rev_graph *graph)
6598 {
6599 graph->boundary = 0;
6600 graph->size = graph->pos = 0;
6601 graph->commit = NULL;
6602 memset(graph->parents, 0, sizeof(*graph->parents));
6603 }
6605 static void
6606 done_rev_graph(struct rev_graph *graph)
6607 {
6608 if (graph_parent_is_merge(graph) &&
6609 graph->pos < graph->size - 1 &&
6610 graph->next->size == graph->size + graph->parents->size - 1) {
6611 size_t i = graph->pos + graph->parents->size - 1;
6613 graph->commit->graph_size = i * 2;
6614 while (i < graph->next->size - 1) {
6615 append_to_rev_graph(graph, ' ');
6616 append_to_rev_graph(graph, '\\');
6617 i++;
6618 }
6619 }
6621 clear_rev_graph(graph);
6622 }
6624 static void
6625 push_rev_graph(struct rev_graph *graph, const char *parent)
6626 {
6627 int i;
6629 /* "Collapse" duplicate parents lines.
6630 *
6631 * FIXME: This needs to also update update the drawn graph but
6632 * for now it just serves as a method for pruning graph lines. */
6633 for (i = 0; i < graph->size; i++)
6634 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6635 return;
6637 if (graph->size < SIZEOF_REVITEMS) {
6638 string_copy_rev(graph->rev[graph->size++], parent);
6639 }
6640 }
6642 static chtype
6643 get_rev_graph_symbol(struct rev_graph *graph)
6644 {
6645 chtype symbol;
6647 if (graph->boundary)
6648 symbol = REVGRAPH_BOUND;
6649 else if (graph->parents->size == 0)
6650 symbol = REVGRAPH_INIT;
6651 else if (graph_parent_is_merge(graph))
6652 symbol = REVGRAPH_MERGE;
6653 else if (graph->pos >= graph->size)
6654 symbol = REVGRAPH_BRANCH;
6655 else
6656 symbol = REVGRAPH_COMMIT;
6658 return symbol;
6659 }
6661 static void
6662 draw_rev_graph(struct rev_graph *graph)
6663 {
6664 struct rev_filler {
6665 chtype separator, line;
6666 };
6667 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6668 static struct rev_filler fillers[] = {
6669 { ' ', '|' },
6670 { '`', '.' },
6671 { '\'', ' ' },
6672 { '/', ' ' },
6673 };
6674 chtype symbol = get_rev_graph_symbol(graph);
6675 struct rev_filler *filler;
6676 size_t i;
6678 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6679 filler = &fillers[DEFAULT];
6681 for (i = 0; i < graph->pos; i++) {
6682 append_to_rev_graph(graph, filler->line);
6683 if (graph_parent_is_merge(graph->prev) &&
6684 graph->prev->pos == i)
6685 filler = &fillers[RSHARP];
6687 append_to_rev_graph(graph, filler->separator);
6688 }
6690 /* Place the symbol for this revision. */
6691 append_to_rev_graph(graph, symbol);
6693 if (graph->prev->size > graph->size)
6694 filler = &fillers[RDIAG];
6695 else
6696 filler = &fillers[DEFAULT];
6698 i++;
6700 for (; i < graph->size; i++) {
6701 append_to_rev_graph(graph, filler->separator);
6702 append_to_rev_graph(graph, filler->line);
6703 if (graph_parent_is_merge(graph->prev) &&
6704 i < graph->prev->pos + graph->parents->size)
6705 filler = &fillers[RSHARP];
6706 if (graph->prev->size > graph->size)
6707 filler = &fillers[LDIAG];
6708 }
6710 if (graph->prev->size > graph->size) {
6711 append_to_rev_graph(graph, filler->separator);
6712 if (filler->line != ' ')
6713 append_to_rev_graph(graph, filler->line);
6714 }
6715 }
6717 /* Prepare the next rev graph */
6718 static void
6719 prepare_rev_graph(struct rev_graph *graph)
6720 {
6721 size_t i;
6723 /* First, traverse all lines of revisions up to the active one. */
6724 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6725 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6726 break;
6728 push_rev_graph(graph->next, graph->rev[graph->pos]);
6729 }
6731 /* Interleave the new revision parent(s). */
6732 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6733 push_rev_graph(graph->next, graph->parents->rev[i]);
6735 /* Lastly, put any remaining revisions. */
6736 for (i = graph->pos + 1; i < graph->size; i++)
6737 push_rev_graph(graph->next, graph->rev[i]);
6738 }
6740 static void
6741 update_rev_graph(struct view *view, struct rev_graph *graph)
6742 {
6743 /* If this is the finalizing update ... */
6744 if (graph->commit)
6745 prepare_rev_graph(graph);
6747 /* Graph visualization needs a one rev look-ahead,
6748 * so the first update doesn't visualize anything. */
6749 if (!graph->prev->commit)
6750 return;
6752 if (view->lines > 2)
6753 view->line[view->lines - 3].dirty = 1;
6754 if (view->lines > 1)
6755 view->line[view->lines - 2].dirty = 1;
6756 draw_rev_graph(graph->prev);
6757 done_rev_graph(graph->prev->prev);
6758 }
6761 /*
6762 * Main view backend
6763 */
6765 static const char *main_argv[SIZEOF_ARG] = {
6766 "git", "log", "--no-color", "--pretty=raw", "--parents",
6767 "--topo-order", "%(diffargs)", "%(revargs)",
6768 "--", "%(fileargs)", NULL
6769 };
6771 static bool
6772 main_draw(struct view *view, struct line *line, unsigned int lineno)
6773 {
6774 struct commit *commit = line->data;
6776 if (!commit->author)
6777 return FALSE;
6779 if (opt_date && draw_date(view, &commit->time))
6780 return TRUE;
6782 if (opt_author && draw_author(view, commit->author))
6783 return TRUE;
6785 if (opt_rev_graph && commit->graph_size &&
6786 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6787 return TRUE;
6789 if (opt_show_refs && commit->refs) {
6790 size_t i;
6792 for (i = 0; i < commit->refs->size; i++) {
6793 struct ref *ref = commit->refs->refs[i];
6794 enum line_type type;
6796 if (ref->head)
6797 type = LINE_MAIN_HEAD;
6798 else if (ref->ltag)
6799 type = LINE_MAIN_LOCAL_TAG;
6800 else if (ref->tag)
6801 type = LINE_MAIN_TAG;
6802 else if (ref->tracked)
6803 type = LINE_MAIN_TRACKED;
6804 else if (ref->remote)
6805 type = LINE_MAIN_REMOTE;
6806 else
6807 type = LINE_MAIN_REF;
6809 if (draw_text(view, type, "[", TRUE) ||
6810 draw_text(view, type, ref->name, TRUE) ||
6811 draw_text(view, type, "]", TRUE))
6812 return TRUE;
6814 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6815 return TRUE;
6816 }
6817 }
6819 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6820 return TRUE;
6821 }
6823 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6824 static bool
6825 main_read(struct view *view, char *line)
6826 {
6827 static struct rev_graph *graph = graph_stacks;
6828 enum line_type type;
6829 struct commit *commit;
6831 if (!line) {
6832 int i;
6834 if (!view->lines && !view->prev)
6835 die("No revisions match the given arguments.");
6836 if (view->lines > 0) {
6837 commit = view->line[view->lines - 1].data;
6838 view->line[view->lines - 1].dirty = 1;
6839 if (!commit->author) {
6840 view->lines--;
6841 free(commit);
6842 graph->commit = NULL;
6843 }
6844 }
6845 update_rev_graph(view, graph);
6847 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6848 clear_rev_graph(&graph_stacks[i]);
6849 return TRUE;
6850 }
6852 type = get_line_type(line);
6853 if (type == LINE_COMMIT) {
6854 commit = calloc(1, sizeof(struct commit));
6855 if (!commit)
6856 return FALSE;
6858 line += STRING_SIZE("commit ");
6859 if (*line == '-') {
6860 graph->boundary = 1;
6861 line++;
6862 }
6864 string_copy_rev(commit->id, line);
6865 commit->refs = get_ref_list(commit->id);
6866 graph->commit = commit;
6867 add_line_data(view, commit, LINE_MAIN_COMMIT);
6869 while ((line = strchr(line, ' '))) {
6870 line++;
6871 push_rev_graph(graph->parents, line);
6872 commit->has_parents = TRUE;
6873 }
6874 return TRUE;
6875 }
6877 if (!view->lines)
6878 return TRUE;
6879 commit = view->line[view->lines - 1].data;
6881 switch (type) {
6882 case LINE_PARENT:
6883 if (commit->has_parents)
6884 break;
6885 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6886 break;
6888 case LINE_AUTHOR:
6889 parse_author_line(line + STRING_SIZE("author "),
6890 &commit->author, &commit->time);
6891 update_rev_graph(view, graph);
6892 graph = graph->next;
6893 break;
6895 default:
6896 /* Fill in the commit title if it has not already been set. */
6897 if (commit->title[0])
6898 break;
6900 /* Require titles to start with a non-space character at the
6901 * offset used by git log. */
6902 if (strncmp(line, " ", 4))
6903 break;
6904 line += 4;
6905 /* Well, if the title starts with a whitespace character,
6906 * try to be forgiving. Otherwise we end up with no title. */
6907 while (isspace(*line))
6908 line++;
6909 if (*line == '\0')
6910 break;
6911 /* FIXME: More graceful handling of titles; append "..." to
6912 * shortened titles, etc. */
6914 string_expand(commit->title, sizeof(commit->title), line, 1);
6915 view->line[view->lines - 1].dirty = 1;
6916 }
6918 return TRUE;
6919 }
6921 static enum request
6922 main_request(struct view *view, enum request request, struct line *line)
6923 {
6924 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6926 switch (request) {
6927 case REQ_ENTER:
6928 open_view(view, REQ_VIEW_DIFF, flags);
6929 break;
6930 case REQ_REFRESH:
6931 load_refs();
6932 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6933 break;
6934 default:
6935 return request;
6936 }
6938 return REQ_NONE;
6939 }
6941 static bool
6942 grep_refs(struct ref_list *list, regex_t *regex)
6943 {
6944 regmatch_t pmatch;
6945 size_t i;
6947 if (!opt_show_refs || !list)
6948 return FALSE;
6950 for (i = 0; i < list->size; i++) {
6951 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6952 return TRUE;
6953 }
6955 return FALSE;
6956 }
6958 static bool
6959 main_grep(struct view *view, struct line *line)
6960 {
6961 struct commit *commit = line->data;
6962 const char *text[] = {
6963 commit->title,
6964 opt_author ? commit->author : "",
6965 mkdate(&commit->time, opt_date),
6966 NULL
6967 };
6969 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6970 }
6972 static void
6973 main_select(struct view *view, struct line *line)
6974 {
6975 struct commit *commit = line->data;
6977 string_copy_rev(view->ref, commit->id);
6978 string_copy_rev(ref_commit, view->ref);
6979 }
6981 static struct view_ops main_ops = {
6982 "commit",
6983 main_argv,
6984 NULL,
6985 main_read,
6986 main_draw,
6987 main_request,
6988 main_grep,
6989 main_select,
6990 };
6993 /*
6994 * Status management
6995 */
6997 /* Whether or not the curses interface has been initialized. */
6998 static bool cursed = FALSE;
7000 /* Terminal hacks and workarounds. */
7001 static bool use_scroll_redrawwin;
7002 static bool use_scroll_status_wclear;
7004 /* The status window is used for polling keystrokes. */
7005 static WINDOW *status_win;
7007 /* Reading from the prompt? */
7008 static bool input_mode = FALSE;
7010 static bool status_empty = FALSE;
7012 /* Update status and title window. */
7013 static void
7014 report(const char *msg, ...)
7015 {
7016 struct view *view = display[current_view];
7018 if (input_mode)
7019 return;
7021 if (!view) {
7022 char buf[SIZEOF_STR];
7023 va_list args;
7025 va_start(args, msg);
7026 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7027 buf[sizeof(buf) - 1] = 0;
7028 buf[sizeof(buf) - 2] = '.';
7029 buf[sizeof(buf) - 3] = '.';
7030 buf[sizeof(buf) - 4] = '.';
7031 }
7032 va_end(args);
7033 die("%s", buf);
7034 }
7036 if (!status_empty || *msg) {
7037 va_list args;
7039 va_start(args, msg);
7041 wmove(status_win, 0, 0);
7042 if (view->has_scrolled && use_scroll_status_wclear)
7043 wclear(status_win);
7044 if (*msg) {
7045 vwprintw(status_win, msg, args);
7046 status_empty = FALSE;
7047 } else {
7048 status_empty = TRUE;
7049 }
7050 wclrtoeol(status_win);
7051 wnoutrefresh(status_win);
7053 va_end(args);
7054 }
7056 update_view_title(view);
7057 }
7059 static void
7060 init_display(void)
7061 {
7062 const char *term;
7063 int x, y;
7065 /* Initialize the curses library */
7066 if (isatty(STDIN_FILENO)) {
7067 cursed = !!initscr();
7068 opt_tty = stdin;
7069 } else {
7070 /* Leave stdin and stdout alone when acting as a pager. */
7071 opt_tty = fopen("/dev/tty", "r+");
7072 if (!opt_tty)
7073 die("Failed to open /dev/tty");
7074 cursed = !!newterm(NULL, opt_tty, opt_tty);
7075 }
7077 if (!cursed)
7078 die("Failed to initialize curses");
7080 nonl(); /* Disable conversion and detect newlines from input. */
7081 cbreak(); /* Take input chars one at a time, no wait for \n */
7082 noecho(); /* Don't echo input */
7083 leaveok(stdscr, FALSE);
7085 if (has_colors())
7086 init_colors();
7088 getmaxyx(stdscr, y, x);
7089 status_win = newwin(1, 0, y - 1, 0);
7090 if (!status_win)
7091 die("Failed to create status window");
7093 /* Enable keyboard mapping */
7094 keypad(status_win, TRUE);
7095 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7097 TABSIZE = opt_tab_size;
7099 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7100 if (term && !strcmp(term, "gnome-terminal")) {
7101 /* In the gnome-terminal-emulator, the message from
7102 * scrolling up one line when impossible followed by
7103 * scrolling down one line causes corruption of the
7104 * status line. This is fixed by calling wclear. */
7105 use_scroll_status_wclear = TRUE;
7106 use_scroll_redrawwin = FALSE;
7108 } else if (term && !strcmp(term, "xrvt-xpm")) {
7109 /* No problems with full optimizations in xrvt-(unicode)
7110 * and aterm. */
7111 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7113 } else {
7114 /* When scrolling in (u)xterm the last line in the
7115 * scrolling direction will update slowly. */
7116 use_scroll_redrawwin = TRUE;
7117 use_scroll_status_wclear = FALSE;
7118 }
7119 }
7121 static int
7122 get_input(int prompt_position)
7123 {
7124 struct view *view;
7125 int i, key, cursor_y, cursor_x;
7127 if (prompt_position)
7128 input_mode = TRUE;
7130 while (TRUE) {
7131 bool loading = FALSE;
7133 foreach_view (view, i) {
7134 update_view(view);
7135 if (view_is_displayed(view) && view->has_scrolled &&
7136 use_scroll_redrawwin)
7137 redrawwin(view->win);
7138 view->has_scrolled = FALSE;
7139 if (view->pipe)
7140 loading = TRUE;
7141 }
7143 /* Update the cursor position. */
7144 if (prompt_position) {
7145 getbegyx(status_win, cursor_y, cursor_x);
7146 cursor_x = prompt_position;
7147 } else {
7148 view = display[current_view];
7149 getbegyx(view->win, cursor_y, cursor_x);
7150 cursor_x = view->width - 1;
7151 cursor_y += view->lineno - view->offset;
7152 }
7153 setsyx(cursor_y, cursor_x);
7155 /* Refresh, accept single keystroke of input */
7156 doupdate();
7157 nodelay(status_win, loading);
7158 key = wgetch(status_win);
7160 /* wgetch() with nodelay() enabled returns ERR when
7161 * there's no input. */
7162 if (key == ERR) {
7164 } else if (key == KEY_RESIZE) {
7165 int height, width;
7167 getmaxyx(stdscr, height, width);
7169 wresize(status_win, 1, width);
7170 mvwin(status_win, height - 1, 0);
7171 wnoutrefresh(status_win);
7172 resize_display();
7173 redraw_display(TRUE);
7175 } else {
7176 input_mode = FALSE;
7177 return key;
7178 }
7179 }
7180 }
7182 static char *
7183 prompt_input(const char *prompt, input_handler handler, void *data)
7184 {
7185 enum input_status status = INPUT_OK;
7186 static char buf[SIZEOF_STR];
7187 size_t pos = 0;
7189 buf[pos] = 0;
7191 while (status == INPUT_OK || status == INPUT_SKIP) {
7192 int key;
7194 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7195 wclrtoeol(status_win);
7197 key = get_input(pos + 1);
7198 switch (key) {
7199 case KEY_RETURN:
7200 case KEY_ENTER:
7201 case '\n':
7202 status = pos ? INPUT_STOP : INPUT_CANCEL;
7203 break;
7205 case KEY_BACKSPACE:
7206 if (pos > 0)
7207 buf[--pos] = 0;
7208 else
7209 status = INPUT_CANCEL;
7210 break;
7212 case KEY_ESC:
7213 status = INPUT_CANCEL;
7214 break;
7216 default:
7217 if (pos >= sizeof(buf)) {
7218 report("Input string too long");
7219 return NULL;
7220 }
7222 status = handler(data, buf, key);
7223 if (status == INPUT_OK)
7224 buf[pos++] = (char) key;
7225 }
7226 }
7228 /* Clear the status window */
7229 status_empty = FALSE;
7230 report("");
7232 if (status == INPUT_CANCEL)
7233 return NULL;
7235 buf[pos++] = 0;
7237 return buf;
7238 }
7240 static enum input_status
7241 prompt_yesno_handler(void *data, char *buf, int c)
7242 {
7243 if (c == 'y' || c == 'Y')
7244 return INPUT_STOP;
7245 if (c == 'n' || c == 'N')
7246 return INPUT_CANCEL;
7247 return INPUT_SKIP;
7248 }
7250 static bool
7251 prompt_yesno(const char *prompt)
7252 {
7253 char prompt2[SIZEOF_STR];
7255 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7256 return FALSE;
7258 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7259 }
7261 static enum input_status
7262 read_prompt_handler(void *data, char *buf, int c)
7263 {
7264 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7265 }
7267 static char *
7268 read_prompt(const char *prompt)
7269 {
7270 return prompt_input(prompt, read_prompt_handler, NULL);
7271 }
7273 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7274 {
7275 enum input_status status = INPUT_OK;
7276 int size = 0;
7278 while (items[size].text)
7279 size++;
7281 while (status == INPUT_OK) {
7282 const struct menu_item *item = &items[*selected];
7283 int key;
7284 int i;
7286 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7287 prompt, *selected + 1, size);
7288 if (item->hotkey)
7289 wprintw(status_win, "[%c] ", (char) item->hotkey);
7290 wprintw(status_win, "%s", item->text);
7291 wclrtoeol(status_win);
7293 key = get_input(COLS - 1);
7294 switch (key) {
7295 case KEY_RETURN:
7296 case KEY_ENTER:
7297 case '\n':
7298 status = INPUT_STOP;
7299 break;
7301 case KEY_LEFT:
7302 case KEY_UP:
7303 *selected = *selected - 1;
7304 if (*selected < 0)
7305 *selected = size - 1;
7306 break;
7308 case KEY_RIGHT:
7309 case KEY_DOWN:
7310 *selected = (*selected + 1) % size;
7311 break;
7313 case KEY_ESC:
7314 status = INPUT_CANCEL;
7315 break;
7317 default:
7318 for (i = 0; items[i].text; i++)
7319 if (items[i].hotkey == key) {
7320 *selected = i;
7321 status = INPUT_STOP;
7322 break;
7323 }
7324 }
7325 }
7327 /* Clear the status window */
7328 status_empty = FALSE;
7329 report("");
7331 return status != INPUT_CANCEL;
7332 }
7334 /*
7335 * Repository properties
7336 */
7338 static struct ref **refs = NULL;
7339 static size_t refs_size = 0;
7340 static struct ref *refs_head = NULL;
7342 static struct ref_list **ref_lists = NULL;
7343 static size_t ref_lists_size = 0;
7345 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7346 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7347 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7349 static int
7350 compare_refs(const void *ref1_, const void *ref2_)
7351 {
7352 const struct ref *ref1 = *(const struct ref **)ref1_;
7353 const struct ref *ref2 = *(const struct ref **)ref2_;
7355 if (ref1->tag != ref2->tag)
7356 return ref2->tag - ref1->tag;
7357 if (ref1->ltag != ref2->ltag)
7358 return ref2->ltag - ref2->ltag;
7359 if (ref1->head != ref2->head)
7360 return ref2->head - ref1->head;
7361 if (ref1->tracked != ref2->tracked)
7362 return ref2->tracked - ref1->tracked;
7363 if (ref1->remote != ref2->remote)
7364 return ref2->remote - ref1->remote;
7365 return strcmp(ref1->name, ref2->name);
7366 }
7368 static void
7369 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7370 {
7371 size_t i;
7373 for (i = 0; i < refs_size; i++)
7374 if (!visitor(data, refs[i]))
7375 break;
7376 }
7378 static struct ref *
7379 get_ref_head()
7380 {
7381 return refs_head;
7382 }
7384 static struct ref_list *
7385 get_ref_list(const char *id)
7386 {
7387 struct ref_list *list;
7388 size_t i;
7390 for (i = 0; i < ref_lists_size; i++)
7391 if (!strcmp(id, ref_lists[i]->id))
7392 return ref_lists[i];
7394 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7395 return NULL;
7396 list = calloc(1, sizeof(*list));
7397 if (!list)
7398 return NULL;
7400 for (i = 0; i < refs_size; i++) {
7401 if (!strcmp(id, refs[i]->id) &&
7402 realloc_refs_list(&list->refs, list->size, 1))
7403 list->refs[list->size++] = refs[i];
7404 }
7406 if (!list->refs) {
7407 free(list);
7408 return NULL;
7409 }
7411 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7412 ref_lists[ref_lists_size++] = list;
7413 return list;
7414 }
7416 static int
7417 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7418 {
7419 struct ref *ref = NULL;
7420 bool tag = FALSE;
7421 bool ltag = FALSE;
7422 bool remote = FALSE;
7423 bool tracked = FALSE;
7424 bool head = FALSE;
7425 int from = 0, to = refs_size - 1;
7427 if (!prefixcmp(name, "refs/tags/")) {
7428 if (!suffixcmp(name, namelen, "^{}")) {
7429 namelen -= 3;
7430 name[namelen] = 0;
7431 } else {
7432 ltag = TRUE;
7433 }
7435 tag = TRUE;
7436 namelen -= STRING_SIZE("refs/tags/");
7437 name += STRING_SIZE("refs/tags/");
7439 } else if (!prefixcmp(name, "refs/remotes/")) {
7440 remote = TRUE;
7441 namelen -= STRING_SIZE("refs/remotes/");
7442 name += STRING_SIZE("refs/remotes/");
7443 tracked = !strcmp(opt_remote, name);
7445 } else if (!prefixcmp(name, "refs/heads/")) {
7446 namelen -= STRING_SIZE("refs/heads/");
7447 name += STRING_SIZE("refs/heads/");
7448 if (!strncmp(opt_head, name, namelen))
7449 return OK;
7451 } else if (!strcmp(name, "HEAD")) {
7452 head = TRUE;
7453 if (*opt_head) {
7454 namelen = strlen(opt_head);
7455 name = opt_head;
7456 }
7457 }
7459 /* If we are reloading or it's an annotated tag, replace the
7460 * previous SHA1 with the resolved commit id; relies on the fact
7461 * git-ls-remote lists the commit id of an annotated tag right
7462 * before the commit id it points to. */
7463 while (from <= to) {
7464 size_t pos = (to + from) / 2;
7465 int cmp = strcmp(name, refs[pos]->name);
7467 if (!cmp) {
7468 ref = refs[pos];
7469 break;
7470 }
7472 if (cmp < 0)
7473 to = pos - 1;
7474 else
7475 from = pos + 1;
7476 }
7478 if (!ref) {
7479 if (!realloc_refs(&refs, refs_size, 1))
7480 return ERR;
7481 ref = calloc(1, sizeof(*ref) + namelen);
7482 if (!ref)
7483 return ERR;
7484 memmove(refs + from + 1, refs + from,
7485 (refs_size - from) * sizeof(*refs));
7486 refs[from] = ref;
7487 strncpy(ref->name, name, namelen);
7488 refs_size++;
7489 }
7491 ref->head = head;
7492 ref->tag = tag;
7493 ref->ltag = ltag;
7494 ref->remote = remote;
7495 ref->tracked = tracked;
7496 string_copy_rev(ref->id, id);
7498 if (head)
7499 refs_head = ref;
7500 return OK;
7501 }
7503 static int
7504 load_refs(void)
7505 {
7506 const char *head_argv[] = {
7507 "git", "symbolic-ref", "HEAD", NULL
7508 };
7509 static const char *ls_remote_argv[SIZEOF_ARG] = {
7510 "git", "ls-remote", opt_git_dir, NULL
7511 };
7512 static bool init = FALSE;
7513 size_t i;
7515 if (!init) {
7516 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7517 die("TIG_LS_REMOTE contains too many arguments");
7518 init = TRUE;
7519 }
7521 if (!*opt_git_dir)
7522 return OK;
7524 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7525 !prefixcmp(opt_head, "refs/heads/")) {
7526 char *offset = opt_head + STRING_SIZE("refs/heads/");
7528 memmove(opt_head, offset, strlen(offset) + 1);
7529 }
7531 refs_head = NULL;
7532 for (i = 0; i < refs_size; i++)
7533 refs[i]->id[0] = 0;
7535 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7536 return ERR;
7538 /* Update the ref lists to reflect changes. */
7539 for (i = 0; i < ref_lists_size; i++) {
7540 struct ref_list *list = ref_lists[i];
7541 size_t old, new;
7543 for (old = new = 0; old < list->size; old++)
7544 if (!strcmp(list->id, list->refs[old]->id))
7545 list->refs[new++] = list->refs[old];
7546 list->size = new;
7547 }
7549 return OK;
7550 }
7552 static void
7553 set_remote_branch(const char *name, const char *value, size_t valuelen)
7554 {
7555 if (!strcmp(name, ".remote")) {
7556 string_ncopy(opt_remote, value, valuelen);
7558 } else if (*opt_remote && !strcmp(name, ".merge")) {
7559 size_t from = strlen(opt_remote);
7561 if (!prefixcmp(value, "refs/heads/"))
7562 value += STRING_SIZE("refs/heads/");
7564 if (!string_format_from(opt_remote, &from, "/%s", value))
7565 opt_remote[0] = 0;
7566 }
7567 }
7569 static void
7570 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7571 {
7572 const char *argv[SIZEOF_ARG] = { name, "=" };
7573 int argc = 1 + (cmd == option_set_command);
7574 int error = ERR;
7576 if (!argv_from_string(argv, &argc, value))
7577 config_msg = "Too many option arguments";
7578 else
7579 error = cmd(argc, argv);
7581 if (error == ERR)
7582 warn("Option 'tig.%s': %s", name, config_msg);
7583 }
7585 static bool
7586 set_environment_variable(const char *name, const char *value)
7587 {
7588 size_t len = strlen(name) + 1 + strlen(value) + 1;
7589 char *env = malloc(len);
7591 if (env &&
7592 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7593 putenv(env) == 0)
7594 return TRUE;
7595 free(env);
7596 return FALSE;
7597 }
7599 static void
7600 set_work_tree(const char *value)
7601 {
7602 char cwd[SIZEOF_STR];
7604 if (!getcwd(cwd, sizeof(cwd)))
7605 die("Failed to get cwd path: %s", strerror(errno));
7606 if (chdir(opt_git_dir) < 0)
7607 die("Failed to chdir(%s): %s", strerror(errno));
7608 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7609 die("Failed to get git path: %s", strerror(errno));
7610 if (chdir(cwd) < 0)
7611 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7612 if (chdir(value) < 0)
7613 die("Failed to chdir(%s): %s", value, strerror(errno));
7614 if (!getcwd(cwd, sizeof(cwd)))
7615 die("Failed to get cwd path: %s", strerror(errno));
7616 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7617 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7618 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7619 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7620 opt_is_inside_work_tree = TRUE;
7621 }
7623 static int
7624 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7625 {
7626 if (!strcmp(name, "i18n.commitencoding"))
7627 string_ncopy(opt_encoding, value, valuelen);
7629 else if (!strcmp(name, "core.editor"))
7630 string_ncopy(opt_editor, value, valuelen);
7632 else if (!strcmp(name, "core.worktree"))
7633 set_work_tree(value);
7635 else if (!prefixcmp(name, "tig.color."))
7636 set_repo_config_option(name + 10, value, option_color_command);
7638 else if (!prefixcmp(name, "tig.bind."))
7639 set_repo_config_option(name + 9, value, option_bind_command);
7641 else if (!prefixcmp(name, "tig."))
7642 set_repo_config_option(name + 4, value, option_set_command);
7644 else if (*opt_head && !prefixcmp(name, "branch.") &&
7645 !strncmp(name + 7, opt_head, strlen(opt_head)))
7646 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7648 return OK;
7649 }
7651 static int
7652 load_git_config(void)
7653 {
7654 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7656 return io_run_load(config_list_argv, "=", read_repo_config_option);
7657 }
7659 static int
7660 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7661 {
7662 if (!opt_git_dir[0]) {
7663 string_ncopy(opt_git_dir, name, namelen);
7665 } else if (opt_is_inside_work_tree == -1) {
7666 /* This can be 3 different values depending on the
7667 * version of git being used. If git-rev-parse does not
7668 * understand --is-inside-work-tree it will simply echo
7669 * the option else either "true" or "false" is printed.
7670 * Default to true for the unknown case. */
7671 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7673 } else if (*name == '.') {
7674 string_ncopy(opt_cdup, name, namelen);
7676 } else {
7677 string_ncopy(opt_prefix, name, namelen);
7678 }
7680 return OK;
7681 }
7683 static int
7684 load_repo_info(void)
7685 {
7686 const char *rev_parse_argv[] = {
7687 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7688 "--show-cdup", "--show-prefix", NULL
7689 };
7691 return io_run_load(rev_parse_argv, "=", read_repo_info);
7692 }
7695 /*
7696 * Main
7697 */
7699 static const char usage[] =
7700 "tig " TIG_VERSION " (" __DATE__ ")\n"
7701 "\n"
7702 "Usage: tig [options] [revs] [--] [paths]\n"
7703 " or: tig show [options] [revs] [--] [paths]\n"
7704 " or: tig blame [rev] path\n"
7705 " or: tig status\n"
7706 " or: tig < [git command output]\n"
7707 "\n"
7708 "Options:\n"
7709 " -v, --version Show version and exit\n"
7710 " -h, --help Show help message and exit";
7712 static void __NORETURN
7713 quit(int sig)
7714 {
7715 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7716 if (cursed)
7717 endwin();
7718 exit(0);
7719 }
7721 static void __NORETURN
7722 die(const char *err, ...)
7723 {
7724 va_list args;
7726 endwin();
7728 va_start(args, err);
7729 fputs("tig: ", stderr);
7730 vfprintf(stderr, err, args);
7731 fputs("\n", stderr);
7732 va_end(args);
7734 exit(1);
7735 }
7737 static void
7738 warn(const char *msg, ...)
7739 {
7740 va_list args;
7742 va_start(args, msg);
7743 fputs("tig warning: ", stderr);
7744 vfprintf(stderr, msg, args);
7745 fputs("\n", stderr);
7746 va_end(args);
7747 }
7749 static const char ***filter_args;
7751 static int
7752 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7753 {
7754 return argv_append(filter_args, name) ? OK : ERR;
7755 }
7757 static void
7758 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7759 {
7760 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7761 const char **all_argv = NULL;
7763 filter_args = args;
7764 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7765 !argv_append_array(&all_argv, argv) ||
7766 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7767 die("Failed to split arguments");
7768 argv_free(all_argv);
7769 free(all_argv);
7770 }
7772 static void
7773 filter_options(const char *argv[])
7774 {
7775 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7776 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7777 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7778 }
7780 static enum request
7781 parse_options(int argc, const char *argv[])
7782 {
7783 enum request request = REQ_VIEW_MAIN;
7784 const char *subcommand;
7785 bool seen_dashdash = FALSE;
7786 const char **filter_argv = NULL;
7787 int i;
7789 if (!isatty(STDIN_FILENO))
7790 return REQ_VIEW_PAGER;
7792 if (argc <= 1)
7793 return REQ_VIEW_MAIN;
7795 subcommand = argv[1];
7796 if (!strcmp(subcommand, "status")) {
7797 if (argc > 2)
7798 warn("ignoring arguments after `%s'", subcommand);
7799 return REQ_VIEW_STATUS;
7801 } else if (!strcmp(subcommand, "blame")) {
7802 if (argc <= 2 || argc > 4)
7803 die("invalid number of options to blame\n\n%s", usage);
7805 i = 2;
7806 if (argc == 4) {
7807 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7808 i++;
7809 }
7811 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7812 return REQ_VIEW_BLAME;
7814 } else if (!strcmp(subcommand, "show")) {
7815 request = REQ_VIEW_DIFF;
7817 } else {
7818 subcommand = NULL;
7819 }
7821 for (i = 1 + !!subcommand; i < argc; i++) {
7822 const char *opt = argv[i];
7824 if (seen_dashdash) {
7825 argv_append(&opt_file_args, opt);
7826 continue;
7828 } else if (!strcmp(opt, "--")) {
7829 seen_dashdash = TRUE;
7830 continue;
7832 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7833 printf("tig version %s\n", TIG_VERSION);
7834 quit(0);
7836 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7837 printf("%s\n", usage);
7838 quit(0);
7840 } else if (!strcmp(opt, "--all")) {
7841 argv_append(&opt_rev_args, opt);
7842 continue;
7843 }
7845 if (!argv_append(&filter_argv, opt))
7846 die("command too long");
7847 }
7849 if (filter_argv)
7850 filter_options(filter_argv);
7852 return request;
7853 }
7855 int
7856 main(int argc, const char *argv[])
7857 {
7858 const char *codeset = "UTF-8";
7859 enum request request = parse_options(argc, argv);
7860 struct view *view;
7861 size_t i;
7863 signal(SIGINT, quit);
7864 signal(SIGPIPE, SIG_IGN);
7866 if (setlocale(LC_ALL, "")) {
7867 codeset = nl_langinfo(CODESET);
7868 }
7870 if (load_repo_info() == ERR)
7871 die("Failed to load repo info.");
7873 if (load_options() == ERR)
7874 die("Failed to load user config.");
7876 if (load_git_config() == ERR)
7877 die("Failed to load repo config.");
7879 /* Require a git repository unless when running in pager mode. */
7880 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7881 die("Not a git repository");
7883 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7884 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7885 if (opt_iconv_in == ICONV_NONE)
7886 die("Failed to initialize character set conversion");
7887 }
7889 if (codeset && strcmp(codeset, "UTF-8")) {
7890 opt_iconv_out = iconv_open(codeset, "UTF-8");
7891 if (opt_iconv_out == ICONV_NONE)
7892 die("Failed to initialize character set conversion");
7893 }
7895 if (load_refs() == ERR)
7896 die("Failed to load refs.");
7898 foreach_view (view, i) {
7899 if (getenv(view->cmd_env))
7900 warn("Use of the %s environment variable is deprecated,"
7901 " use options or TIG_DIFF_ARGS instead",
7902 view->cmd_env);
7903 if (!argv_from_env(view->ops->argv, view->cmd_env))
7904 die("Too many arguments in the `%s` environment variable",
7905 view->cmd_env);
7906 }
7908 init_display();
7910 while (view_driver(display[current_view], request)) {
7911 int key = get_input(0);
7913 view = display[current_view];
7914 request = get_keybinding(view->keymap, key);
7916 /* Some low-level request handling. This keeps access to
7917 * status_win restricted. */
7918 switch (request) {
7919 case REQ_NONE:
7920 report("Unknown key, press %s for help",
7921 get_key(view->keymap, REQ_VIEW_HELP));
7922 break;
7923 case REQ_PROMPT:
7924 {
7925 char *cmd = read_prompt(":");
7927 if (cmd && isdigit(*cmd)) {
7928 int lineno = view->lineno + 1;
7930 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7931 select_view_line(view, lineno - 1);
7932 report("");
7933 } else {
7934 report("Unable to parse '%s' as a line number", cmd);
7935 }
7937 } else if (cmd) {
7938 struct view *next = VIEW(REQ_VIEW_PAGER);
7939 const char *argv[SIZEOF_ARG] = { "git" };
7940 int argc = 1;
7942 /* When running random commands, initially show the
7943 * command in the title. However, it maybe later be
7944 * overwritten if a commit line is selected. */
7945 string_ncopy(next->ref, cmd, strlen(cmd));
7947 if (!argv_from_string(argv, &argc, cmd)) {
7948 report("Too many arguments");
7949 } else if (!prepare_update(next, argv, NULL)) {
7950 report("Failed to format command");
7951 } else {
7952 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7953 }
7954 }
7956 request = REQ_NONE;
7957 break;
7958 }
7959 case REQ_SEARCH:
7960 case REQ_SEARCH_BACK:
7961 {
7962 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7963 char *search = read_prompt(prompt);
7965 if (search)
7966 string_ncopy(opt_search, search, strlen(search));
7967 else if (*opt_search)
7968 request = request == REQ_SEARCH ?
7969 REQ_FIND_NEXT :
7970 REQ_FIND_PREV;
7971 else
7972 request = REQ_NONE;
7973 break;
7974 }
7975 default:
7976 break;
7977 }
7978 }
7980 quit(0);
7982 return 0;
7983 }