37a779cadea5d7abc85a55d8d40de453e1db9683
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_FIRST_COL, "Scroll to the first line columns"), \
1140 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1141 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1142 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1143 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1144 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1145 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1146 \
1147 REQ_GROUP("Searching") \
1148 REQ_(SEARCH, "Search the view"), \
1149 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1150 REQ_(FIND_NEXT, "Find next search match"), \
1151 REQ_(FIND_PREV, "Find previous search match"), \
1152 \
1153 REQ_GROUP("Option manipulation") \
1154 REQ_(OPTIONS, "Open option menu"), \
1155 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1156 REQ_(TOGGLE_DATE, "Toggle date display"), \
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 bool opt_untracked_dirs_content = TRUE;
1227 static int opt_num_interval = 5;
1228 static double opt_hscroll = 0.50;
1229 static double opt_scale_split_view = 2.0 / 3.0;
1230 static int opt_tab_size = 8;
1231 static int opt_author_cols = AUTHOR_COLS;
1232 static char opt_path[SIZEOF_STR] = "";
1233 static char opt_file[SIZEOF_STR] = "";
1234 static char opt_ref[SIZEOF_REF] = "";
1235 static char opt_head[SIZEOF_REF] = "";
1236 static char opt_remote[SIZEOF_REF] = "";
1237 static char opt_encoding[20] = "UTF-8";
1238 static iconv_t opt_iconv_in = ICONV_NONE;
1239 static iconv_t opt_iconv_out = ICONV_NONE;
1240 static char opt_search[SIZEOF_STR] = "";
1241 static char opt_cdup[SIZEOF_STR] = "";
1242 static char opt_prefix[SIZEOF_STR] = "";
1243 static char opt_git_dir[SIZEOF_STR] = "";
1244 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1245 static char opt_editor[SIZEOF_STR] = "";
1246 static FILE *opt_tty = NULL;
1247 static const char **opt_diff_args = NULL;
1248 static const char **opt_rev_args = NULL;
1249 static const char **opt_file_args = NULL;
1251 #define is_initial_commit() (!get_ref_head())
1252 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1255 /*
1256 * Line-oriented content detection.
1257 */
1259 #define LINE_INFO \
1260 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1274 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1275 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1276 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1281 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1283 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1285 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1291 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1292 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1293 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1294 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1295 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1298 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1299 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1301 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1307 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1308 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1309 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1310 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1311 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1312 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1313 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1315 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1317 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1318 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1320 enum line_type {
1321 #define LINE(type, line, fg, bg, attr) \
1322 LINE_##type
1323 LINE_INFO,
1324 LINE_NONE
1325 #undef LINE
1326 };
1328 struct line_info {
1329 const char *name; /* Option name. */
1330 int namelen; /* Size of option name. */
1331 const char *line; /* The start of line to match. */
1332 int linelen; /* Size of string to match. */
1333 int fg, bg, attr; /* Color and text attributes for the lines. */
1334 };
1336 static struct line_info line_info[] = {
1337 #define LINE(type, line, fg, bg, attr) \
1338 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1339 LINE_INFO
1340 #undef LINE
1341 };
1343 static enum line_type
1344 get_line_type(const char *line)
1345 {
1346 int linelen = strlen(line);
1347 enum line_type type;
1349 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1350 /* Case insensitive search matches Signed-off-by lines better. */
1351 if (linelen >= line_info[type].linelen &&
1352 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1353 return type;
1355 return LINE_DEFAULT;
1356 }
1358 static inline int
1359 get_line_attr(enum line_type type)
1360 {
1361 assert(type < ARRAY_SIZE(line_info));
1362 return COLOR_PAIR(type) | line_info[type].attr;
1363 }
1365 static struct line_info *
1366 get_line_info(const char *name)
1367 {
1368 size_t namelen = strlen(name);
1369 enum line_type type;
1371 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1372 if (enum_equals(line_info[type], name, namelen))
1373 return &line_info[type];
1375 return NULL;
1376 }
1378 static void
1379 init_colors(void)
1380 {
1381 int default_bg = line_info[LINE_DEFAULT].bg;
1382 int default_fg = line_info[LINE_DEFAULT].fg;
1383 enum line_type type;
1385 start_color();
1387 if (assume_default_colors(default_fg, default_bg) == ERR) {
1388 default_bg = COLOR_BLACK;
1389 default_fg = COLOR_WHITE;
1390 }
1392 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1393 struct line_info *info = &line_info[type];
1394 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1395 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1397 init_pair(type, fg, bg);
1398 }
1399 }
1401 struct line {
1402 enum line_type type;
1404 /* State flags */
1405 unsigned int selected:1;
1406 unsigned int dirty:1;
1407 unsigned int cleareol:1;
1408 unsigned int other:16;
1410 void *data; /* User data */
1411 };
1414 /*
1415 * Keys
1416 */
1418 struct keybinding {
1419 int alias;
1420 enum request request;
1421 };
1423 static struct keybinding default_keybindings[] = {
1424 /* View switching */
1425 { 'm', REQ_VIEW_MAIN },
1426 { 'd', REQ_VIEW_DIFF },
1427 { 'l', REQ_VIEW_LOG },
1428 { 't', REQ_VIEW_TREE },
1429 { 'f', REQ_VIEW_BLOB },
1430 { 'B', REQ_VIEW_BLAME },
1431 { 'H', REQ_VIEW_BRANCH },
1432 { 'p', REQ_VIEW_PAGER },
1433 { 'h', REQ_VIEW_HELP },
1434 { 'S', REQ_VIEW_STATUS },
1435 { 'c', REQ_VIEW_STAGE },
1437 /* View manipulation */
1438 { 'q', REQ_VIEW_CLOSE },
1439 { KEY_TAB, REQ_VIEW_NEXT },
1440 { KEY_RETURN, REQ_ENTER },
1441 { KEY_UP, REQ_PREVIOUS },
1442 { KEY_CTL('P'), REQ_PREVIOUS },
1443 { KEY_DOWN, REQ_NEXT },
1444 { KEY_CTL('N'), REQ_NEXT },
1445 { 'R', REQ_REFRESH },
1446 { KEY_F(5), REQ_REFRESH },
1447 { 'O', REQ_MAXIMIZE },
1449 /* Cursor navigation */
1450 { 'k', REQ_MOVE_UP },
1451 { 'j', REQ_MOVE_DOWN },
1452 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1453 { KEY_END, REQ_MOVE_LAST_LINE },
1454 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1455 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1456 { ' ', REQ_MOVE_PAGE_DOWN },
1457 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1458 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1459 { 'b', REQ_MOVE_PAGE_UP },
1460 { '-', REQ_MOVE_PAGE_UP },
1462 /* Scrolling */
1463 { '|', REQ_SCROLL_FIRST_COL },
1464 { KEY_LEFT, REQ_SCROLL_LEFT },
1465 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1466 { KEY_IC, REQ_SCROLL_LINE_UP },
1467 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1468 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1469 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1470 { 'w', REQ_SCROLL_PAGE_UP },
1471 { 's', REQ_SCROLL_PAGE_DOWN },
1473 /* Searching */
1474 { '/', REQ_SEARCH },
1475 { '?', REQ_SEARCH_BACK },
1476 { 'n', REQ_FIND_NEXT },
1477 { 'N', REQ_FIND_PREV },
1479 /* Misc */
1480 { 'Q', REQ_QUIT },
1481 { 'z', REQ_STOP_LOADING },
1482 { 'v', REQ_SHOW_VERSION },
1483 { 'r', REQ_SCREEN_REDRAW },
1484 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1485 { 'o', REQ_OPTIONS },
1486 { '.', REQ_TOGGLE_LINENO },
1487 { 'D', REQ_TOGGLE_DATE },
1488 { 'A', REQ_TOGGLE_AUTHOR },
1489 { 'g', REQ_TOGGLE_REV_GRAPH },
1490 { 'F', REQ_TOGGLE_REFS },
1491 { 'I', REQ_TOGGLE_SORT_ORDER },
1492 { 'i', REQ_TOGGLE_SORT_FIELD },
1493 { ':', REQ_PROMPT },
1494 { 'u', REQ_STATUS_UPDATE },
1495 { '!', REQ_STATUS_REVERT },
1496 { 'M', REQ_STATUS_MERGE },
1497 { '@', REQ_STAGE_NEXT },
1498 { ',', REQ_PARENT },
1499 { 'e', REQ_EDIT },
1500 };
1502 #define KEYMAP_INFO \
1503 KEYMAP_(GENERIC), \
1504 KEYMAP_(MAIN), \
1505 KEYMAP_(DIFF), \
1506 KEYMAP_(LOG), \
1507 KEYMAP_(TREE), \
1508 KEYMAP_(BLOB), \
1509 KEYMAP_(BLAME), \
1510 KEYMAP_(BRANCH), \
1511 KEYMAP_(PAGER), \
1512 KEYMAP_(HELP), \
1513 KEYMAP_(STATUS), \
1514 KEYMAP_(STAGE)
1516 enum keymap {
1517 #define KEYMAP_(name) KEYMAP_##name
1518 KEYMAP_INFO
1519 #undef KEYMAP_
1520 };
1522 static const struct enum_map keymap_table[] = {
1523 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1524 KEYMAP_INFO
1525 #undef KEYMAP_
1526 };
1528 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1530 struct keybinding_table {
1531 struct keybinding *data;
1532 size_t size;
1533 };
1535 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1537 static void
1538 add_keybinding(enum keymap keymap, enum request request, int key)
1539 {
1540 struct keybinding_table *table = &keybindings[keymap];
1541 size_t i;
1543 for (i = 0; i < keybindings[keymap].size; i++) {
1544 if (keybindings[keymap].data[i].alias == key) {
1545 keybindings[keymap].data[i].request = request;
1546 return;
1547 }
1548 }
1550 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1551 if (!table->data)
1552 die("Failed to allocate keybinding");
1553 table->data[table->size].alias = key;
1554 table->data[table->size++].request = request;
1556 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1557 int i;
1559 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1560 if (default_keybindings[i].alias == key)
1561 default_keybindings[i].request = REQ_NONE;
1562 }
1563 }
1565 /* Looks for a key binding first in the given map, then in the generic map, and
1566 * lastly in the default keybindings. */
1567 static enum request
1568 get_keybinding(enum keymap keymap, int key)
1569 {
1570 size_t i;
1572 for (i = 0; i < keybindings[keymap].size; i++)
1573 if (keybindings[keymap].data[i].alias == key)
1574 return keybindings[keymap].data[i].request;
1576 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1577 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1578 return keybindings[KEYMAP_GENERIC].data[i].request;
1580 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1581 if (default_keybindings[i].alias == key)
1582 return default_keybindings[i].request;
1584 return (enum request) key;
1585 }
1588 struct key {
1589 const char *name;
1590 int value;
1591 };
1593 static const struct key key_table[] = {
1594 { "Enter", KEY_RETURN },
1595 { "Space", ' ' },
1596 { "Backspace", KEY_BACKSPACE },
1597 { "Tab", KEY_TAB },
1598 { "Escape", KEY_ESC },
1599 { "Left", KEY_LEFT },
1600 { "Right", KEY_RIGHT },
1601 { "Up", KEY_UP },
1602 { "Down", KEY_DOWN },
1603 { "Insert", KEY_IC },
1604 { "Delete", KEY_DC },
1605 { "Hash", '#' },
1606 { "Home", KEY_HOME },
1607 { "End", KEY_END },
1608 { "PageUp", KEY_PPAGE },
1609 { "PageDown", KEY_NPAGE },
1610 { "F1", KEY_F(1) },
1611 { "F2", KEY_F(2) },
1612 { "F3", KEY_F(3) },
1613 { "F4", KEY_F(4) },
1614 { "F5", KEY_F(5) },
1615 { "F6", KEY_F(6) },
1616 { "F7", KEY_F(7) },
1617 { "F8", KEY_F(8) },
1618 { "F9", KEY_F(9) },
1619 { "F10", KEY_F(10) },
1620 { "F11", KEY_F(11) },
1621 { "F12", KEY_F(12) },
1622 };
1624 static int
1625 get_key_value(const char *name)
1626 {
1627 int i;
1629 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1630 if (!strcasecmp(key_table[i].name, name))
1631 return key_table[i].value;
1633 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1634 return (int)name[1] & 0x1f;
1635 if (strlen(name) == 1 && isprint(*name))
1636 return (int) *name;
1637 return ERR;
1638 }
1640 static const char *
1641 get_key_name(int key_value)
1642 {
1643 static char key_char[] = "'X'\0";
1644 const char *seq = NULL;
1645 int key;
1647 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1648 if (key_table[key].value == key_value)
1649 seq = key_table[key].name;
1651 if (seq == NULL && key_value < 0x7f) {
1652 char *s = key_char + 1;
1654 if (key_value >= 0x20) {
1655 *s++ = key_value;
1656 } else {
1657 *s++ = '^';
1658 *s++ = 0x40 | (key_value & 0x1f);
1659 }
1660 *s++ = '\'';
1661 *s++ = '\0';
1662 seq = key_char;
1663 }
1665 return seq ? seq : "(no key)";
1666 }
1668 static bool
1669 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1670 {
1671 const char *sep = *pos > 0 ? ", " : "";
1672 const char *keyname = get_key_name(keybinding->alias);
1674 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1675 }
1677 static bool
1678 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1679 enum keymap keymap, bool all)
1680 {
1681 int i;
1683 for (i = 0; i < keybindings[keymap].size; i++) {
1684 if (keybindings[keymap].data[i].request == request) {
1685 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1686 return FALSE;
1687 if (!all)
1688 break;
1689 }
1690 }
1692 return TRUE;
1693 }
1695 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1697 static const char *
1698 get_keys(enum keymap keymap, enum request request, bool all)
1699 {
1700 static char buf[BUFSIZ];
1701 size_t pos = 0;
1702 int i;
1704 buf[pos] = 0;
1706 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1707 return "Too many keybindings!";
1708 if (pos > 0 && !all)
1709 return buf;
1711 if (keymap != KEYMAP_GENERIC) {
1712 /* Only the generic keymap includes the default keybindings when
1713 * listing all keys. */
1714 if (all)
1715 return buf;
1717 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1718 return "Too many keybindings!";
1719 if (pos)
1720 return buf;
1721 }
1723 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1724 if (default_keybindings[i].request == request) {
1725 if (!append_key(buf, &pos, &default_keybindings[i]))
1726 return "Too many keybindings!";
1727 if (!all)
1728 return buf;
1729 }
1730 }
1732 return buf;
1733 }
1735 struct run_request {
1736 enum keymap keymap;
1737 int key;
1738 const char **argv;
1739 };
1741 static struct run_request *run_request;
1742 static size_t run_requests;
1744 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1746 static enum request
1747 add_run_request(enum keymap keymap, int key, const char **argv)
1748 {
1749 struct run_request *req;
1751 if (!realloc_run_requests(&run_request, run_requests, 1))
1752 return REQ_NONE;
1754 req = &run_request[run_requests];
1755 req->keymap = keymap;
1756 req->key = key;
1757 req->argv = NULL;
1759 if (!argv_copy(&req->argv, argv))
1760 return REQ_NONE;
1762 return REQ_NONE + ++run_requests;
1763 }
1765 static struct run_request *
1766 get_run_request(enum request request)
1767 {
1768 if (request <= REQ_NONE)
1769 return NULL;
1770 return &run_request[request - REQ_NONE - 1];
1771 }
1773 static void
1774 add_builtin_run_requests(void)
1775 {
1776 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1777 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1778 const char *commit[] = { "git", "commit", NULL };
1779 const char *gc[] = { "git", "gc", NULL };
1780 struct run_request reqs[] = {
1781 { KEYMAP_MAIN, 'C', cherry_pick },
1782 { KEYMAP_STATUS, 'C', commit },
1783 { KEYMAP_BRANCH, 'C', checkout },
1784 { KEYMAP_GENERIC, 'G', gc },
1785 };
1786 int i;
1788 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1789 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1791 if (req != reqs[i].key)
1792 continue;
1793 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1794 if (req != REQ_NONE)
1795 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1796 }
1797 }
1799 /*
1800 * User config file handling.
1801 */
1803 static int config_lineno;
1804 static bool config_errors;
1805 static const char *config_msg;
1807 static const struct enum_map color_map[] = {
1808 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1809 COLOR_MAP(DEFAULT),
1810 COLOR_MAP(BLACK),
1811 COLOR_MAP(BLUE),
1812 COLOR_MAP(CYAN),
1813 COLOR_MAP(GREEN),
1814 COLOR_MAP(MAGENTA),
1815 COLOR_MAP(RED),
1816 COLOR_MAP(WHITE),
1817 COLOR_MAP(YELLOW),
1818 };
1820 static const struct enum_map attr_map[] = {
1821 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1822 ATTR_MAP(NORMAL),
1823 ATTR_MAP(BLINK),
1824 ATTR_MAP(BOLD),
1825 ATTR_MAP(DIM),
1826 ATTR_MAP(REVERSE),
1827 ATTR_MAP(STANDOUT),
1828 ATTR_MAP(UNDERLINE),
1829 };
1831 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1833 static int parse_step(double *opt, const char *arg)
1834 {
1835 *opt = atoi(arg);
1836 if (!strchr(arg, '%'))
1837 return OK;
1839 /* "Shift down" so 100% and 1 does not conflict. */
1840 *opt = (*opt - 1) / 100;
1841 if (*opt >= 1.0) {
1842 *opt = 0.99;
1843 config_msg = "Step value larger than 100%";
1844 return ERR;
1845 }
1846 if (*opt < 0.0) {
1847 *opt = 1;
1848 config_msg = "Invalid step value";
1849 return ERR;
1850 }
1851 return OK;
1852 }
1854 static int
1855 parse_int(int *opt, const char *arg, int min, int max)
1856 {
1857 int value = atoi(arg);
1859 if (min <= value && value <= max) {
1860 *opt = value;
1861 return OK;
1862 }
1864 config_msg = "Integer value out of bound";
1865 return ERR;
1866 }
1868 static bool
1869 set_color(int *color, const char *name)
1870 {
1871 if (map_enum(color, color_map, name))
1872 return TRUE;
1873 if (!prefixcmp(name, "color"))
1874 return parse_int(color, name + 5, 0, 255) == OK;
1875 return FALSE;
1876 }
1878 /* Wants: object fgcolor bgcolor [attribute] */
1879 static int
1880 option_color_command(int argc, const char *argv[])
1881 {
1882 struct line_info *info;
1884 if (argc < 3) {
1885 config_msg = "Wrong number of arguments given to color command";
1886 return ERR;
1887 }
1889 info = get_line_info(argv[0]);
1890 if (!info) {
1891 static const struct enum_map obsolete[] = {
1892 ENUM_MAP("main-delim", LINE_DELIMITER),
1893 ENUM_MAP("main-date", LINE_DATE),
1894 ENUM_MAP("main-author", LINE_AUTHOR),
1895 };
1896 int index;
1898 if (!map_enum(&index, obsolete, argv[0])) {
1899 config_msg = "Unknown color name";
1900 return ERR;
1901 }
1902 info = &line_info[index];
1903 }
1905 if (!set_color(&info->fg, argv[1]) ||
1906 !set_color(&info->bg, argv[2])) {
1907 config_msg = "Unknown color";
1908 return ERR;
1909 }
1911 info->attr = 0;
1912 while (argc-- > 3) {
1913 int attr;
1915 if (!set_attribute(&attr, argv[argc])) {
1916 config_msg = "Unknown attribute";
1917 return ERR;
1918 }
1919 info->attr |= attr;
1920 }
1922 return OK;
1923 }
1925 static int parse_bool(bool *opt, const char *arg)
1926 {
1927 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1928 ? TRUE : FALSE;
1929 return OK;
1930 }
1932 static int parse_enum_do(unsigned int *opt, const char *arg,
1933 const struct enum_map *map, size_t map_size)
1934 {
1935 bool is_true;
1937 assert(map_size > 1);
1939 if (map_enum_do(map, map_size, (int *) opt, arg))
1940 return OK;
1942 if (parse_bool(&is_true, arg) != OK)
1943 return ERR;
1945 *opt = is_true ? map[1].value : map[0].value;
1946 return OK;
1947 }
1949 #define parse_enum(opt, arg, map) \
1950 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1952 static int
1953 parse_string(char *opt, const char *arg, size_t optsize)
1954 {
1955 int arglen = strlen(arg);
1957 switch (arg[0]) {
1958 case '\"':
1959 case '\'':
1960 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1961 config_msg = "Unmatched quotation";
1962 return ERR;
1963 }
1964 arg += 1; arglen -= 2;
1965 default:
1966 string_ncopy_do(opt, optsize, arg, arglen);
1967 return OK;
1968 }
1969 }
1971 /* Wants: name = value */
1972 static int
1973 option_set_command(int argc, const char *argv[])
1974 {
1975 if (argc != 3) {
1976 config_msg = "Wrong number of arguments given to set command";
1977 return ERR;
1978 }
1980 if (strcmp(argv[1], "=")) {
1981 config_msg = "No value assigned";
1982 return ERR;
1983 }
1985 if (!strcmp(argv[0], "show-author"))
1986 return parse_enum(&opt_author, argv[2], author_map);
1988 if (!strcmp(argv[0], "show-date"))
1989 return parse_enum(&opt_date, argv[2], date_map);
1991 if (!strcmp(argv[0], "show-rev-graph"))
1992 return parse_bool(&opt_rev_graph, argv[2]);
1994 if (!strcmp(argv[0], "show-refs"))
1995 return parse_bool(&opt_show_refs, argv[2]);
1997 if (!strcmp(argv[0], "show-line-numbers"))
1998 return parse_bool(&opt_line_number, argv[2]);
2000 if (!strcmp(argv[0], "line-graphics"))
2001 return parse_bool(&opt_line_graphics, argv[2]);
2003 if (!strcmp(argv[0], "line-number-interval"))
2004 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2006 if (!strcmp(argv[0], "author-width"))
2007 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2009 if (!strcmp(argv[0], "horizontal-scroll"))
2010 return parse_step(&opt_hscroll, argv[2]);
2012 if (!strcmp(argv[0], "split-view-height"))
2013 return parse_step(&opt_scale_split_view, argv[2]);
2015 if (!strcmp(argv[0], "tab-size"))
2016 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2018 if (!strcmp(argv[0], "commit-encoding"))
2019 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2021 if (!strcmp(argv[0], "status-untracked-dirs"))
2022 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2024 config_msg = "Unknown variable name";
2025 return ERR;
2026 }
2028 /* Wants: mode request key */
2029 static int
2030 option_bind_command(int argc, const char *argv[])
2031 {
2032 enum request request;
2033 int keymap = -1;
2034 int key;
2036 if (argc < 3) {
2037 config_msg = "Wrong number of arguments given to bind command";
2038 return ERR;
2039 }
2041 if (!set_keymap(&keymap, argv[0])) {
2042 config_msg = "Unknown key map";
2043 return ERR;
2044 }
2046 key = get_key_value(argv[1]);
2047 if (key == ERR) {
2048 config_msg = "Unknown key";
2049 return ERR;
2050 }
2052 request = get_request(argv[2]);
2053 if (request == REQ_UNKNOWN) {
2054 static const struct enum_map obsolete[] = {
2055 ENUM_MAP("cherry-pick", REQ_NONE),
2056 ENUM_MAP("screen-resize", REQ_NONE),
2057 ENUM_MAP("tree-parent", REQ_PARENT),
2058 };
2059 int alias;
2061 if (map_enum(&alias, obsolete, argv[2])) {
2062 if (alias != REQ_NONE)
2063 add_keybinding(keymap, alias, key);
2064 config_msg = "Obsolete request name";
2065 return ERR;
2066 }
2067 }
2068 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2069 request = add_run_request(keymap, key, argv + 2);
2070 if (request == REQ_UNKNOWN) {
2071 config_msg = "Unknown request name";
2072 return ERR;
2073 }
2075 add_keybinding(keymap, request, key);
2077 return OK;
2078 }
2080 static int
2081 set_option(const char *opt, char *value)
2082 {
2083 const char *argv[SIZEOF_ARG];
2084 int argc = 0;
2086 if (!argv_from_string(argv, &argc, value)) {
2087 config_msg = "Too many option arguments";
2088 return ERR;
2089 }
2091 if (!strcmp(opt, "color"))
2092 return option_color_command(argc, argv);
2094 if (!strcmp(opt, "set"))
2095 return option_set_command(argc, argv);
2097 if (!strcmp(opt, "bind"))
2098 return option_bind_command(argc, argv);
2100 config_msg = "Unknown option command";
2101 return ERR;
2102 }
2104 static int
2105 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2106 {
2107 int status = OK;
2109 config_lineno++;
2110 config_msg = "Internal error";
2112 /* Check for comment markers, since read_properties() will
2113 * only ensure opt and value are split at first " \t". */
2114 optlen = strcspn(opt, "#");
2115 if (optlen == 0)
2116 return OK;
2118 if (opt[optlen] != 0) {
2119 config_msg = "No option value";
2120 status = ERR;
2122 } else {
2123 /* Look for comment endings in the value. */
2124 size_t len = strcspn(value, "#");
2126 if (len < valuelen) {
2127 valuelen = len;
2128 value[valuelen] = 0;
2129 }
2131 status = set_option(opt, value);
2132 }
2134 if (status == ERR) {
2135 warn("Error on line %d, near '%.*s': %s",
2136 config_lineno, (int) optlen, opt, config_msg);
2137 config_errors = TRUE;
2138 }
2140 /* Always keep going if errors are encountered. */
2141 return OK;
2142 }
2144 static void
2145 load_option_file(const char *path)
2146 {
2147 struct io io;
2149 /* It's OK that the file doesn't exist. */
2150 if (!io_open(&io, "%s", path))
2151 return;
2153 config_lineno = 0;
2154 config_errors = FALSE;
2156 if (io_load(&io, " \t", read_option) == ERR ||
2157 config_errors == TRUE)
2158 warn("Errors while loading %s.", path);
2159 }
2161 static int
2162 load_options(void)
2163 {
2164 const char *home = getenv("HOME");
2165 const char *tigrc_user = getenv("TIGRC_USER");
2166 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2167 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2168 char buf[SIZEOF_STR];
2170 if (!tigrc_system)
2171 tigrc_system = SYSCONFDIR "/tigrc";
2172 load_option_file(tigrc_system);
2174 if (!tigrc_user) {
2175 if (!home || !string_format(buf, "%s/.tigrc", home))
2176 return ERR;
2177 tigrc_user = buf;
2178 }
2179 load_option_file(tigrc_user);
2181 /* Add _after_ loading config files to avoid adding run requests
2182 * that conflict with keybindings. */
2183 add_builtin_run_requests();
2185 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2186 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2187 int argc = 0;
2189 if (!string_format(buf, "%s", tig_diff_opts) ||
2190 !argv_from_string(diff_opts, &argc, buf))
2191 die("TIG_DIFF_OPTS contains too many arguments");
2192 else if (!argv_copy(&opt_diff_args, diff_opts))
2193 die("Failed to format TIG_DIFF_OPTS arguments");
2194 }
2196 return OK;
2197 }
2200 /*
2201 * The viewer
2202 */
2204 struct view;
2205 struct view_ops;
2207 /* The display array of active views and the index of the current view. */
2208 static struct view *display[2];
2209 static unsigned int current_view;
2211 #define foreach_displayed_view(view, i) \
2212 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2214 #define displayed_views() (display[1] != NULL ? 2 : 1)
2216 /* Current head and commit ID */
2217 static char ref_blob[SIZEOF_REF] = "";
2218 static char ref_commit[SIZEOF_REF] = "HEAD";
2219 static char ref_head[SIZEOF_REF] = "HEAD";
2220 static char ref_branch[SIZEOF_REF] = "";
2222 enum view_type {
2223 VIEW_MAIN,
2224 VIEW_DIFF,
2225 VIEW_LOG,
2226 VIEW_TREE,
2227 VIEW_BLOB,
2228 VIEW_BLAME,
2229 VIEW_BRANCH,
2230 VIEW_HELP,
2231 VIEW_PAGER,
2232 VIEW_STATUS,
2233 VIEW_STAGE,
2234 };
2236 struct view {
2237 enum view_type type; /* View type */
2238 const char *name; /* View name */
2239 const char *cmd_env; /* Command line set via environment */
2240 const char *id; /* Points to either of ref_{head,commit,blob} */
2242 struct view_ops *ops; /* View operations */
2244 enum keymap keymap; /* What keymap does this view have */
2245 bool git_dir; /* Whether the view requires a git directory. */
2247 char ref[SIZEOF_REF]; /* Hovered commit reference */
2248 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2250 int height, width; /* The width and height of the main window */
2251 WINDOW *win; /* The main window */
2252 WINDOW *title; /* The title window living below the main window */
2254 /* Navigation */
2255 unsigned long offset; /* Offset of the window top */
2256 unsigned long yoffset; /* Offset from the window side. */
2257 unsigned long lineno; /* Current line number */
2258 unsigned long p_offset; /* Previous offset of the window top */
2259 unsigned long p_yoffset;/* Previous offset from the window side */
2260 unsigned long p_lineno; /* Previous current line number */
2261 bool p_restore; /* Should the previous position be restored. */
2263 /* Searching */
2264 char grep[SIZEOF_STR]; /* Search string */
2265 regex_t *regex; /* Pre-compiled regexp */
2267 /* If non-NULL, points to the view that opened this view. If this view
2268 * is closed tig will switch back to the parent view. */
2269 struct view *parent;
2270 struct view *prev;
2272 /* Buffering */
2273 size_t lines; /* Total number of lines */
2274 struct line *line; /* Line index */
2275 unsigned int digits; /* Number of digits in the lines member. */
2277 /* Drawing */
2278 struct line *curline; /* Line currently being drawn. */
2279 enum line_type curtype; /* Attribute currently used for drawing. */
2280 unsigned long col; /* Column when drawing. */
2281 bool has_scrolled; /* View was scrolled. */
2283 /* Loading */
2284 const char **argv; /* Shell command arguments. */
2285 const char *dir; /* Directory from which to execute. */
2286 struct io io;
2287 struct io *pipe;
2288 time_t start_time;
2289 time_t update_secs;
2290 };
2292 struct view_ops {
2293 /* What type of content being displayed. Used in the title bar. */
2294 const char *type;
2295 /* Default command arguments. */
2296 const char **argv;
2297 /* Open and reads in all view content. */
2298 bool (*open)(struct view *view);
2299 /* Read one line; updates view->line. */
2300 bool (*read)(struct view *view, char *data);
2301 /* Draw one line; @lineno must be < view->height. */
2302 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2303 /* Depending on view handle a special requests. */
2304 enum request (*request)(struct view *view, enum request request, struct line *line);
2305 /* Search for regexp in a line. */
2306 bool (*grep)(struct view *view, struct line *line);
2307 /* Select line */
2308 void (*select)(struct view *view, struct line *line);
2309 /* Prepare view for loading */
2310 bool (*prepare)(struct view *view);
2311 };
2313 static struct view_ops blame_ops;
2314 static struct view_ops blob_ops;
2315 static struct view_ops diff_ops;
2316 static struct view_ops help_ops;
2317 static struct view_ops log_ops;
2318 static struct view_ops main_ops;
2319 static struct view_ops pager_ops;
2320 static struct view_ops stage_ops;
2321 static struct view_ops status_ops;
2322 static struct view_ops tree_ops;
2323 static struct view_ops branch_ops;
2325 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2326 { type, name, #env, ref, ops, map, git }
2328 #define VIEW_(id, name, ops, git, ref) \
2329 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2331 static struct view views[] = {
2332 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2333 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2334 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2335 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2336 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2337 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2338 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2339 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2340 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2341 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2342 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2343 };
2345 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2347 #define foreach_view(view, i) \
2348 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2350 #define view_is_displayed(view) \
2351 (view == display[0] || view == display[1])
2353 static enum request
2354 view_request(struct view *view, enum request request)
2355 {
2356 if (!view || !view->lines)
2357 return request;
2358 return view->ops->request(view, request, &view->line[view->lineno]);
2359 }
2362 /*
2363 * View drawing.
2364 */
2366 static inline void
2367 set_view_attr(struct view *view, enum line_type type)
2368 {
2369 if (!view->curline->selected && view->curtype != type) {
2370 (void) wattrset(view->win, get_line_attr(type));
2371 wchgat(view->win, -1, 0, type, NULL);
2372 view->curtype = type;
2373 }
2374 }
2376 static int
2377 draw_chars(struct view *view, enum line_type type, const char *string,
2378 int max_len, bool use_tilde)
2379 {
2380 static char out_buffer[BUFSIZ * 2];
2381 int len = 0;
2382 int col = 0;
2383 int trimmed = FALSE;
2384 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2386 if (max_len <= 0)
2387 return 0;
2389 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2391 set_view_attr(view, type);
2392 if (len > 0) {
2393 if (opt_iconv_out != ICONV_NONE) {
2394 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2395 size_t inlen = len + 1;
2397 char *outbuf = out_buffer;
2398 size_t outlen = sizeof(out_buffer);
2400 size_t ret;
2402 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2403 if (ret != (size_t) -1) {
2404 string = out_buffer;
2405 len = sizeof(out_buffer) - outlen;
2406 }
2407 }
2409 waddnstr(view->win, string, len);
2411 if (trimmed && use_tilde) {
2412 set_view_attr(view, LINE_DELIMITER);
2413 waddch(view->win, '~');
2414 col++;
2415 }
2416 }
2418 return col;
2419 }
2421 static int
2422 draw_space(struct view *view, enum line_type type, int max, int spaces)
2423 {
2424 static char space[] = " ";
2425 int col = 0;
2427 spaces = MIN(max, spaces);
2429 while (spaces > 0) {
2430 int len = MIN(spaces, sizeof(space) - 1);
2432 col += draw_chars(view, type, space, len, FALSE);
2433 spaces -= len;
2434 }
2436 return col;
2437 }
2439 static bool
2440 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2441 {
2442 char text[SIZEOF_STR];
2444 do {
2445 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2447 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2448 string += pos;
2449 } while (*string && view->width + view->yoffset > view->col);
2451 return view->width + view->yoffset <= view->col;
2452 }
2454 static bool
2455 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2456 {
2457 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2458 int max = view->width + view->yoffset - view->col;
2459 int i;
2461 if (max < size)
2462 size = max;
2464 set_view_attr(view, type);
2465 /* Using waddch() instead of waddnstr() ensures that
2466 * they'll be rendered correctly for the cursor line. */
2467 for (i = skip; i < size; i++)
2468 waddch(view->win, graphic[i]);
2470 view->col += size;
2471 if (size < max && skip <= size)
2472 waddch(view->win, ' ');
2473 view->col++;
2475 return view->width + view->yoffset <= view->col;
2476 }
2478 static bool
2479 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2480 {
2481 int max = MIN(view->width + view->yoffset - view->col, len);
2482 int col;
2484 if (text)
2485 col = draw_chars(view, type, text, max - 1, trim);
2486 else
2487 col = draw_space(view, type, max - 1, max - 1);
2489 view->col += col;
2490 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2491 return view->width + view->yoffset <= view->col;
2492 }
2494 static bool
2495 draw_date(struct view *view, struct time *time)
2496 {
2497 const char *date = mkdate(time, opt_date);
2498 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2500 return draw_field(view, LINE_DATE, date, cols, FALSE);
2501 }
2503 static bool
2504 draw_author(struct view *view, const char *author)
2505 {
2506 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2507 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2509 if (abbreviate && author)
2510 author = get_author_initials(author);
2512 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2513 }
2515 static bool
2516 draw_mode(struct view *view, mode_t mode)
2517 {
2518 const char *str;
2520 if (S_ISDIR(mode))
2521 str = "drwxr-xr-x";
2522 else if (S_ISLNK(mode))
2523 str = "lrwxrwxrwx";
2524 else if (S_ISGITLINK(mode))
2525 str = "m---------";
2526 else if (S_ISREG(mode) && mode & S_IXUSR)
2527 str = "-rwxr-xr-x";
2528 else if (S_ISREG(mode))
2529 str = "-rw-r--r--";
2530 else
2531 str = "----------";
2533 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2534 }
2536 static bool
2537 draw_lineno(struct view *view, unsigned int lineno)
2538 {
2539 char number[10];
2540 int digits3 = view->digits < 3 ? 3 : view->digits;
2541 int max = MIN(view->width + view->yoffset - view->col, digits3);
2542 char *text = NULL;
2543 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2545 lineno += view->offset + 1;
2546 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2547 static char fmt[] = "%1ld";
2549 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2550 if (string_format(number, fmt, lineno))
2551 text = number;
2552 }
2553 if (text)
2554 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2555 else
2556 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2557 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2558 }
2560 static bool
2561 draw_view_line(struct view *view, unsigned int lineno)
2562 {
2563 struct line *line;
2564 bool selected = (view->offset + lineno == view->lineno);
2566 assert(view_is_displayed(view));
2568 if (view->offset + lineno >= view->lines)
2569 return FALSE;
2571 line = &view->line[view->offset + lineno];
2573 wmove(view->win, lineno, 0);
2574 if (line->cleareol)
2575 wclrtoeol(view->win);
2576 view->col = 0;
2577 view->curline = line;
2578 view->curtype = LINE_NONE;
2579 line->selected = FALSE;
2580 line->dirty = line->cleareol = 0;
2582 if (selected) {
2583 set_view_attr(view, LINE_CURSOR);
2584 line->selected = TRUE;
2585 view->ops->select(view, line);
2586 }
2588 return view->ops->draw(view, line, lineno);
2589 }
2591 static void
2592 redraw_view_dirty(struct view *view)
2593 {
2594 bool dirty = FALSE;
2595 int lineno;
2597 for (lineno = 0; lineno < view->height; lineno++) {
2598 if (view->offset + lineno >= view->lines)
2599 break;
2600 if (!view->line[view->offset + lineno].dirty)
2601 continue;
2602 dirty = TRUE;
2603 if (!draw_view_line(view, lineno))
2604 break;
2605 }
2607 if (!dirty)
2608 return;
2609 wnoutrefresh(view->win);
2610 }
2612 static void
2613 redraw_view_from(struct view *view, int lineno)
2614 {
2615 assert(0 <= lineno && lineno < view->height);
2617 for (; lineno < view->height; lineno++) {
2618 if (!draw_view_line(view, lineno))
2619 break;
2620 }
2622 wnoutrefresh(view->win);
2623 }
2625 static void
2626 redraw_view(struct view *view)
2627 {
2628 werase(view->win);
2629 redraw_view_from(view, 0);
2630 }
2633 static void
2634 update_view_title(struct view *view)
2635 {
2636 char buf[SIZEOF_STR];
2637 char state[SIZEOF_STR];
2638 size_t bufpos = 0, statelen = 0;
2640 assert(view_is_displayed(view));
2642 if (view->type != VIEW_STATUS && view->lines) {
2643 unsigned int view_lines = view->offset + view->height;
2644 unsigned int lines = view->lines
2645 ? MIN(view_lines, view->lines) * 100 / view->lines
2646 : 0;
2648 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2649 view->ops->type,
2650 view->lineno + 1,
2651 view->lines,
2652 lines);
2654 }
2656 if (view->pipe) {
2657 time_t secs = time(NULL) - view->start_time;
2659 /* Three git seconds are a long time ... */
2660 if (secs > 2)
2661 string_format_from(state, &statelen, " loading %lds", secs);
2662 }
2664 string_format_from(buf, &bufpos, "[%s]", view->name);
2665 if (*view->ref && bufpos < view->width) {
2666 size_t refsize = strlen(view->ref);
2667 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2669 if (minsize < view->width)
2670 refsize = view->width - minsize + 7;
2671 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2672 }
2674 if (statelen && bufpos < view->width) {
2675 string_format_from(buf, &bufpos, "%s", state);
2676 }
2678 if (view == display[current_view])
2679 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2680 else
2681 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2683 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2684 wclrtoeol(view->title);
2685 wnoutrefresh(view->title);
2686 }
2688 static int
2689 apply_step(double step, int value)
2690 {
2691 if (step >= 1)
2692 return (int) step;
2693 value *= step + 0.01;
2694 return value ? value : 1;
2695 }
2697 static void
2698 resize_display(void)
2699 {
2700 int offset, i;
2701 struct view *base = display[0];
2702 struct view *view = display[1] ? display[1] : display[0];
2704 /* Setup window dimensions */
2706 getmaxyx(stdscr, base->height, base->width);
2708 /* Make room for the status window. */
2709 base->height -= 1;
2711 if (view != base) {
2712 /* Horizontal split. */
2713 view->width = base->width;
2714 view->height = apply_step(opt_scale_split_view, base->height);
2715 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2716 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2717 base->height -= view->height;
2719 /* Make room for the title bar. */
2720 view->height -= 1;
2721 }
2723 /* Make room for the title bar. */
2724 base->height -= 1;
2726 offset = 0;
2728 foreach_displayed_view (view, i) {
2729 if (!view->win) {
2730 view->win = newwin(view->height, 0, offset, 0);
2731 if (!view->win)
2732 die("Failed to create %s view", view->name);
2734 scrollok(view->win, FALSE);
2736 view->title = newwin(1, 0, offset + view->height, 0);
2737 if (!view->title)
2738 die("Failed to create title window");
2740 } else {
2741 wresize(view->win, view->height, view->width);
2742 mvwin(view->win, offset, 0);
2743 mvwin(view->title, offset + view->height, 0);
2744 }
2746 offset += view->height + 1;
2747 }
2748 }
2750 static void
2751 redraw_display(bool clear)
2752 {
2753 struct view *view;
2754 int i;
2756 foreach_displayed_view (view, i) {
2757 if (clear)
2758 wclear(view->win);
2759 redraw_view(view);
2760 update_view_title(view);
2761 }
2762 }
2765 /*
2766 * Option management
2767 */
2769 static void
2770 toggle_enum_option_do(unsigned int *opt, const char *help,
2771 const struct enum_map *map, size_t size)
2772 {
2773 *opt = (*opt + 1) % size;
2774 redraw_display(FALSE);
2775 report("Displaying %s %s", enum_name(map[*opt]), help);
2776 }
2778 #define toggle_enum_option(opt, help, map) \
2779 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2781 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2782 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2784 static void
2785 toggle_view_option(bool *option, const char *help)
2786 {
2787 *option = !*option;
2788 redraw_display(FALSE);
2789 report("%sabling %s", *option ? "En" : "Dis", help);
2790 }
2792 static void
2793 open_option_menu(void)
2794 {
2795 const struct menu_item menu[] = {
2796 { '.', "line numbers", &opt_line_number },
2797 { 'D', "date display", &opt_date },
2798 { 'A', "author display", &opt_author },
2799 { 'g', "revision graph display", &opt_rev_graph },
2800 { 'F', "reference display", &opt_show_refs },
2801 { 0 }
2802 };
2803 int selected = 0;
2805 if (prompt_menu("Toggle option", menu, &selected)) {
2806 if (menu[selected].data == &opt_date)
2807 toggle_date();
2808 else if (menu[selected].data == &opt_author)
2809 toggle_author();
2810 else
2811 toggle_view_option(menu[selected].data, menu[selected].text);
2812 }
2813 }
2815 static void
2816 maximize_view(struct view *view)
2817 {
2818 memset(display, 0, sizeof(display));
2819 current_view = 0;
2820 display[current_view] = view;
2821 resize_display();
2822 redraw_display(FALSE);
2823 report("");
2824 }
2827 /*
2828 * Navigation
2829 */
2831 static bool
2832 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2833 {
2834 if (lineno >= view->lines)
2835 lineno = view->lines > 0 ? view->lines - 1 : 0;
2837 if (offset > lineno || offset + view->height <= lineno) {
2838 unsigned long half = view->height / 2;
2840 if (lineno > half)
2841 offset = lineno - half;
2842 else
2843 offset = 0;
2844 }
2846 if (offset != view->offset || lineno != view->lineno) {
2847 view->offset = offset;
2848 view->lineno = lineno;
2849 return TRUE;
2850 }
2852 return FALSE;
2853 }
2855 /* Scrolling backend */
2856 static void
2857 do_scroll_view(struct view *view, int lines)
2858 {
2859 bool redraw_current_line = FALSE;
2861 /* The rendering expects the new offset. */
2862 view->offset += lines;
2864 assert(0 <= view->offset && view->offset < view->lines);
2865 assert(lines);
2867 /* Move current line into the view. */
2868 if (view->lineno < view->offset) {
2869 view->lineno = view->offset;
2870 redraw_current_line = TRUE;
2871 } else if (view->lineno >= view->offset + view->height) {
2872 view->lineno = view->offset + view->height - 1;
2873 redraw_current_line = TRUE;
2874 }
2876 assert(view->offset <= view->lineno && view->lineno < view->lines);
2878 /* Redraw the whole screen if scrolling is pointless. */
2879 if (view->height < ABS(lines)) {
2880 redraw_view(view);
2882 } else {
2883 int line = lines > 0 ? view->height - lines : 0;
2884 int end = line + ABS(lines);
2886 scrollok(view->win, TRUE);
2887 wscrl(view->win, lines);
2888 scrollok(view->win, FALSE);
2890 while (line < end && draw_view_line(view, line))
2891 line++;
2893 if (redraw_current_line)
2894 draw_view_line(view, view->lineno - view->offset);
2895 wnoutrefresh(view->win);
2896 }
2898 view->has_scrolled = TRUE;
2899 report("");
2900 }
2902 /* Scroll frontend */
2903 static void
2904 scroll_view(struct view *view, enum request request)
2905 {
2906 int lines = 1;
2908 assert(view_is_displayed(view));
2910 switch (request) {
2911 case REQ_SCROLL_FIRST_COL:
2912 view->yoffset = 0;
2913 redraw_view_from(view, 0);
2914 report("");
2915 return;
2916 case REQ_SCROLL_LEFT:
2917 if (view->yoffset == 0) {
2918 report("Cannot scroll beyond the first column");
2919 return;
2920 }
2921 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2922 view->yoffset = 0;
2923 else
2924 view->yoffset -= apply_step(opt_hscroll, view->width);
2925 redraw_view_from(view, 0);
2926 report("");
2927 return;
2928 case REQ_SCROLL_RIGHT:
2929 view->yoffset += apply_step(opt_hscroll, view->width);
2930 redraw_view(view);
2931 report("");
2932 return;
2933 case REQ_SCROLL_PAGE_DOWN:
2934 lines = view->height;
2935 case REQ_SCROLL_LINE_DOWN:
2936 if (view->offset + lines > view->lines)
2937 lines = view->lines - view->offset;
2939 if (lines == 0 || view->offset + view->height >= view->lines) {
2940 report("Cannot scroll beyond the last line");
2941 return;
2942 }
2943 break;
2945 case REQ_SCROLL_PAGE_UP:
2946 lines = view->height;
2947 case REQ_SCROLL_LINE_UP:
2948 if (lines > view->offset)
2949 lines = view->offset;
2951 if (lines == 0) {
2952 report("Cannot scroll beyond the first line");
2953 return;
2954 }
2956 lines = -lines;
2957 break;
2959 default:
2960 die("request %d not handled in switch", request);
2961 }
2963 do_scroll_view(view, lines);
2964 }
2966 /* Cursor moving */
2967 static void
2968 move_view(struct view *view, enum request request)
2969 {
2970 int scroll_steps = 0;
2971 int steps;
2973 switch (request) {
2974 case REQ_MOVE_FIRST_LINE:
2975 steps = -view->lineno;
2976 break;
2978 case REQ_MOVE_LAST_LINE:
2979 steps = view->lines - view->lineno - 1;
2980 break;
2982 case REQ_MOVE_PAGE_UP:
2983 steps = view->height > view->lineno
2984 ? -view->lineno : -view->height;
2985 break;
2987 case REQ_MOVE_PAGE_DOWN:
2988 steps = view->lineno + view->height >= view->lines
2989 ? view->lines - view->lineno - 1 : view->height;
2990 break;
2992 case REQ_MOVE_UP:
2993 steps = -1;
2994 break;
2996 case REQ_MOVE_DOWN:
2997 steps = 1;
2998 break;
3000 default:
3001 die("request %d not handled in switch", request);
3002 }
3004 if (steps <= 0 && view->lineno == 0) {
3005 report("Cannot move beyond the first line");
3006 return;
3008 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3009 report("Cannot move beyond the last line");
3010 return;
3011 }
3013 /* Move the current line */
3014 view->lineno += steps;
3015 assert(0 <= view->lineno && view->lineno < view->lines);
3017 /* Check whether the view needs to be scrolled */
3018 if (view->lineno < view->offset ||
3019 view->lineno >= view->offset + view->height) {
3020 scroll_steps = steps;
3021 if (steps < 0 && -steps > view->offset) {
3022 scroll_steps = -view->offset;
3024 } else if (steps > 0) {
3025 if (view->lineno == view->lines - 1 &&
3026 view->lines > view->height) {
3027 scroll_steps = view->lines - view->offset - 1;
3028 if (scroll_steps >= view->height)
3029 scroll_steps -= view->height - 1;
3030 }
3031 }
3032 }
3034 if (!view_is_displayed(view)) {
3035 view->offset += scroll_steps;
3036 assert(0 <= view->offset && view->offset < view->lines);
3037 view->ops->select(view, &view->line[view->lineno]);
3038 return;
3039 }
3041 /* Repaint the old "current" line if we be scrolling */
3042 if (ABS(steps) < view->height)
3043 draw_view_line(view, view->lineno - steps - view->offset);
3045 if (scroll_steps) {
3046 do_scroll_view(view, scroll_steps);
3047 return;
3048 }
3050 /* Draw the current line */
3051 draw_view_line(view, view->lineno - view->offset);
3053 wnoutrefresh(view->win);
3054 report("");
3055 }
3058 /*
3059 * Searching
3060 */
3062 static void search_view(struct view *view, enum request request);
3064 static bool
3065 grep_text(struct view *view, const char *text[])
3066 {
3067 regmatch_t pmatch;
3068 size_t i;
3070 for (i = 0; text[i]; i++)
3071 if (*text[i] &&
3072 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3073 return TRUE;
3074 return FALSE;
3075 }
3077 static void
3078 select_view_line(struct view *view, unsigned long lineno)
3079 {
3080 unsigned long old_lineno = view->lineno;
3081 unsigned long old_offset = view->offset;
3083 if (goto_view_line(view, view->offset, lineno)) {
3084 if (view_is_displayed(view)) {
3085 if (old_offset != view->offset) {
3086 redraw_view(view);
3087 } else {
3088 draw_view_line(view, old_lineno - view->offset);
3089 draw_view_line(view, view->lineno - view->offset);
3090 wnoutrefresh(view->win);
3091 }
3092 } else {
3093 view->ops->select(view, &view->line[view->lineno]);
3094 }
3095 }
3096 }
3098 static void
3099 find_next(struct view *view, enum request request)
3100 {
3101 unsigned long lineno = view->lineno;
3102 int direction;
3104 if (!*view->grep) {
3105 if (!*opt_search)
3106 report("No previous search");
3107 else
3108 search_view(view, request);
3109 return;
3110 }
3112 switch (request) {
3113 case REQ_SEARCH:
3114 case REQ_FIND_NEXT:
3115 direction = 1;
3116 break;
3118 case REQ_SEARCH_BACK:
3119 case REQ_FIND_PREV:
3120 direction = -1;
3121 break;
3123 default:
3124 return;
3125 }
3127 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3128 lineno += direction;
3130 /* Note, lineno is unsigned long so will wrap around in which case it
3131 * will become bigger than view->lines. */
3132 for (; lineno < view->lines; lineno += direction) {
3133 if (view->ops->grep(view, &view->line[lineno])) {
3134 select_view_line(view, lineno);
3135 report("Line %ld matches '%s'", lineno + 1, view->grep);
3136 return;
3137 }
3138 }
3140 report("No match found for '%s'", view->grep);
3141 }
3143 static void
3144 search_view(struct view *view, enum request request)
3145 {
3146 int regex_err;
3148 if (view->regex) {
3149 regfree(view->regex);
3150 *view->grep = 0;
3151 } else {
3152 view->regex = calloc(1, sizeof(*view->regex));
3153 if (!view->regex)
3154 return;
3155 }
3157 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3158 if (regex_err != 0) {
3159 char buf[SIZEOF_STR] = "unknown error";
3161 regerror(regex_err, view->regex, buf, sizeof(buf));
3162 report("Search failed: %s", buf);
3163 return;
3164 }
3166 string_copy(view->grep, opt_search);
3168 find_next(view, request);
3169 }
3171 /*
3172 * Incremental updating
3173 */
3175 static void
3176 reset_view(struct view *view)
3177 {
3178 int i;
3180 for (i = 0; i < view->lines; i++)
3181 free(view->line[i].data);
3182 free(view->line);
3184 view->p_offset = view->offset;
3185 view->p_yoffset = view->yoffset;
3186 view->p_lineno = view->lineno;
3188 view->line = NULL;
3189 view->offset = 0;
3190 view->yoffset = 0;
3191 view->lines = 0;
3192 view->lineno = 0;
3193 view->vid[0] = 0;
3194 view->update_secs = 0;
3195 }
3197 static const char *
3198 format_arg(const char *name)
3199 {
3200 static struct {
3201 const char *name;
3202 size_t namelen;
3203 const char *value;
3204 const char *value_if_empty;
3205 } vars[] = {
3206 #define FORMAT_VAR(name, value, value_if_empty) \
3207 { name, STRING_SIZE(name), value, value_if_empty }
3208 FORMAT_VAR("%(directory)", opt_path, ""),
3209 FORMAT_VAR("%(file)", opt_file, ""),
3210 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3211 FORMAT_VAR("%(head)", ref_head, ""),
3212 FORMAT_VAR("%(commit)", ref_commit, ""),
3213 FORMAT_VAR("%(blob)", ref_blob, ""),
3214 FORMAT_VAR("%(branch)", ref_branch, ""),
3215 };
3216 int i;
3218 for (i = 0; i < ARRAY_SIZE(vars); i++)
3219 if (!strncmp(name, vars[i].name, vars[i].namelen))
3220 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3222 report("Unknown replacement: `%s`", name);
3223 return NULL;
3224 }
3226 static bool
3227 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3228 {
3229 char buf[SIZEOF_STR];
3230 int argc;
3232 argv_free(*dst_argv);
3234 for (argc = 0; src_argv[argc]; argc++) {
3235 const char *arg = src_argv[argc];
3236 size_t bufpos = 0;
3238 if (!strcmp(arg, "%(fileargs)")) {
3239 if (!argv_append_array(dst_argv, opt_file_args))
3240 break;
3241 continue;
3243 } else if (!strcmp(arg, "%(diffargs)")) {
3244 if (!argv_append_array(dst_argv, opt_diff_args))
3245 break;
3246 continue;
3248 } else if (!strcmp(arg, "%(revargs)") ||
3249 (first && !strcmp(arg, "%(commit)"))) {
3250 if (!argv_append_array(dst_argv, opt_rev_args))
3251 break;
3252 continue;
3253 }
3255 while (arg) {
3256 char *next = strstr(arg, "%(");
3257 int len = next - arg;
3258 const char *value;
3260 if (!next || !replace) {
3261 len = strlen(arg);
3262 value = "";
3264 } else {
3265 value = format_arg(next);
3267 if (!value) {
3268 return FALSE;
3269 }
3270 }
3272 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3273 return FALSE;
3275 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3276 }
3278 if (!argv_append(dst_argv, buf))
3279 break;
3280 }
3282 return src_argv[argc] == NULL;
3283 }
3285 static bool
3286 restore_view_position(struct view *view)
3287 {
3288 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3289 return FALSE;
3291 /* Changing the view position cancels the restoring. */
3292 /* FIXME: Changing back to the first line is not detected. */
3293 if (view->offset != 0 || view->lineno != 0) {
3294 view->p_restore = FALSE;
3295 return FALSE;
3296 }
3298 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3299 view_is_displayed(view))
3300 werase(view->win);
3302 view->yoffset = view->p_yoffset;
3303 view->p_restore = FALSE;
3305 return TRUE;
3306 }
3308 static void
3309 end_update(struct view *view, bool force)
3310 {
3311 if (!view->pipe)
3312 return;
3313 while (!view->ops->read(view, NULL))
3314 if (!force)
3315 return;
3316 if (force)
3317 io_kill(view->pipe);
3318 io_done(view->pipe);
3319 view->pipe = NULL;
3320 }
3322 static void
3323 setup_update(struct view *view, const char *vid)
3324 {
3325 reset_view(view);
3326 string_copy_rev(view->vid, vid);
3327 view->pipe = &view->io;
3328 view->start_time = time(NULL);
3329 }
3331 static bool
3332 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3333 {
3334 view->dir = dir;
3335 return format_argv(&view->argv, argv, replace, !view->prev);
3336 }
3338 static bool
3339 prepare_update(struct view *view, const char *argv[], const char *dir)
3340 {
3341 if (view->pipe)
3342 end_update(view, TRUE);
3343 return prepare_io(view, dir, argv, FALSE);
3344 }
3346 static bool
3347 start_update(struct view *view, const char **argv, const char *dir)
3348 {
3349 if (view->pipe)
3350 io_done(view->pipe);
3351 return prepare_io(view, dir, argv, FALSE) &&
3352 io_run(&view->io, IO_RD, dir, view->argv);
3353 }
3355 static bool
3356 prepare_update_file(struct view *view, const char *name)
3357 {
3358 if (view->pipe)
3359 end_update(view, TRUE);
3360 argv_free(view->argv);
3361 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3362 }
3364 static bool
3365 begin_update(struct view *view, bool refresh)
3366 {
3367 if (view->pipe)
3368 end_update(view, TRUE);
3370 if (!refresh) {
3371 if (view->ops->prepare) {
3372 if (!view->ops->prepare(view))
3373 return FALSE;
3374 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3375 return FALSE;
3376 }
3378 /* Put the current ref_* value to the view title ref
3379 * member. This is needed by the blob view. Most other
3380 * views sets it automatically after loading because the
3381 * first line is a commit line. */
3382 string_copy_rev(view->ref, view->id);
3383 }
3385 if (view->argv && view->argv[0] &&
3386 !io_run(&view->io, IO_RD, view->dir, view->argv))
3387 return FALSE;
3389 setup_update(view, view->id);
3391 return TRUE;
3392 }
3394 static bool
3395 update_view(struct view *view)
3396 {
3397 char out_buffer[BUFSIZ * 2];
3398 char *line;
3399 /* Clear the view and redraw everything since the tree sorting
3400 * might have rearranged things. */
3401 bool redraw = view->lines == 0;
3402 bool can_read = TRUE;
3404 if (!view->pipe)
3405 return TRUE;
3407 if (!io_can_read(view->pipe)) {
3408 if (view->lines == 0 && view_is_displayed(view)) {
3409 time_t secs = time(NULL) - view->start_time;
3411 if (secs > 1 && secs > view->update_secs) {
3412 if (view->update_secs == 0)
3413 redraw_view(view);
3414 update_view_title(view);
3415 view->update_secs = secs;
3416 }
3417 }
3418 return TRUE;
3419 }
3421 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3422 if (opt_iconv_in != ICONV_NONE) {
3423 ICONV_CONST char *inbuf = line;
3424 size_t inlen = strlen(line) + 1;
3426 char *outbuf = out_buffer;
3427 size_t outlen = sizeof(out_buffer);
3429 size_t ret;
3431 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3432 if (ret != (size_t) -1)
3433 line = out_buffer;
3434 }
3436 if (!view->ops->read(view, line)) {
3437 report("Allocation failure");
3438 end_update(view, TRUE);
3439 return FALSE;
3440 }
3441 }
3443 {
3444 unsigned long lines = view->lines;
3445 int digits;
3447 for (digits = 0; lines; digits++)
3448 lines /= 10;
3450 /* Keep the displayed view in sync with line number scaling. */
3451 if (digits != view->digits) {
3452 view->digits = digits;
3453 if (opt_line_number || view->type == VIEW_BLAME)
3454 redraw = TRUE;
3455 }
3456 }
3458 if (io_error(view->pipe)) {
3459 report("Failed to read: %s", io_strerror(view->pipe));
3460 end_update(view, TRUE);
3462 } else if (io_eof(view->pipe)) {
3463 if (view_is_displayed(view))
3464 report("");
3465 end_update(view, FALSE);
3466 }
3468 if (restore_view_position(view))
3469 redraw = TRUE;
3471 if (!view_is_displayed(view))
3472 return TRUE;
3474 if (redraw)
3475 redraw_view_from(view, 0);
3476 else
3477 redraw_view_dirty(view);
3479 /* Update the title _after_ the redraw so that if the redraw picks up a
3480 * commit reference in view->ref it'll be available here. */
3481 update_view_title(view);
3482 return TRUE;
3483 }
3485 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3487 static struct line *
3488 add_line_data(struct view *view, void *data, enum line_type type)
3489 {
3490 struct line *line;
3492 if (!realloc_lines(&view->line, view->lines, 1))
3493 return NULL;
3495 line = &view->line[view->lines++];
3496 memset(line, 0, sizeof(*line));
3497 line->type = type;
3498 line->data = data;
3499 line->dirty = 1;
3501 return line;
3502 }
3504 static struct line *
3505 add_line_text(struct view *view, const char *text, enum line_type type)
3506 {
3507 char *data = text ? strdup(text) : NULL;
3509 return data ? add_line_data(view, data, type) : NULL;
3510 }
3512 static struct line *
3513 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3514 {
3515 char buf[SIZEOF_STR];
3516 va_list args;
3518 va_start(args, fmt);
3519 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3520 buf[0] = 0;
3521 va_end(args);
3523 return buf[0] ? add_line_text(view, buf, type) : NULL;
3524 }
3526 /*
3527 * View opening
3528 */
3530 enum open_flags {
3531 OPEN_DEFAULT = 0, /* Use default view switching. */
3532 OPEN_SPLIT = 1, /* Split current view. */
3533 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3534 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3535 OPEN_PREPARED = 32, /* Open already prepared command. */
3536 };
3538 static void
3539 open_view(struct view *prev, enum request request, enum open_flags flags)
3540 {
3541 bool split = !!(flags & OPEN_SPLIT);
3542 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3543 bool nomaximize = !!(flags & OPEN_REFRESH);
3544 struct view *view = VIEW(request);
3545 int nviews = displayed_views();
3546 struct view *base_view = display[0];
3548 if (view == prev && nviews == 1 && !reload) {
3549 report("Already in %s view", view->name);
3550 return;
3551 }
3553 if (view->git_dir && !opt_git_dir[0]) {
3554 report("The %s view is disabled in pager view", view->name);
3555 return;
3556 }
3558 if (split) {
3559 display[1] = view;
3560 current_view = 1;
3561 view->parent = prev;
3562 } else if (!nomaximize) {
3563 /* Maximize the current view. */
3564 memset(display, 0, sizeof(display));
3565 current_view = 0;
3566 display[current_view] = view;
3567 }
3569 /* No prev signals that this is the first loaded view. */
3570 if (prev && view != prev) {
3571 view->prev = prev;
3572 }
3574 /* Resize the view when switching between split- and full-screen,
3575 * or when switching between two different full-screen views. */
3576 if (nviews != displayed_views() ||
3577 (nviews == 1 && base_view != display[0]))
3578 resize_display();
3580 if (view->ops->open) {
3581 if (view->pipe)
3582 end_update(view, TRUE);
3583 if (!view->ops->open(view)) {
3584 report("Failed to load %s view", view->name);
3585 return;
3586 }
3587 restore_view_position(view);
3589 } else if ((reload || strcmp(view->vid, view->id)) &&
3590 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3591 report("Failed to load %s view", view->name);
3592 return;
3593 }
3595 if (split && prev->lineno - prev->offset >= prev->height) {
3596 /* Take the title line into account. */
3597 int lines = prev->lineno - prev->offset - prev->height + 1;
3599 /* Scroll the view that was split if the current line is
3600 * outside the new limited view. */
3601 do_scroll_view(prev, lines);
3602 }
3604 if (prev && view != prev && split && view_is_displayed(prev)) {
3605 /* "Blur" the previous view. */
3606 update_view_title(prev);
3607 }
3609 if (view->pipe && view->lines == 0) {
3610 /* Clear the old view and let the incremental updating refill
3611 * the screen. */
3612 werase(view->win);
3613 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3614 report("");
3615 } else if (view_is_displayed(view)) {
3616 redraw_view(view);
3617 report("");
3618 }
3619 }
3621 static void
3622 open_external_viewer(const char *argv[], const char *dir)
3623 {
3624 def_prog_mode(); /* save current tty modes */
3625 endwin(); /* restore original tty modes */
3626 io_run_fg(argv, dir);
3627 fprintf(stderr, "Press Enter to continue");
3628 getc(opt_tty);
3629 reset_prog_mode();
3630 redraw_display(TRUE);
3631 }
3633 static void
3634 open_mergetool(const char *file)
3635 {
3636 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3638 open_external_viewer(mergetool_argv, opt_cdup);
3639 }
3641 static void
3642 open_editor(const char *file)
3643 {
3644 const char *editor_argv[] = { "vi", file, NULL };
3645 const char *editor;
3647 editor = getenv("GIT_EDITOR");
3648 if (!editor && *opt_editor)
3649 editor = opt_editor;
3650 if (!editor)
3651 editor = getenv("VISUAL");
3652 if (!editor)
3653 editor = getenv("EDITOR");
3654 if (!editor)
3655 editor = "vi";
3657 editor_argv[0] = editor;
3658 open_external_viewer(editor_argv, opt_cdup);
3659 }
3661 static void
3662 open_run_request(enum request request)
3663 {
3664 struct run_request *req = get_run_request(request);
3665 const char **argv = NULL;
3667 if (!req) {
3668 report("Unknown run request");
3669 return;
3670 }
3672 if (format_argv(&argv, req->argv, TRUE, FALSE))
3673 open_external_viewer(argv, NULL);
3674 if (argv)
3675 argv_free(argv);
3676 free(argv);
3677 }
3679 /*
3680 * User request switch noodle
3681 */
3683 static int
3684 view_driver(struct view *view, enum request request)
3685 {
3686 int i;
3688 if (request == REQ_NONE)
3689 return TRUE;
3691 if (request > REQ_NONE) {
3692 open_run_request(request);
3693 view_request(view, REQ_REFRESH);
3694 return TRUE;
3695 }
3697 request = view_request(view, request);
3698 if (request == REQ_NONE)
3699 return TRUE;
3701 switch (request) {
3702 case REQ_MOVE_UP:
3703 case REQ_MOVE_DOWN:
3704 case REQ_MOVE_PAGE_UP:
3705 case REQ_MOVE_PAGE_DOWN:
3706 case REQ_MOVE_FIRST_LINE:
3707 case REQ_MOVE_LAST_LINE:
3708 move_view(view, request);
3709 break;
3711 case REQ_SCROLL_FIRST_COL:
3712 case REQ_SCROLL_LEFT:
3713 case REQ_SCROLL_RIGHT:
3714 case REQ_SCROLL_LINE_DOWN:
3715 case REQ_SCROLL_LINE_UP:
3716 case REQ_SCROLL_PAGE_DOWN:
3717 case REQ_SCROLL_PAGE_UP:
3718 scroll_view(view, request);
3719 break;
3721 case REQ_VIEW_BLAME:
3722 if (!opt_file[0]) {
3723 report("No file chosen, press %s to open tree view",
3724 get_key(view->keymap, REQ_VIEW_TREE));
3725 break;
3726 }
3727 open_view(view, request, OPEN_DEFAULT);
3728 break;
3730 case REQ_VIEW_BLOB:
3731 if (!ref_blob[0]) {
3732 report("No file chosen, press %s to open tree view",
3733 get_key(view->keymap, REQ_VIEW_TREE));
3734 break;
3735 }
3736 open_view(view, request, OPEN_DEFAULT);
3737 break;
3739 case REQ_VIEW_PAGER:
3740 if (view == NULL) {
3741 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3742 die("Failed to open stdin");
3743 open_view(view, request, OPEN_PREPARED);
3744 break;
3745 }
3747 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3748 report("No pager content, press %s to run command from prompt",
3749 get_key(view->keymap, REQ_PROMPT));
3750 break;
3751 }
3752 open_view(view, request, OPEN_DEFAULT);
3753 break;
3755 case REQ_VIEW_STAGE:
3756 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3757 report("No stage content, press %s to open the status view and choose file",
3758 get_key(view->keymap, REQ_VIEW_STATUS));
3759 break;
3760 }
3761 open_view(view, request, OPEN_DEFAULT);
3762 break;
3764 case REQ_VIEW_STATUS:
3765 if (opt_is_inside_work_tree == FALSE) {
3766 report("The status view requires a working tree");
3767 break;
3768 }
3769 open_view(view, request, OPEN_DEFAULT);
3770 break;
3772 case REQ_VIEW_MAIN:
3773 case REQ_VIEW_DIFF:
3774 case REQ_VIEW_LOG:
3775 case REQ_VIEW_TREE:
3776 case REQ_VIEW_HELP:
3777 case REQ_VIEW_BRANCH:
3778 open_view(view, request, OPEN_DEFAULT);
3779 break;
3781 case REQ_NEXT:
3782 case REQ_PREVIOUS:
3783 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3785 if (view->parent) {
3786 int line;
3788 view = view->parent;
3789 line = view->lineno;
3790 move_view(view, request);
3791 if (view_is_displayed(view))
3792 update_view_title(view);
3793 if (line != view->lineno)
3794 view_request(view, REQ_ENTER);
3795 } else {
3796 move_view(view, request);
3797 }
3798 break;
3800 case REQ_VIEW_NEXT:
3801 {
3802 int nviews = displayed_views();
3803 int next_view = (current_view + 1) % nviews;
3805 if (next_view == current_view) {
3806 report("Only one view is displayed");
3807 break;
3808 }
3810 current_view = next_view;
3811 /* Blur out the title of the previous view. */
3812 update_view_title(view);
3813 report("");
3814 break;
3815 }
3816 case REQ_REFRESH:
3817 report("Refreshing is not yet supported for the %s view", view->name);
3818 break;
3820 case REQ_MAXIMIZE:
3821 if (displayed_views() == 2)
3822 maximize_view(view);
3823 break;
3825 case REQ_OPTIONS:
3826 open_option_menu();
3827 break;
3829 case REQ_TOGGLE_LINENO:
3830 toggle_view_option(&opt_line_number, "line numbers");
3831 break;
3833 case REQ_TOGGLE_DATE:
3834 toggle_date();
3835 break;
3837 case REQ_TOGGLE_AUTHOR:
3838 toggle_author();
3839 break;
3841 case REQ_TOGGLE_REV_GRAPH:
3842 toggle_view_option(&opt_rev_graph, "revision graph display");
3843 break;
3845 case REQ_TOGGLE_REFS:
3846 toggle_view_option(&opt_show_refs, "reference display");
3847 break;
3849 case REQ_TOGGLE_SORT_FIELD:
3850 case REQ_TOGGLE_SORT_ORDER:
3851 report("Sorting is not yet supported for the %s view", view->name);
3852 break;
3854 case REQ_SEARCH:
3855 case REQ_SEARCH_BACK:
3856 search_view(view, request);
3857 break;
3859 case REQ_FIND_NEXT:
3860 case REQ_FIND_PREV:
3861 find_next(view, request);
3862 break;
3864 case REQ_STOP_LOADING:
3865 foreach_view(view, i) {
3866 if (view->pipe)
3867 report("Stopped loading the %s view", view->name),
3868 end_update(view, TRUE);
3869 }
3870 break;
3872 case REQ_SHOW_VERSION:
3873 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3874 return TRUE;
3876 case REQ_SCREEN_REDRAW:
3877 redraw_display(TRUE);
3878 break;
3880 case REQ_EDIT:
3881 report("Nothing to edit");
3882 break;
3884 case REQ_ENTER:
3885 report("Nothing to enter");
3886 break;
3888 case REQ_VIEW_CLOSE:
3889 /* XXX: Mark closed views by letting view->prev point to the
3890 * view itself. Parents to closed view should never be
3891 * followed. */
3892 if (view->prev && view->prev != view) {
3893 maximize_view(view->prev);
3894 view->prev = view;
3895 break;
3896 }
3897 /* Fall-through */
3898 case REQ_QUIT:
3899 return FALSE;
3901 default:
3902 report("Unknown key, press %s for help",
3903 get_key(view->keymap, REQ_VIEW_HELP));
3904 return TRUE;
3905 }
3907 return TRUE;
3908 }
3911 /*
3912 * View backend utilities
3913 */
3915 enum sort_field {
3916 ORDERBY_NAME,
3917 ORDERBY_DATE,
3918 ORDERBY_AUTHOR,
3919 };
3921 struct sort_state {
3922 const enum sort_field *fields;
3923 size_t size, current;
3924 bool reverse;
3925 };
3927 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3928 #define get_sort_field(state) ((state).fields[(state).current])
3929 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3931 static void
3932 sort_view(struct view *view, enum request request, struct sort_state *state,
3933 int (*compare)(const void *, const void *))
3934 {
3935 switch (request) {
3936 case REQ_TOGGLE_SORT_FIELD:
3937 state->current = (state->current + 1) % state->size;
3938 break;
3940 case REQ_TOGGLE_SORT_ORDER:
3941 state->reverse = !state->reverse;
3942 break;
3943 default:
3944 die("Not a sort request");
3945 }
3947 qsort(view->line, view->lines, sizeof(*view->line), compare);
3948 redraw_view(view);
3949 }
3951 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3953 /* Small author cache to reduce memory consumption. It uses binary
3954 * search to lookup or find place to position new entries. No entries
3955 * are ever freed. */
3956 static const char *
3957 get_author(const char *name)
3958 {
3959 static const char **authors;
3960 static size_t authors_size;
3961 int from = 0, to = authors_size - 1;
3963 while (from <= to) {
3964 size_t pos = (to + from) / 2;
3965 int cmp = strcmp(name, authors[pos]);
3967 if (!cmp)
3968 return authors[pos];
3970 if (cmp < 0)
3971 to = pos - 1;
3972 else
3973 from = pos + 1;
3974 }
3976 if (!realloc_authors(&authors, authors_size, 1))
3977 return NULL;
3978 name = strdup(name);
3979 if (!name)
3980 return NULL;
3982 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3983 authors[from] = name;
3984 authors_size++;
3986 return name;
3987 }
3989 static void
3990 parse_timesec(struct time *time, const char *sec)
3991 {
3992 time->sec = (time_t) atol(sec);
3993 }
3995 static void
3996 parse_timezone(struct time *time, const char *zone)
3997 {
3998 long tz;
4000 tz = ('0' - zone[1]) * 60 * 60 * 10;
4001 tz += ('0' - zone[2]) * 60 * 60;
4002 tz += ('0' - zone[3]) * 60 * 10;
4003 tz += ('0' - zone[4]) * 60;
4005 if (zone[0] == '-')
4006 tz = -tz;
4008 time->tz = tz;
4009 time->sec -= tz;
4010 }
4012 /* Parse author lines where the name may be empty:
4013 * author <email@address.tld> 1138474660 +0100
4014 */
4015 static void
4016 parse_author_line(char *ident, const char **author, struct time *time)
4017 {
4018 char *nameend = strchr(ident, '<');
4019 char *emailend = strchr(ident, '>');
4021 if (nameend && emailend)
4022 *nameend = *emailend = 0;
4023 ident = chomp_string(ident);
4024 if (!*ident) {
4025 if (nameend)
4026 ident = chomp_string(nameend + 1);
4027 if (!*ident)
4028 ident = "Unknown";
4029 }
4031 *author = get_author(ident);
4033 /* Parse epoch and timezone */
4034 if (emailend && emailend[1] == ' ') {
4035 char *secs = emailend + 2;
4036 char *zone = strchr(secs, ' ');
4038 parse_timesec(time, secs);
4040 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4041 parse_timezone(time, zone + 1);
4042 }
4043 }
4045 /*
4046 * Pager backend
4047 */
4049 static bool
4050 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4051 {
4052 if (opt_line_number && draw_lineno(view, lineno))
4053 return TRUE;
4055 draw_text(view, line->type, line->data, TRUE);
4056 return TRUE;
4057 }
4059 static bool
4060 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4061 {
4062 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4063 char ref[SIZEOF_STR];
4065 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4066 return TRUE;
4068 /* This is the only fatal call, since it can "corrupt" the buffer. */
4069 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4070 return FALSE;
4072 return TRUE;
4073 }
4075 static void
4076 add_pager_refs(struct view *view, struct line *line)
4077 {
4078 char buf[SIZEOF_STR];
4079 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4080 struct ref_list *list;
4081 size_t bufpos = 0, i;
4082 const char *sep = "Refs: ";
4083 bool is_tag = FALSE;
4085 assert(line->type == LINE_COMMIT);
4087 list = get_ref_list(commit_id);
4088 if (!list) {
4089 if (view->type == VIEW_DIFF)
4090 goto try_add_describe_ref;
4091 return;
4092 }
4094 for (i = 0; i < list->size; i++) {
4095 struct ref *ref = list->refs[i];
4096 const char *fmt = ref->tag ? "%s[%s]" :
4097 ref->remote ? "%s<%s>" : "%s%s";
4099 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4100 return;
4101 sep = ", ";
4102 if (ref->tag)
4103 is_tag = TRUE;
4104 }
4106 if (!is_tag && view->type == VIEW_DIFF) {
4107 try_add_describe_ref:
4108 /* Add <tag>-g<commit_id> "fake" reference. */
4109 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4110 return;
4111 }
4113 if (bufpos == 0)
4114 return;
4116 add_line_text(view, buf, LINE_PP_REFS);
4117 }
4119 static bool
4120 pager_read(struct view *view, char *data)
4121 {
4122 struct line *line;
4124 if (!data)
4125 return TRUE;
4127 line = add_line_text(view, data, get_line_type(data));
4128 if (!line)
4129 return FALSE;
4131 if (line->type == LINE_COMMIT &&
4132 (view->type == VIEW_DIFF ||
4133 view->type == VIEW_LOG))
4134 add_pager_refs(view, line);
4136 return TRUE;
4137 }
4139 static enum request
4140 pager_request(struct view *view, enum request request, struct line *line)
4141 {
4142 int split = 0;
4144 if (request != REQ_ENTER)
4145 return request;
4147 if (line->type == LINE_COMMIT &&
4148 (view->type == VIEW_LOG ||
4149 view->type == VIEW_PAGER)) {
4150 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4151 split = 1;
4152 }
4154 /* Always scroll the view even if it was split. That way
4155 * you can use Enter to scroll through the log view and
4156 * split open each commit diff. */
4157 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4159 /* FIXME: A minor workaround. Scrolling the view will call report("")
4160 * but if we are scrolling a non-current view this won't properly
4161 * update the view title. */
4162 if (split)
4163 update_view_title(view);
4165 return REQ_NONE;
4166 }
4168 static bool
4169 pager_grep(struct view *view, struct line *line)
4170 {
4171 const char *text[] = { line->data, NULL };
4173 return grep_text(view, text);
4174 }
4176 static void
4177 pager_select(struct view *view, struct line *line)
4178 {
4179 if (line->type == LINE_COMMIT) {
4180 char *text = (char *)line->data + STRING_SIZE("commit ");
4182 if (view->type != VIEW_PAGER)
4183 string_copy_rev(view->ref, text);
4184 string_copy_rev(ref_commit, text);
4185 }
4186 }
4188 static struct view_ops pager_ops = {
4189 "line",
4190 NULL,
4191 NULL,
4192 pager_read,
4193 pager_draw,
4194 pager_request,
4195 pager_grep,
4196 pager_select,
4197 };
4199 static const char *log_argv[SIZEOF_ARG] = {
4200 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4201 };
4203 static enum request
4204 log_request(struct view *view, enum request request, struct line *line)
4205 {
4206 switch (request) {
4207 case REQ_REFRESH:
4208 load_refs();
4209 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4210 return REQ_NONE;
4211 default:
4212 return pager_request(view, request, line);
4213 }
4214 }
4216 static struct view_ops log_ops = {
4217 "line",
4218 log_argv,
4219 NULL,
4220 pager_read,
4221 pager_draw,
4222 log_request,
4223 pager_grep,
4224 pager_select,
4225 };
4227 static const char *diff_argv[SIZEOF_ARG] = {
4228 "git", "show", "--pretty=fuller", "--no-color", "--root",
4229 "--patch-with-stat", "--find-copies-harder", "-C",
4230 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4231 };
4233 static bool
4234 diff_read(struct view *view, char *data)
4235 {
4236 if (!data) {
4237 /* Fall back to retry if no diff will be shown. */
4238 if (view->lines == 0 && opt_file_args) {
4239 int pos = argv_size(view->argv)
4240 - argv_size(opt_file_args) - 1;
4242 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4243 for (; view->argv[pos]; pos++) {
4244 free((void *) view->argv[pos]);
4245 view->argv[pos] = NULL;
4246 }
4248 if (view->pipe)
4249 io_done(view->pipe);
4250 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4251 return FALSE;
4252 }
4253 }
4254 return TRUE;
4255 }
4257 return pager_read(view, data);
4258 }
4260 static struct view_ops diff_ops = {
4261 "line",
4262 diff_argv,
4263 NULL,
4264 diff_read,
4265 pager_draw,
4266 pager_request,
4267 pager_grep,
4268 pager_select,
4269 };
4271 /*
4272 * Help backend
4273 */
4275 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4277 static bool
4278 help_open_keymap_title(struct view *view, enum keymap keymap)
4279 {
4280 struct line *line;
4282 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4283 help_keymap_hidden[keymap] ? '+' : '-',
4284 enum_name(keymap_table[keymap]));
4285 if (line)
4286 line->other = keymap;
4288 return help_keymap_hidden[keymap];
4289 }
4291 static void
4292 help_open_keymap(struct view *view, enum keymap keymap)
4293 {
4294 const char *group = NULL;
4295 char buf[SIZEOF_STR];
4296 size_t bufpos;
4297 bool add_title = TRUE;
4298 int i;
4300 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4301 const char *key = NULL;
4303 if (req_info[i].request == REQ_NONE)
4304 continue;
4306 if (!req_info[i].request) {
4307 group = req_info[i].help;
4308 continue;
4309 }
4311 key = get_keys(keymap, req_info[i].request, TRUE);
4312 if (!key || !*key)
4313 continue;
4315 if (add_title && help_open_keymap_title(view, keymap))
4316 return;
4317 add_title = FALSE;
4319 if (group) {
4320 add_line_text(view, group, LINE_HELP_GROUP);
4321 group = NULL;
4322 }
4324 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4325 enum_name(req_info[i]), req_info[i].help);
4326 }
4328 group = "External commands:";
4330 for (i = 0; i < run_requests; i++) {
4331 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4332 const char *key;
4333 int argc;
4335 if (!req || req->keymap != keymap)
4336 continue;
4338 key = get_key_name(req->key);
4339 if (!*key)
4340 key = "(no key defined)";
4342 if (add_title && help_open_keymap_title(view, keymap))
4343 return;
4344 if (group) {
4345 add_line_text(view, group, LINE_HELP_GROUP);
4346 group = NULL;
4347 }
4349 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4350 if (!string_format_from(buf, &bufpos, "%s%s",
4351 argc ? " " : "", req->argv[argc]))
4352 return;
4354 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4355 }
4356 }
4358 static bool
4359 help_open(struct view *view)
4360 {
4361 enum keymap keymap;
4363 reset_view(view);
4364 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4365 add_line_text(view, "", LINE_DEFAULT);
4367 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4368 help_open_keymap(view, keymap);
4370 return TRUE;
4371 }
4373 static enum request
4374 help_request(struct view *view, enum request request, struct line *line)
4375 {
4376 switch (request) {
4377 case REQ_ENTER:
4378 if (line->type == LINE_HELP_KEYMAP) {
4379 help_keymap_hidden[line->other] =
4380 !help_keymap_hidden[line->other];
4381 view->p_restore = TRUE;
4382 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4383 }
4385 return REQ_NONE;
4386 default:
4387 return pager_request(view, request, line);
4388 }
4389 }
4391 static struct view_ops help_ops = {
4392 "line",
4393 NULL,
4394 help_open,
4395 NULL,
4396 pager_draw,
4397 help_request,
4398 pager_grep,
4399 pager_select,
4400 };
4403 /*
4404 * Tree backend
4405 */
4407 struct tree_stack_entry {
4408 struct tree_stack_entry *prev; /* Entry below this in the stack */
4409 unsigned long lineno; /* Line number to restore */
4410 char *name; /* Position of name in opt_path */
4411 };
4413 /* The top of the path stack. */
4414 static struct tree_stack_entry *tree_stack = NULL;
4415 unsigned long tree_lineno = 0;
4417 static void
4418 pop_tree_stack_entry(void)
4419 {
4420 struct tree_stack_entry *entry = tree_stack;
4422 tree_lineno = entry->lineno;
4423 entry->name[0] = 0;
4424 tree_stack = entry->prev;
4425 free(entry);
4426 }
4428 static void
4429 push_tree_stack_entry(const char *name, unsigned long lineno)
4430 {
4431 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4432 size_t pathlen = strlen(opt_path);
4434 if (!entry)
4435 return;
4437 entry->prev = tree_stack;
4438 entry->name = opt_path + pathlen;
4439 tree_stack = entry;
4441 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4442 pop_tree_stack_entry();
4443 return;
4444 }
4446 /* Move the current line to the first tree entry. */
4447 tree_lineno = 1;
4448 entry->lineno = lineno;
4449 }
4451 /* Parse output from git-ls-tree(1):
4452 *
4453 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4454 */
4456 #define SIZEOF_TREE_ATTR \
4457 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4459 #define SIZEOF_TREE_MODE \
4460 STRING_SIZE("100644 ")
4462 #define TREE_ID_OFFSET \
4463 STRING_SIZE("100644 blob ")
4465 struct tree_entry {
4466 char id[SIZEOF_REV];
4467 mode_t mode;
4468 struct time time; /* Date from the author ident. */
4469 const char *author; /* Author of the commit. */
4470 char name[1];
4471 };
4473 static const char *
4474 tree_path(const struct line *line)
4475 {
4476 return ((struct tree_entry *) line->data)->name;
4477 }
4479 static int
4480 tree_compare_entry(const struct line *line1, const struct line *line2)
4481 {
4482 if (line1->type != line2->type)
4483 return line1->type == LINE_TREE_DIR ? -1 : 1;
4484 return strcmp(tree_path(line1), tree_path(line2));
4485 }
4487 static const enum sort_field tree_sort_fields[] = {
4488 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4489 };
4490 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4492 static int
4493 tree_compare(const void *l1, const void *l2)
4494 {
4495 const struct line *line1 = (const struct line *) l1;
4496 const struct line *line2 = (const struct line *) l2;
4497 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4498 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4500 if (line1->type == LINE_TREE_HEAD)
4501 return -1;
4502 if (line2->type == LINE_TREE_HEAD)
4503 return 1;
4505 switch (get_sort_field(tree_sort_state)) {
4506 case ORDERBY_DATE:
4507 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4509 case ORDERBY_AUTHOR:
4510 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4512 case ORDERBY_NAME:
4513 default:
4514 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4515 }
4516 }
4519 static struct line *
4520 tree_entry(struct view *view, enum line_type type, const char *path,
4521 const char *mode, const char *id)
4522 {
4523 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4524 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4526 if (!entry || !line) {
4527 free(entry);
4528 return NULL;
4529 }
4531 strncpy(entry->name, path, strlen(path));
4532 if (mode)
4533 entry->mode = strtoul(mode, NULL, 8);
4534 if (id)
4535 string_copy_rev(entry->id, id);
4537 return line;
4538 }
4540 static bool
4541 tree_read_date(struct view *view, char *text, bool *read_date)
4542 {
4543 static const char *author_name;
4544 static struct time author_time;
4546 if (!text && *read_date) {
4547 *read_date = FALSE;
4548 return TRUE;
4550 } else if (!text) {
4551 char *path = *opt_path ? opt_path : ".";
4552 /* Find next entry to process */
4553 const char *log_file[] = {
4554 "git", "log", "--no-color", "--pretty=raw",
4555 "--cc", "--raw", view->id, "--", path, NULL
4556 };
4558 if (!view->lines) {
4559 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4560 report("Tree is empty");
4561 return TRUE;
4562 }
4564 if (!start_update(view, log_file, opt_cdup)) {
4565 report("Failed to load tree data");
4566 return TRUE;
4567 }
4569 *read_date = TRUE;
4570 return FALSE;
4572 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4573 parse_author_line(text + STRING_SIZE("author "),
4574 &author_name, &author_time);
4576 } else if (*text == ':') {
4577 char *pos;
4578 size_t annotated = 1;
4579 size_t i;
4581 pos = strchr(text, '\t');
4582 if (!pos)
4583 return TRUE;
4584 text = pos + 1;
4585 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4586 text += strlen(opt_path);
4587 pos = strchr(text, '/');
4588 if (pos)
4589 *pos = 0;
4591 for (i = 1; i < view->lines; i++) {
4592 struct line *line = &view->line[i];
4593 struct tree_entry *entry = line->data;
4595 annotated += !!entry->author;
4596 if (entry->author || strcmp(entry->name, text))
4597 continue;
4599 entry->author = author_name;
4600 entry->time = author_time;
4601 line->dirty = 1;
4602 break;
4603 }
4605 if (annotated == view->lines)
4606 io_kill(view->pipe);
4607 }
4608 return TRUE;
4609 }
4611 static bool
4612 tree_read(struct view *view, char *text)
4613 {
4614 static bool read_date = FALSE;
4615 struct tree_entry *data;
4616 struct line *entry, *line;
4617 enum line_type type;
4618 size_t textlen = text ? strlen(text) : 0;
4619 char *path = text + SIZEOF_TREE_ATTR;
4621 if (read_date || !text)
4622 return tree_read_date(view, text, &read_date);
4624 if (textlen <= SIZEOF_TREE_ATTR)
4625 return FALSE;
4626 if (view->lines == 0 &&
4627 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4628 return FALSE;
4630 /* Strip the path part ... */
4631 if (*opt_path) {
4632 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4633 size_t striplen = strlen(opt_path);
4635 if (pathlen > striplen)
4636 memmove(path, path + striplen,
4637 pathlen - striplen + 1);
4639 /* Insert "link" to parent directory. */
4640 if (view->lines == 1 &&
4641 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4642 return FALSE;
4643 }
4645 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4646 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4647 if (!entry)
4648 return FALSE;
4649 data = entry->data;
4651 /* Skip "Directory ..." and ".." line. */
4652 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4653 if (tree_compare_entry(line, entry) <= 0)
4654 continue;
4656 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4658 line->data = data;
4659 line->type = type;
4660 for (; line <= entry; line++)
4661 line->dirty = line->cleareol = 1;
4662 return TRUE;
4663 }
4665 if (tree_lineno > view->lineno) {
4666 view->lineno = tree_lineno;
4667 tree_lineno = 0;
4668 }
4670 return TRUE;
4671 }
4673 static bool
4674 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4675 {
4676 struct tree_entry *entry = line->data;
4678 if (line->type == LINE_TREE_HEAD) {
4679 if (draw_text(view, line->type, "Directory path /", TRUE))
4680 return TRUE;
4681 } else {
4682 if (draw_mode(view, entry->mode))
4683 return TRUE;
4685 if (opt_author && draw_author(view, entry->author))
4686 return TRUE;
4688 if (opt_date && draw_date(view, &entry->time))
4689 return TRUE;
4690 }
4691 if (draw_text(view, line->type, entry->name, TRUE))
4692 return TRUE;
4693 return TRUE;
4694 }
4696 static void
4697 open_blob_editor(const char *id)
4698 {
4699 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4700 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4701 int fd = mkstemp(file);
4703 if (fd == -1)
4704 report("Failed to create temporary file");
4705 else if (!io_run_append(blob_argv, fd))
4706 report("Failed to save blob data to file");
4707 else
4708 open_editor(file);
4709 if (fd != -1)
4710 unlink(file);
4711 }
4713 static enum request
4714 tree_request(struct view *view, enum request request, struct line *line)
4715 {
4716 enum open_flags flags;
4717 struct tree_entry *entry = line->data;
4719 switch (request) {
4720 case REQ_VIEW_BLAME:
4721 if (line->type != LINE_TREE_FILE) {
4722 report("Blame only supported for files");
4723 return REQ_NONE;
4724 }
4726 string_copy(opt_ref, view->vid);
4727 return request;
4729 case REQ_EDIT:
4730 if (line->type != LINE_TREE_FILE) {
4731 report("Edit only supported for files");
4732 } else if (!is_head_commit(view->vid)) {
4733 open_blob_editor(entry->id);
4734 } else {
4735 open_editor(opt_file);
4736 }
4737 return REQ_NONE;
4739 case REQ_TOGGLE_SORT_FIELD:
4740 case REQ_TOGGLE_SORT_ORDER:
4741 sort_view(view, request, &tree_sort_state, tree_compare);
4742 return REQ_NONE;
4744 case REQ_PARENT:
4745 if (!*opt_path) {
4746 /* quit view if at top of tree */
4747 return REQ_VIEW_CLOSE;
4748 }
4749 /* fake 'cd ..' */
4750 line = &view->line[1];
4751 break;
4753 case REQ_ENTER:
4754 break;
4756 default:
4757 return request;
4758 }
4760 /* Cleanup the stack if the tree view is at a different tree. */
4761 while (!*opt_path && tree_stack)
4762 pop_tree_stack_entry();
4764 switch (line->type) {
4765 case LINE_TREE_DIR:
4766 /* Depending on whether it is a subdirectory or parent link
4767 * mangle the path buffer. */
4768 if (line == &view->line[1] && *opt_path) {
4769 pop_tree_stack_entry();
4771 } else {
4772 const char *basename = tree_path(line);
4774 push_tree_stack_entry(basename, view->lineno);
4775 }
4777 /* Trees and subtrees share the same ID, so they are not not
4778 * unique like blobs. */
4779 flags = OPEN_RELOAD;
4780 request = REQ_VIEW_TREE;
4781 break;
4783 case LINE_TREE_FILE:
4784 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4785 request = REQ_VIEW_BLOB;
4786 break;
4788 default:
4789 return REQ_NONE;
4790 }
4792 open_view(view, request, flags);
4793 if (request == REQ_VIEW_TREE)
4794 view->lineno = tree_lineno;
4796 return REQ_NONE;
4797 }
4799 static bool
4800 tree_grep(struct view *view, struct line *line)
4801 {
4802 struct tree_entry *entry = line->data;
4803 const char *text[] = {
4804 entry->name,
4805 opt_author ? entry->author : "",
4806 mkdate(&entry->time, opt_date),
4807 NULL
4808 };
4810 return grep_text(view, text);
4811 }
4813 static void
4814 tree_select(struct view *view, struct line *line)
4815 {
4816 struct tree_entry *entry = line->data;
4818 if (line->type == LINE_TREE_FILE) {
4819 string_copy_rev(ref_blob, entry->id);
4820 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4822 } else if (line->type != LINE_TREE_DIR) {
4823 return;
4824 }
4826 string_copy_rev(view->ref, entry->id);
4827 }
4829 static bool
4830 tree_prepare(struct view *view)
4831 {
4832 if (view->lines == 0 && opt_prefix[0]) {
4833 char *pos = opt_prefix;
4835 while (pos && *pos) {
4836 char *end = strchr(pos, '/');
4838 if (end)
4839 *end = 0;
4840 push_tree_stack_entry(pos, 0);
4841 pos = end;
4842 if (end) {
4843 *end = '/';
4844 pos++;
4845 }
4846 }
4848 } else if (strcmp(view->vid, view->id)) {
4849 opt_path[0] = 0;
4850 }
4852 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4853 }
4855 static const char *tree_argv[SIZEOF_ARG] = {
4856 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4857 };
4859 static struct view_ops tree_ops = {
4860 "file",
4861 tree_argv,
4862 NULL,
4863 tree_read,
4864 tree_draw,
4865 tree_request,
4866 tree_grep,
4867 tree_select,
4868 tree_prepare,
4869 };
4871 static bool
4872 blob_read(struct view *view, char *line)
4873 {
4874 if (!line)
4875 return TRUE;
4876 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4877 }
4879 static enum request
4880 blob_request(struct view *view, enum request request, struct line *line)
4881 {
4882 switch (request) {
4883 case REQ_EDIT:
4884 open_blob_editor(view->vid);
4885 return REQ_NONE;
4886 default:
4887 return pager_request(view, request, line);
4888 }
4889 }
4891 static const char *blob_argv[SIZEOF_ARG] = {
4892 "git", "cat-file", "blob", "%(blob)", NULL
4893 };
4895 static struct view_ops blob_ops = {
4896 "line",
4897 blob_argv,
4898 NULL,
4899 blob_read,
4900 pager_draw,
4901 blob_request,
4902 pager_grep,
4903 pager_select,
4904 };
4906 /*
4907 * Blame backend
4908 *
4909 * Loading the blame view is a two phase job:
4910 *
4911 * 1. File content is read either using opt_file from the
4912 * filesystem or using git-cat-file.
4913 * 2. Then blame information is incrementally added by
4914 * reading output from git-blame.
4915 */
4917 struct blame_commit {
4918 char id[SIZEOF_REV]; /* SHA1 ID. */
4919 char title[128]; /* First line of the commit message. */
4920 const char *author; /* Author of the commit. */
4921 struct time time; /* Date from the author ident. */
4922 char filename[128]; /* Name of file. */
4923 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4924 char parent_filename[128]; /* Parent/previous name of file. */
4925 };
4927 struct blame {
4928 struct blame_commit *commit;
4929 unsigned long lineno;
4930 char text[1];
4931 };
4933 static bool
4934 blame_open(struct view *view)
4935 {
4936 char path[SIZEOF_STR];
4937 size_t i;
4939 if (!view->prev && *opt_prefix) {
4940 string_copy(path, opt_file);
4941 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4942 return FALSE;
4943 }
4945 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4946 const char *blame_cat_file_argv[] = {
4947 "git", "cat-file", "blob", path, NULL
4948 };
4950 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4951 !start_update(view, blame_cat_file_argv, opt_cdup))
4952 return FALSE;
4953 }
4955 /* First pass: remove multiple references to the same commit. */
4956 for (i = 0; i < view->lines; i++) {
4957 struct blame *blame = view->line[i].data;
4959 if (blame->commit && blame->commit->id[0])
4960 blame->commit->id[0] = 0;
4961 else
4962 blame->commit = NULL;
4963 }
4965 /* Second pass: free existing references. */
4966 for (i = 0; i < view->lines; i++) {
4967 struct blame *blame = view->line[i].data;
4969 if (blame->commit)
4970 free(blame->commit);
4971 }
4973 setup_update(view, opt_file);
4974 string_format(view->ref, "%s ...", opt_file);
4976 return TRUE;
4977 }
4979 static struct blame_commit *
4980 get_blame_commit(struct view *view, const char *id)
4981 {
4982 size_t i;
4984 for (i = 0; i < view->lines; i++) {
4985 struct blame *blame = view->line[i].data;
4987 if (!blame->commit)
4988 continue;
4990 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4991 return blame->commit;
4992 }
4994 {
4995 struct blame_commit *commit = calloc(1, sizeof(*commit));
4997 if (commit)
4998 string_ncopy(commit->id, id, SIZEOF_REV);
4999 return commit;
5000 }
5001 }
5003 static bool
5004 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5005 {
5006 const char *pos = *posref;
5008 *posref = NULL;
5009 pos = strchr(pos + 1, ' ');
5010 if (!pos || !isdigit(pos[1]))
5011 return FALSE;
5012 *number = atoi(pos + 1);
5013 if (*number < min || *number > max)
5014 return FALSE;
5016 *posref = pos;
5017 return TRUE;
5018 }
5020 static struct blame_commit *
5021 parse_blame_commit(struct view *view, const char *text, int *blamed)
5022 {
5023 struct blame_commit *commit;
5024 struct blame *blame;
5025 const char *pos = text + SIZEOF_REV - 2;
5026 size_t orig_lineno = 0;
5027 size_t lineno;
5028 size_t group;
5030 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5031 return NULL;
5033 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5034 !parse_number(&pos, &lineno, 1, view->lines) ||
5035 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5036 return NULL;
5038 commit = get_blame_commit(view, text);
5039 if (!commit)
5040 return NULL;
5042 *blamed += group;
5043 while (group--) {
5044 struct line *line = &view->line[lineno + group - 1];
5046 blame = line->data;
5047 blame->commit = commit;
5048 blame->lineno = orig_lineno + group - 1;
5049 line->dirty = 1;
5050 }
5052 return commit;
5053 }
5055 static bool
5056 blame_read_file(struct view *view, const char *line, bool *read_file)
5057 {
5058 if (!line) {
5059 const char *blame_argv[] = {
5060 "git", "blame", "--incremental",
5061 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5062 };
5064 if (view->lines == 0 && !view->prev)
5065 die("No blame exist for %s", view->vid);
5067 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5068 report("Failed to load blame data");
5069 return TRUE;
5070 }
5072 *read_file = FALSE;
5073 return FALSE;
5075 } else {
5076 size_t linelen = strlen(line);
5077 struct blame *blame = malloc(sizeof(*blame) + linelen);
5079 if (!blame)
5080 return FALSE;
5082 blame->commit = NULL;
5083 strncpy(blame->text, line, linelen);
5084 blame->text[linelen] = 0;
5085 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5086 }
5087 }
5089 static bool
5090 match_blame_header(const char *name, char **line)
5091 {
5092 size_t namelen = strlen(name);
5093 bool matched = !strncmp(name, *line, namelen);
5095 if (matched)
5096 *line += namelen;
5098 return matched;
5099 }
5101 static bool
5102 blame_read(struct view *view, char *line)
5103 {
5104 static struct blame_commit *commit = NULL;
5105 static int blamed = 0;
5106 static bool read_file = TRUE;
5108 if (read_file)
5109 return blame_read_file(view, line, &read_file);
5111 if (!line) {
5112 /* Reset all! */
5113 commit = NULL;
5114 blamed = 0;
5115 read_file = TRUE;
5116 string_format(view->ref, "%s", view->vid);
5117 if (view_is_displayed(view)) {
5118 update_view_title(view);
5119 redraw_view_from(view, 0);
5120 }
5121 return TRUE;
5122 }
5124 if (!commit) {
5125 commit = parse_blame_commit(view, line, &blamed);
5126 string_format(view->ref, "%s %2d%%", view->vid,
5127 view->lines ? blamed * 100 / view->lines : 0);
5129 } else if (match_blame_header("author ", &line)) {
5130 commit->author = get_author(line);
5132 } else if (match_blame_header("author-time ", &line)) {
5133 parse_timesec(&commit->time, line);
5135 } else if (match_blame_header("author-tz ", &line)) {
5136 parse_timezone(&commit->time, line);
5138 } else if (match_blame_header("summary ", &line)) {
5139 string_ncopy(commit->title, line, strlen(line));
5141 } else if (match_blame_header("previous ", &line)) {
5142 if (strlen(line) <= SIZEOF_REV)
5143 return FALSE;
5144 string_copy_rev(commit->parent_id, line);
5145 line += SIZEOF_REV;
5146 string_ncopy(commit->parent_filename, line, strlen(line));
5148 } else if (match_blame_header("filename ", &line)) {
5149 string_ncopy(commit->filename, line, strlen(line));
5150 commit = NULL;
5151 }
5153 return TRUE;
5154 }
5156 static bool
5157 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5158 {
5159 struct blame *blame = line->data;
5160 struct time *time = NULL;
5161 const char *id = NULL, *author = NULL;
5163 if (blame->commit && *blame->commit->filename) {
5164 id = blame->commit->id;
5165 author = blame->commit->author;
5166 time = &blame->commit->time;
5167 }
5169 if (opt_date && draw_date(view, time))
5170 return TRUE;
5172 if (opt_author && draw_author(view, author))
5173 return TRUE;
5175 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5176 return TRUE;
5178 if (draw_lineno(view, lineno))
5179 return TRUE;
5181 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5182 return TRUE;
5183 }
5185 static bool
5186 check_blame_commit(struct blame *blame, bool check_null_id)
5187 {
5188 if (!blame->commit)
5189 report("Commit data not loaded yet");
5190 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5191 report("No commit exist for the selected line");
5192 else
5193 return TRUE;
5194 return FALSE;
5195 }
5197 static void
5198 setup_blame_parent_line(struct view *view, struct blame *blame)
5199 {
5200 char from[SIZEOF_REF + SIZEOF_STR];
5201 char to[SIZEOF_REF + SIZEOF_STR];
5202 const char *diff_tree_argv[] = {
5203 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5204 "-U0", from, to, "--", NULL
5205 };
5206 struct io io;
5207 int parent_lineno = -1;
5208 int blamed_lineno = -1;
5209 char *line;
5211 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5212 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5213 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5214 return;
5216 while ((line = io_get(&io, '\n', TRUE))) {
5217 if (*line == '@') {
5218 char *pos = strchr(line, '+');
5220 parent_lineno = atoi(line + 4);
5221 if (pos)
5222 blamed_lineno = atoi(pos + 1);
5224 } else if (*line == '+' && parent_lineno != -1) {
5225 if (blame->lineno == blamed_lineno - 1 &&
5226 !strcmp(blame->text, line + 1)) {
5227 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5228 break;
5229 }
5230 blamed_lineno++;
5231 }
5232 }
5234 io_done(&io);
5235 }
5237 static enum request
5238 blame_request(struct view *view, enum request request, struct line *line)
5239 {
5240 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5241 struct blame *blame = line->data;
5243 switch (request) {
5244 case REQ_VIEW_BLAME:
5245 if (check_blame_commit(blame, TRUE)) {
5246 string_copy(opt_ref, blame->commit->id);
5247 string_copy(opt_file, blame->commit->filename);
5248 if (blame->lineno)
5249 view->lineno = blame->lineno;
5250 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5251 }
5252 break;
5254 case REQ_PARENT:
5255 if (!check_blame_commit(blame, TRUE))
5256 break;
5257 if (!*blame->commit->parent_id) {
5258 report("The selected commit has no parents");
5259 } else {
5260 string_copy_rev(opt_ref, blame->commit->parent_id);
5261 string_copy(opt_file, blame->commit->parent_filename);
5262 setup_blame_parent_line(view, blame);
5263 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5264 }
5265 break;
5267 case REQ_ENTER:
5268 if (!check_blame_commit(blame, FALSE))
5269 break;
5271 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5272 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5273 break;
5275 if (!strcmp(blame->commit->id, NULL_ID)) {
5276 struct view *diff = VIEW(REQ_VIEW_DIFF);
5277 const char *diff_index_argv[] = {
5278 "git", "diff-index", "--root", "--patch-with-stat",
5279 "-C", "-M", "HEAD", "--", view->vid, NULL
5280 };
5282 if (!*blame->commit->parent_id) {
5283 diff_index_argv[1] = "diff";
5284 diff_index_argv[2] = "--no-color";
5285 diff_index_argv[6] = "--";
5286 diff_index_argv[7] = "/dev/null";
5287 }
5289 if (!prepare_update(diff, diff_index_argv, NULL)) {
5290 report("Failed to allocate diff command");
5291 break;
5292 }
5293 flags |= OPEN_PREPARED;
5294 }
5296 open_view(view, REQ_VIEW_DIFF, flags);
5297 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5298 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5299 break;
5301 default:
5302 return request;
5303 }
5305 return REQ_NONE;
5306 }
5308 static bool
5309 blame_grep(struct view *view, struct line *line)
5310 {
5311 struct blame *blame = line->data;
5312 struct blame_commit *commit = blame->commit;
5313 const char *text[] = {
5314 blame->text,
5315 commit ? commit->title : "",
5316 commit ? commit->id : "",
5317 commit && opt_author ? commit->author : "",
5318 commit ? mkdate(&commit->time, opt_date) : "",
5319 NULL
5320 };
5322 return grep_text(view, text);
5323 }
5325 static void
5326 blame_select(struct view *view, struct line *line)
5327 {
5328 struct blame *blame = line->data;
5329 struct blame_commit *commit = blame->commit;
5331 if (!commit)
5332 return;
5334 if (!strcmp(commit->id, NULL_ID))
5335 string_ncopy(ref_commit, "HEAD", 4);
5336 else
5337 string_copy_rev(ref_commit, commit->id);
5338 }
5340 static struct view_ops blame_ops = {
5341 "line",
5342 NULL,
5343 blame_open,
5344 blame_read,
5345 blame_draw,
5346 blame_request,
5347 blame_grep,
5348 blame_select,
5349 };
5351 /*
5352 * Branch backend
5353 */
5355 struct branch {
5356 const char *author; /* Author of the last commit. */
5357 struct time time; /* Date of the last activity. */
5358 const struct ref *ref; /* Name and commit ID information. */
5359 };
5361 static const struct ref branch_all;
5363 static const enum sort_field branch_sort_fields[] = {
5364 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5365 };
5366 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5368 static int
5369 branch_compare(const void *l1, const void *l2)
5370 {
5371 const struct branch *branch1 = ((const struct line *) l1)->data;
5372 const struct branch *branch2 = ((const struct line *) l2)->data;
5374 switch (get_sort_field(branch_sort_state)) {
5375 case ORDERBY_DATE:
5376 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5378 case ORDERBY_AUTHOR:
5379 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5381 case ORDERBY_NAME:
5382 default:
5383 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5384 }
5385 }
5387 static bool
5388 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5389 {
5390 struct branch *branch = line->data;
5391 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5393 if (opt_date && draw_date(view, &branch->time))
5394 return TRUE;
5396 if (opt_author && draw_author(view, branch->author))
5397 return TRUE;
5399 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5400 return TRUE;
5401 }
5403 static enum request
5404 branch_request(struct view *view, enum request request, struct line *line)
5405 {
5406 struct branch *branch = line->data;
5408 switch (request) {
5409 case REQ_REFRESH:
5410 load_refs();
5411 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5412 return REQ_NONE;
5414 case REQ_TOGGLE_SORT_FIELD:
5415 case REQ_TOGGLE_SORT_ORDER:
5416 sort_view(view, request, &branch_sort_state, branch_compare);
5417 return REQ_NONE;
5419 case REQ_ENTER:
5420 {
5421 const struct ref *ref = branch->ref;
5422 const char *all_branches_argv[] = {
5423 "git", "log", "--no-color", "--pretty=raw", "--parents",
5424 "--topo-order",
5425 ref == &branch_all ? "--all" : ref->name, NULL
5426 };
5427 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5429 if (!prepare_update(main_view, all_branches_argv, NULL))
5430 report("Failed to load view of all branches");
5431 else
5432 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5433 return REQ_NONE;
5434 }
5435 default:
5436 return request;
5437 }
5438 }
5440 static bool
5441 branch_read(struct view *view, char *line)
5442 {
5443 static char id[SIZEOF_REV];
5444 struct branch *reference;
5445 size_t i;
5447 if (!line)
5448 return TRUE;
5450 switch (get_line_type(line)) {
5451 case LINE_COMMIT:
5452 string_copy_rev(id, line + STRING_SIZE("commit "));
5453 return TRUE;
5455 case LINE_AUTHOR:
5456 for (i = 0, reference = NULL; i < view->lines; i++) {
5457 struct branch *branch = view->line[i].data;
5459 if (strcmp(branch->ref->id, id))
5460 continue;
5462 view->line[i].dirty = TRUE;
5463 if (reference) {
5464 branch->author = reference->author;
5465 branch->time = reference->time;
5466 continue;
5467 }
5469 parse_author_line(line + STRING_SIZE("author "),
5470 &branch->author, &branch->time);
5471 reference = branch;
5472 }
5473 return TRUE;
5475 default:
5476 return TRUE;
5477 }
5479 }
5481 static bool
5482 branch_open_visitor(void *data, const struct ref *ref)
5483 {
5484 struct view *view = data;
5485 struct branch *branch;
5487 if (ref->tag || ref->ltag || ref->remote)
5488 return TRUE;
5490 branch = calloc(1, sizeof(*branch));
5491 if (!branch)
5492 return FALSE;
5494 branch->ref = ref;
5495 return !!add_line_data(view, branch, LINE_DEFAULT);
5496 }
5498 static bool
5499 branch_open(struct view *view)
5500 {
5501 const char *branch_log[] = {
5502 "git", "log", "--no-color", "--pretty=raw",
5503 "--simplify-by-decoration", "--all", NULL
5504 };
5506 if (!start_update(view, branch_log, NULL)) {
5507 report("Failed to load branch data");
5508 return TRUE;
5509 }
5511 setup_update(view, view->id);
5512 branch_open_visitor(view, &branch_all);
5513 foreach_ref(branch_open_visitor, view);
5514 view->p_restore = TRUE;
5516 return TRUE;
5517 }
5519 static bool
5520 branch_grep(struct view *view, struct line *line)
5521 {
5522 struct branch *branch = line->data;
5523 const char *text[] = {
5524 branch->ref->name,
5525 branch->author,
5526 NULL
5527 };
5529 return grep_text(view, text);
5530 }
5532 static void
5533 branch_select(struct view *view, struct line *line)
5534 {
5535 struct branch *branch = line->data;
5537 string_copy_rev(view->ref, branch->ref->id);
5538 string_copy_rev(ref_commit, branch->ref->id);
5539 string_copy_rev(ref_head, branch->ref->id);
5540 string_copy_rev(ref_branch, branch->ref->name);
5541 }
5543 static struct view_ops branch_ops = {
5544 "branch",
5545 NULL,
5546 branch_open,
5547 branch_read,
5548 branch_draw,
5549 branch_request,
5550 branch_grep,
5551 branch_select,
5552 };
5554 /*
5555 * Status backend
5556 */
5558 struct status {
5559 char status;
5560 struct {
5561 mode_t mode;
5562 char rev[SIZEOF_REV];
5563 char name[SIZEOF_STR];
5564 } old;
5565 struct {
5566 mode_t mode;
5567 char rev[SIZEOF_REV];
5568 char name[SIZEOF_STR];
5569 } new;
5570 };
5572 static char status_onbranch[SIZEOF_STR];
5573 static struct status stage_status;
5574 static enum line_type stage_line_type;
5575 static size_t stage_chunks;
5576 static int *stage_chunk;
5578 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5580 /* This should work even for the "On branch" line. */
5581 static inline bool
5582 status_has_none(struct view *view, struct line *line)
5583 {
5584 return line < view->line + view->lines && !line[1].data;
5585 }
5587 /* Get fields from the diff line:
5588 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5589 */
5590 static inline bool
5591 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5592 {
5593 const char *old_mode = buf + 1;
5594 const char *new_mode = buf + 8;
5595 const char *old_rev = buf + 15;
5596 const char *new_rev = buf + 56;
5597 const char *status = buf + 97;
5599 if (bufsize < 98 ||
5600 old_mode[-1] != ':' ||
5601 new_mode[-1] != ' ' ||
5602 old_rev[-1] != ' ' ||
5603 new_rev[-1] != ' ' ||
5604 status[-1] != ' ')
5605 return FALSE;
5607 file->status = *status;
5609 string_copy_rev(file->old.rev, old_rev);
5610 string_copy_rev(file->new.rev, new_rev);
5612 file->old.mode = strtoul(old_mode, NULL, 8);
5613 file->new.mode = strtoul(new_mode, NULL, 8);
5615 file->old.name[0] = file->new.name[0] = 0;
5617 return TRUE;
5618 }
5620 static bool
5621 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5622 {
5623 struct status *unmerged = NULL;
5624 char *buf;
5625 struct io io;
5627 if (!io_run(&io, IO_RD, opt_cdup, argv))
5628 return FALSE;
5630 add_line_data(view, NULL, type);
5632 while ((buf = io_get(&io, 0, TRUE))) {
5633 struct status *file = unmerged;
5635 if (!file) {
5636 file = calloc(1, sizeof(*file));
5637 if (!file || !add_line_data(view, file, type))
5638 goto error_out;
5639 }
5641 /* Parse diff info part. */
5642 if (status) {
5643 file->status = status;
5644 if (status == 'A')
5645 string_copy(file->old.rev, NULL_ID);
5647 } else if (!file->status || file == unmerged) {
5648 if (!status_get_diff(file, buf, strlen(buf)))
5649 goto error_out;
5651 buf = io_get(&io, 0, TRUE);
5652 if (!buf)
5653 break;
5655 /* Collapse all modified entries that follow an
5656 * associated unmerged entry. */
5657 if (unmerged == file) {
5658 unmerged->status = 'U';
5659 unmerged = NULL;
5660 } else if (file->status == 'U') {
5661 unmerged = file;
5662 }
5663 }
5665 /* Grab the old name for rename/copy. */
5666 if (!*file->old.name &&
5667 (file->status == 'R' || file->status == 'C')) {
5668 string_ncopy(file->old.name, buf, strlen(buf));
5670 buf = io_get(&io, 0, TRUE);
5671 if (!buf)
5672 break;
5673 }
5675 /* git-ls-files just delivers a NUL separated list of
5676 * file names similar to the second half of the
5677 * git-diff-* output. */
5678 string_ncopy(file->new.name, buf, strlen(buf));
5679 if (!*file->old.name)
5680 string_copy(file->old.name, file->new.name);
5681 file = NULL;
5682 }
5684 if (io_error(&io)) {
5685 error_out:
5686 io_done(&io);
5687 return FALSE;
5688 }
5690 if (!view->line[view->lines - 1].data)
5691 add_line_data(view, NULL, LINE_STAT_NONE);
5693 io_done(&io);
5694 return TRUE;
5695 }
5697 /* Don't show unmerged entries in the staged section. */
5698 static const char *status_diff_index_argv[] = {
5699 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5700 "--cached", "-M", "HEAD", NULL
5701 };
5703 static const char *status_diff_files_argv[] = {
5704 "git", "diff-files", "-z", NULL
5705 };
5707 static const char *status_list_other_argv[] = {
5708 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5709 };
5711 static const char *status_list_no_head_argv[] = {
5712 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5713 };
5715 static const char *update_index_argv[] = {
5716 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5717 };
5719 /* Restore the previous line number to stay in the context or select a
5720 * line with something that can be updated. */
5721 static void
5722 status_restore(struct view *view)
5723 {
5724 if (view->p_lineno >= view->lines)
5725 view->p_lineno = view->lines - 1;
5726 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5727 view->p_lineno++;
5728 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5729 view->p_lineno--;
5731 /* If the above fails, always skip the "On branch" line. */
5732 if (view->p_lineno < view->lines)
5733 view->lineno = view->p_lineno;
5734 else
5735 view->lineno = 1;
5737 if (view->lineno < view->offset)
5738 view->offset = view->lineno;
5739 else if (view->offset + view->height <= view->lineno)
5740 view->offset = view->lineno - view->height + 1;
5742 view->p_restore = FALSE;
5743 }
5745 static void
5746 status_update_onbranch(void)
5747 {
5748 static const char *paths[][2] = {
5749 { "rebase-apply/rebasing", "Rebasing" },
5750 { "rebase-apply/applying", "Applying mailbox" },
5751 { "rebase-apply/", "Rebasing mailbox" },
5752 { "rebase-merge/interactive", "Interactive rebase" },
5753 { "rebase-merge/", "Rebase merge" },
5754 { "MERGE_HEAD", "Merging" },
5755 { "BISECT_LOG", "Bisecting" },
5756 { "HEAD", "On branch" },
5757 };
5758 char buf[SIZEOF_STR];
5759 struct stat stat;
5760 int i;
5762 if (is_initial_commit()) {
5763 string_copy(status_onbranch, "Initial commit");
5764 return;
5765 }
5767 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5768 char *head = opt_head;
5770 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5771 lstat(buf, &stat) < 0)
5772 continue;
5774 if (!*opt_head) {
5775 struct io io;
5777 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5778 io_read_buf(&io, buf, sizeof(buf))) {
5779 head = buf;
5780 if (!prefixcmp(head, "refs/heads/"))
5781 head += STRING_SIZE("refs/heads/");
5782 }
5783 }
5785 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5786 string_copy(status_onbranch, opt_head);
5787 return;
5788 }
5790 string_copy(status_onbranch, "Not currently on any branch");
5791 }
5793 /* First parse staged info using git-diff-index(1), then parse unstaged
5794 * info using git-diff-files(1), and finally untracked files using
5795 * git-ls-files(1). */
5796 static bool
5797 status_open(struct view *view)
5798 {
5799 reset_view(view);
5801 add_line_data(view, NULL, LINE_STAT_HEAD);
5802 status_update_onbranch();
5804 io_run_bg(update_index_argv);
5806 if (is_initial_commit()) {
5807 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5808 return FALSE;
5809 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5810 return FALSE;
5811 }
5813 if (!opt_untracked_dirs_content)
5814 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5816 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5817 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5818 return FALSE;
5820 /* Restore the exact position or use the specialized restore
5821 * mode? */
5822 if (!view->p_restore)
5823 status_restore(view);
5824 return TRUE;
5825 }
5827 static bool
5828 status_draw(struct view *view, struct line *line, unsigned int lineno)
5829 {
5830 struct status *status = line->data;
5831 enum line_type type;
5832 const char *text;
5834 if (!status) {
5835 switch (line->type) {
5836 case LINE_STAT_STAGED:
5837 type = LINE_STAT_SECTION;
5838 text = "Changes to be committed:";
5839 break;
5841 case LINE_STAT_UNSTAGED:
5842 type = LINE_STAT_SECTION;
5843 text = "Changed but not updated:";
5844 break;
5846 case LINE_STAT_UNTRACKED:
5847 type = LINE_STAT_SECTION;
5848 text = "Untracked files:";
5849 break;
5851 case LINE_STAT_NONE:
5852 type = LINE_DEFAULT;
5853 text = " (no files)";
5854 break;
5856 case LINE_STAT_HEAD:
5857 type = LINE_STAT_HEAD;
5858 text = status_onbranch;
5859 break;
5861 default:
5862 return FALSE;
5863 }
5864 } else {
5865 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5867 buf[0] = status->status;
5868 if (draw_text(view, line->type, buf, TRUE))
5869 return TRUE;
5870 type = LINE_DEFAULT;
5871 text = status->new.name;
5872 }
5874 draw_text(view, type, text, TRUE);
5875 return TRUE;
5876 }
5878 static enum request
5879 status_load_error(struct view *view, struct view *stage, const char *path)
5880 {
5881 if (displayed_views() == 2 || display[current_view] != view)
5882 maximize_view(view);
5883 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5884 return REQ_NONE;
5885 }
5887 static enum request
5888 status_enter(struct view *view, struct line *line)
5889 {
5890 struct status *status = line->data;
5891 const char *oldpath = status ? status->old.name : NULL;
5892 /* Diffs for unmerged entries are empty when passing the new
5893 * path, so leave it empty. */
5894 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5895 const char *info;
5896 enum open_flags split;
5897 struct view *stage = VIEW(REQ_VIEW_STAGE);
5899 if (line->type == LINE_STAT_NONE ||
5900 (!status && line[1].type == LINE_STAT_NONE)) {
5901 report("No file to diff");
5902 return REQ_NONE;
5903 }
5905 switch (line->type) {
5906 case LINE_STAT_STAGED:
5907 if (is_initial_commit()) {
5908 const char *no_head_diff_argv[] = {
5909 "git", "diff", "--no-color", "--patch-with-stat",
5910 "--", "/dev/null", newpath, NULL
5911 };
5913 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5914 return status_load_error(view, stage, newpath);
5915 } else {
5916 const char *index_show_argv[] = {
5917 "git", "diff-index", "--root", "--patch-with-stat",
5918 "-C", "-M", "--cached", "HEAD", "--",
5919 oldpath, newpath, NULL
5920 };
5922 if (!prepare_update(stage, index_show_argv, opt_cdup))
5923 return status_load_error(view, stage, newpath);
5924 }
5926 if (status)
5927 info = "Staged changes to %s";
5928 else
5929 info = "Staged changes";
5930 break;
5932 case LINE_STAT_UNSTAGED:
5933 {
5934 const char *files_show_argv[] = {
5935 "git", "diff-files", "--root", "--patch-with-stat",
5936 "-C", "-M", "--", oldpath, newpath, NULL
5937 };
5939 if (!prepare_update(stage, files_show_argv, opt_cdup))
5940 return status_load_error(view, stage, newpath);
5941 if (status)
5942 info = "Unstaged changes to %s";
5943 else
5944 info = "Unstaged changes";
5945 break;
5946 }
5947 case LINE_STAT_UNTRACKED:
5948 if (!newpath) {
5949 report("No file to show");
5950 return REQ_NONE;
5951 }
5953 if (!suffixcmp(status->new.name, -1, "/")) {
5954 report("Cannot display a directory");
5955 return REQ_NONE;
5956 }
5958 if (!prepare_update_file(stage, newpath))
5959 return status_load_error(view, stage, newpath);
5960 info = "Untracked file %s";
5961 break;
5963 case LINE_STAT_HEAD:
5964 return REQ_NONE;
5966 default:
5967 die("line type %d not handled in switch", line->type);
5968 }
5970 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5971 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5972 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5973 if (status) {
5974 stage_status = *status;
5975 } else {
5976 memset(&stage_status, 0, sizeof(stage_status));
5977 }
5979 stage_line_type = line->type;
5980 stage_chunks = 0;
5981 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5982 }
5984 return REQ_NONE;
5985 }
5987 static bool
5988 status_exists(struct status *status, enum line_type type)
5989 {
5990 struct view *view = VIEW(REQ_VIEW_STATUS);
5991 unsigned long lineno;
5993 for (lineno = 0; lineno < view->lines; lineno++) {
5994 struct line *line = &view->line[lineno];
5995 struct status *pos = line->data;
5997 if (line->type != type)
5998 continue;
5999 if (!pos && (!status || !status->status) && line[1].data) {
6000 select_view_line(view, lineno);
6001 return TRUE;
6002 }
6003 if (pos && !strcmp(status->new.name, pos->new.name)) {
6004 select_view_line(view, lineno);
6005 return TRUE;
6006 }
6007 }
6009 return FALSE;
6010 }
6013 static bool
6014 status_update_prepare(struct io *io, enum line_type type)
6015 {
6016 const char *staged_argv[] = {
6017 "git", "update-index", "-z", "--index-info", NULL
6018 };
6019 const char *others_argv[] = {
6020 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6021 };
6023 switch (type) {
6024 case LINE_STAT_STAGED:
6025 return io_run(io, IO_WR, opt_cdup, staged_argv);
6027 case LINE_STAT_UNSTAGED:
6028 case LINE_STAT_UNTRACKED:
6029 return io_run(io, IO_WR, opt_cdup, others_argv);
6031 default:
6032 die("line type %d not handled in switch", type);
6033 return FALSE;
6034 }
6035 }
6037 static bool
6038 status_update_write(struct io *io, struct status *status, enum line_type type)
6039 {
6040 char buf[SIZEOF_STR];
6041 size_t bufsize = 0;
6043 switch (type) {
6044 case LINE_STAT_STAGED:
6045 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6046 status->old.mode,
6047 status->old.rev,
6048 status->old.name, 0))
6049 return FALSE;
6050 break;
6052 case LINE_STAT_UNSTAGED:
6053 case LINE_STAT_UNTRACKED:
6054 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6055 return FALSE;
6056 break;
6058 default:
6059 die("line type %d not handled in switch", type);
6060 }
6062 return io_write(io, buf, bufsize);
6063 }
6065 static bool
6066 status_update_file(struct status *status, enum line_type type)
6067 {
6068 struct io io;
6069 bool result;
6071 if (!status_update_prepare(&io, type))
6072 return FALSE;
6074 result = status_update_write(&io, status, type);
6075 return io_done(&io) && result;
6076 }
6078 static bool
6079 status_update_files(struct view *view, struct line *line)
6080 {
6081 char buf[sizeof(view->ref)];
6082 struct io io;
6083 bool result = TRUE;
6084 struct line *pos = view->line + view->lines;
6085 int files = 0;
6086 int file, done;
6087 int cursor_y = -1, cursor_x = -1;
6089 if (!status_update_prepare(&io, line->type))
6090 return FALSE;
6092 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6093 files++;
6095 string_copy(buf, view->ref);
6096 getsyx(cursor_y, cursor_x);
6097 for (file = 0, done = 5; result && file < files; line++, file++) {
6098 int almost_done = file * 100 / files;
6100 if (almost_done > done) {
6101 done = almost_done;
6102 string_format(view->ref, "updating file %u of %u (%d%% done)",
6103 file, files, done);
6104 update_view_title(view);
6105 setsyx(cursor_y, cursor_x);
6106 doupdate();
6107 }
6108 result = status_update_write(&io, line->data, line->type);
6109 }
6110 string_copy(view->ref, buf);
6112 return io_done(&io) && result;
6113 }
6115 static bool
6116 status_update(struct view *view)
6117 {
6118 struct line *line = &view->line[view->lineno];
6120 assert(view->lines);
6122 if (!line->data) {
6123 /* This should work even for the "On branch" line. */
6124 if (line < view->line + view->lines && !line[1].data) {
6125 report("Nothing to update");
6126 return FALSE;
6127 }
6129 if (!status_update_files(view, line + 1)) {
6130 report("Failed to update file status");
6131 return FALSE;
6132 }
6134 } else if (!status_update_file(line->data, line->type)) {
6135 report("Failed to update file status");
6136 return FALSE;
6137 }
6139 return TRUE;
6140 }
6142 static bool
6143 status_revert(struct status *status, enum line_type type, bool has_none)
6144 {
6145 if (!status || type != LINE_STAT_UNSTAGED) {
6146 if (type == LINE_STAT_STAGED) {
6147 report("Cannot revert changes to staged files");
6148 } else if (type == LINE_STAT_UNTRACKED) {
6149 report("Cannot revert changes to untracked files");
6150 } else if (has_none) {
6151 report("Nothing to revert");
6152 } else {
6153 report("Cannot revert changes to multiple files");
6154 }
6156 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6157 char mode[10] = "100644";
6158 const char *reset_argv[] = {
6159 "git", "update-index", "--cacheinfo", mode,
6160 status->old.rev, status->old.name, NULL
6161 };
6162 const char *checkout_argv[] = {
6163 "git", "checkout", "--", status->old.name, NULL
6164 };
6166 if (status->status == 'U') {
6167 string_format(mode, "%5o", status->old.mode);
6169 if (status->old.mode == 0 && status->new.mode == 0) {
6170 reset_argv[2] = "--force-remove";
6171 reset_argv[3] = status->old.name;
6172 reset_argv[4] = NULL;
6173 }
6175 if (!io_run_fg(reset_argv, opt_cdup))
6176 return FALSE;
6177 if (status->old.mode == 0 && status->new.mode == 0)
6178 return TRUE;
6179 }
6181 return io_run_fg(checkout_argv, opt_cdup);
6182 }
6184 return FALSE;
6185 }
6187 static enum request
6188 status_request(struct view *view, enum request request, struct line *line)
6189 {
6190 struct status *status = line->data;
6192 switch (request) {
6193 case REQ_STATUS_UPDATE:
6194 if (!status_update(view))
6195 return REQ_NONE;
6196 break;
6198 case REQ_STATUS_REVERT:
6199 if (!status_revert(status, line->type, status_has_none(view, line)))
6200 return REQ_NONE;
6201 break;
6203 case REQ_STATUS_MERGE:
6204 if (!status || status->status != 'U') {
6205 report("Merging only possible for files with unmerged status ('U').");
6206 return REQ_NONE;
6207 }
6208 open_mergetool(status->new.name);
6209 break;
6211 case REQ_EDIT:
6212 if (!status)
6213 return request;
6214 if (status->status == 'D') {
6215 report("File has been deleted.");
6216 return REQ_NONE;
6217 }
6219 open_editor(status->new.name);
6220 break;
6222 case REQ_VIEW_BLAME:
6223 if (status)
6224 opt_ref[0] = 0;
6225 return request;
6227 case REQ_ENTER:
6228 /* After returning the status view has been split to
6229 * show the stage view. No further reloading is
6230 * necessary. */
6231 return status_enter(view, line);
6233 case REQ_REFRESH:
6234 /* Simply reload the view. */
6235 break;
6237 default:
6238 return request;
6239 }
6241 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6243 return REQ_NONE;
6244 }
6246 static void
6247 status_select(struct view *view, struct line *line)
6248 {
6249 struct status *status = line->data;
6250 char file[SIZEOF_STR] = "all files";
6251 const char *text;
6252 const char *key;
6254 if (status && !string_format(file, "'%s'", status->new.name))
6255 return;
6257 if (!status && line[1].type == LINE_STAT_NONE)
6258 line++;
6260 switch (line->type) {
6261 case LINE_STAT_STAGED:
6262 text = "Press %s to unstage %s for commit";
6263 break;
6265 case LINE_STAT_UNSTAGED:
6266 text = "Press %s to stage %s for commit";
6267 break;
6269 case LINE_STAT_UNTRACKED:
6270 text = "Press %s to stage %s for addition";
6271 break;
6273 case LINE_STAT_HEAD:
6274 case LINE_STAT_NONE:
6275 text = "Nothing to update";
6276 break;
6278 default:
6279 die("line type %d not handled in switch", line->type);
6280 }
6282 if (status && status->status == 'U') {
6283 text = "Press %s to resolve conflict in %s";
6284 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6286 } else {
6287 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6288 }
6290 string_format(view->ref, text, key, file);
6291 if (status)
6292 string_copy(opt_file, status->new.name);
6293 }
6295 static bool
6296 status_grep(struct view *view, struct line *line)
6297 {
6298 struct status *status = line->data;
6300 if (status) {
6301 const char buf[2] = { status->status, 0 };
6302 const char *text[] = { status->new.name, buf, NULL };
6304 return grep_text(view, text);
6305 }
6307 return FALSE;
6308 }
6310 static struct view_ops status_ops = {
6311 "file",
6312 NULL,
6313 status_open,
6314 NULL,
6315 status_draw,
6316 status_request,
6317 status_grep,
6318 status_select,
6319 };
6322 static bool
6323 stage_diff_write(struct io *io, struct line *line, struct line *end)
6324 {
6325 while (line < end) {
6326 if (!io_write(io, line->data, strlen(line->data)) ||
6327 !io_write(io, "\n", 1))
6328 return FALSE;
6329 line++;
6330 if (line->type == LINE_DIFF_CHUNK ||
6331 line->type == LINE_DIFF_HEADER)
6332 break;
6333 }
6335 return TRUE;
6336 }
6338 static struct line *
6339 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6340 {
6341 for (; view->line < line; line--)
6342 if (line->type == type)
6343 return line;
6345 return NULL;
6346 }
6348 static bool
6349 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6350 {
6351 const char *apply_argv[SIZEOF_ARG] = {
6352 "git", "apply", "--whitespace=nowarn", NULL
6353 };
6354 struct line *diff_hdr;
6355 struct io io;
6356 int argc = 3;
6358 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6359 if (!diff_hdr)
6360 return FALSE;
6362 if (!revert)
6363 apply_argv[argc++] = "--cached";
6364 if (revert || stage_line_type == LINE_STAT_STAGED)
6365 apply_argv[argc++] = "-R";
6366 apply_argv[argc++] = "-";
6367 apply_argv[argc++] = NULL;
6368 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6369 return FALSE;
6371 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6372 !stage_diff_write(&io, chunk, view->line + view->lines))
6373 chunk = NULL;
6375 io_done(&io);
6376 io_run_bg(update_index_argv);
6378 return chunk ? TRUE : FALSE;
6379 }
6381 static bool
6382 stage_update(struct view *view, struct line *line)
6383 {
6384 struct line *chunk = NULL;
6386 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6387 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6389 if (chunk) {
6390 if (!stage_apply_chunk(view, chunk, FALSE)) {
6391 report("Failed to apply chunk");
6392 return FALSE;
6393 }
6395 } else if (!stage_status.status) {
6396 view = VIEW(REQ_VIEW_STATUS);
6398 for (line = view->line; line < view->line + view->lines; line++)
6399 if (line->type == stage_line_type)
6400 break;
6402 if (!status_update_files(view, line + 1)) {
6403 report("Failed to update files");
6404 return FALSE;
6405 }
6407 } else if (!status_update_file(&stage_status, stage_line_type)) {
6408 report("Failed to update file");
6409 return FALSE;
6410 }
6412 return TRUE;
6413 }
6415 static bool
6416 stage_revert(struct view *view, struct line *line)
6417 {
6418 struct line *chunk = NULL;
6420 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6421 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6423 if (chunk) {
6424 if (!prompt_yesno("Are you sure you want to revert changes?"))
6425 return FALSE;
6427 if (!stage_apply_chunk(view, chunk, TRUE)) {
6428 report("Failed to revert chunk");
6429 return FALSE;
6430 }
6431 return TRUE;
6433 } else {
6434 return status_revert(stage_status.status ? &stage_status : NULL,
6435 stage_line_type, FALSE);
6436 }
6437 }
6440 static void
6441 stage_next(struct view *view, struct line *line)
6442 {
6443 int i;
6445 if (!stage_chunks) {
6446 for (line = view->line; line < view->line + view->lines; line++) {
6447 if (line->type != LINE_DIFF_CHUNK)
6448 continue;
6450 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6451 report("Allocation failure");
6452 return;
6453 }
6455 stage_chunk[stage_chunks++] = line - view->line;
6456 }
6457 }
6459 for (i = 0; i < stage_chunks; i++) {
6460 if (stage_chunk[i] > view->lineno) {
6461 do_scroll_view(view, stage_chunk[i] - view->lineno);
6462 report("Chunk %d of %d", i + 1, stage_chunks);
6463 return;
6464 }
6465 }
6467 report("No next chunk found");
6468 }
6470 static enum request
6471 stage_request(struct view *view, enum request request, struct line *line)
6472 {
6473 switch (request) {
6474 case REQ_STATUS_UPDATE:
6475 if (!stage_update(view, line))
6476 return REQ_NONE;
6477 break;
6479 case REQ_STATUS_REVERT:
6480 if (!stage_revert(view, line))
6481 return REQ_NONE;
6482 break;
6484 case REQ_STAGE_NEXT:
6485 if (stage_line_type == LINE_STAT_UNTRACKED) {
6486 report("File is untracked; press %s to add",
6487 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6488 return REQ_NONE;
6489 }
6490 stage_next(view, line);
6491 return REQ_NONE;
6493 case REQ_EDIT:
6494 if (!stage_status.new.name[0])
6495 return request;
6496 if (stage_status.status == 'D') {
6497 report("File has been deleted.");
6498 return REQ_NONE;
6499 }
6501 open_editor(stage_status.new.name);
6502 break;
6504 case REQ_REFRESH:
6505 /* Reload everything ... */
6506 break;
6508 case REQ_VIEW_BLAME:
6509 if (stage_status.new.name[0]) {
6510 string_copy(opt_file, stage_status.new.name);
6511 opt_ref[0] = 0;
6512 }
6513 return request;
6515 case REQ_ENTER:
6516 return pager_request(view, request, line);
6518 default:
6519 return request;
6520 }
6522 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6523 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6525 /* Check whether the staged entry still exists, and close the
6526 * stage view if it doesn't. */
6527 if (!status_exists(&stage_status, stage_line_type)) {
6528 status_restore(VIEW(REQ_VIEW_STATUS));
6529 return REQ_VIEW_CLOSE;
6530 }
6532 if (stage_line_type == LINE_STAT_UNTRACKED) {
6533 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6534 report("Cannot display a directory");
6535 return REQ_NONE;
6536 }
6538 if (!prepare_update_file(view, stage_status.new.name)) {
6539 report("Failed to open file: %s", strerror(errno));
6540 return REQ_NONE;
6541 }
6542 }
6543 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6545 return REQ_NONE;
6546 }
6548 static struct view_ops stage_ops = {
6549 "line",
6550 NULL,
6551 NULL,
6552 pager_read,
6553 pager_draw,
6554 stage_request,
6555 pager_grep,
6556 pager_select,
6557 };
6560 /*
6561 * Revision graph
6562 */
6564 struct commit {
6565 char id[SIZEOF_REV]; /* SHA1 ID. */
6566 char title[128]; /* First line of the commit message. */
6567 const char *author; /* Author of the commit. */
6568 struct time time; /* Date from the author ident. */
6569 struct ref_list *refs; /* Repository references. */
6570 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6571 size_t graph_size; /* The width of the graph array. */
6572 bool has_parents; /* Rewritten --parents seen. */
6573 };
6575 /* Size of rev graph with no "padding" columns */
6576 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6578 struct rev_graph {
6579 struct rev_graph *prev, *next, *parents;
6580 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6581 size_t size;
6582 struct commit *commit;
6583 size_t pos;
6584 unsigned int boundary:1;
6585 };
6587 /* Parents of the commit being visualized. */
6588 static struct rev_graph graph_parents[4];
6590 /* The current stack of revisions on the graph. */
6591 static struct rev_graph graph_stacks[4] = {
6592 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6593 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6594 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6595 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6596 };
6598 static inline bool
6599 graph_parent_is_merge(struct rev_graph *graph)
6600 {
6601 return graph->parents->size > 1;
6602 }
6604 static inline void
6605 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6606 {
6607 struct commit *commit = graph->commit;
6609 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6610 commit->graph[commit->graph_size++] = symbol;
6611 }
6613 static void
6614 clear_rev_graph(struct rev_graph *graph)
6615 {
6616 graph->boundary = 0;
6617 graph->size = graph->pos = 0;
6618 graph->commit = NULL;
6619 memset(graph->parents, 0, sizeof(*graph->parents));
6620 }
6622 static void
6623 done_rev_graph(struct rev_graph *graph)
6624 {
6625 if (graph_parent_is_merge(graph) &&
6626 graph->pos < graph->size - 1 &&
6627 graph->next->size == graph->size + graph->parents->size - 1) {
6628 size_t i = graph->pos + graph->parents->size - 1;
6630 graph->commit->graph_size = i * 2;
6631 while (i < graph->next->size - 1) {
6632 append_to_rev_graph(graph, ' ');
6633 append_to_rev_graph(graph, '\\');
6634 i++;
6635 }
6636 }
6638 clear_rev_graph(graph);
6639 }
6641 static void
6642 push_rev_graph(struct rev_graph *graph, const char *parent)
6643 {
6644 int i;
6646 /* "Collapse" duplicate parents lines.
6647 *
6648 * FIXME: This needs to also update update the drawn graph but
6649 * for now it just serves as a method for pruning graph lines. */
6650 for (i = 0; i < graph->size; i++)
6651 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6652 return;
6654 if (graph->size < SIZEOF_REVITEMS) {
6655 string_copy_rev(graph->rev[graph->size++], parent);
6656 }
6657 }
6659 static chtype
6660 get_rev_graph_symbol(struct rev_graph *graph)
6661 {
6662 chtype symbol;
6664 if (graph->boundary)
6665 symbol = REVGRAPH_BOUND;
6666 else if (graph->parents->size == 0)
6667 symbol = REVGRAPH_INIT;
6668 else if (graph_parent_is_merge(graph))
6669 symbol = REVGRAPH_MERGE;
6670 else if (graph->pos >= graph->size)
6671 symbol = REVGRAPH_BRANCH;
6672 else
6673 symbol = REVGRAPH_COMMIT;
6675 return symbol;
6676 }
6678 static void
6679 draw_rev_graph(struct rev_graph *graph)
6680 {
6681 struct rev_filler {
6682 chtype separator, line;
6683 };
6684 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6685 static struct rev_filler fillers[] = {
6686 { ' ', '|' },
6687 { '`', '.' },
6688 { '\'', ' ' },
6689 { '/', ' ' },
6690 };
6691 chtype symbol = get_rev_graph_symbol(graph);
6692 struct rev_filler *filler;
6693 size_t i;
6695 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6696 filler = &fillers[DEFAULT];
6698 for (i = 0; i < graph->pos; i++) {
6699 append_to_rev_graph(graph, filler->line);
6700 if (graph_parent_is_merge(graph->prev) &&
6701 graph->prev->pos == i)
6702 filler = &fillers[RSHARP];
6704 append_to_rev_graph(graph, filler->separator);
6705 }
6707 /* Place the symbol for this revision. */
6708 append_to_rev_graph(graph, symbol);
6710 if (graph->prev->size > graph->size)
6711 filler = &fillers[RDIAG];
6712 else
6713 filler = &fillers[DEFAULT];
6715 i++;
6717 for (; i < graph->size; i++) {
6718 append_to_rev_graph(graph, filler->separator);
6719 append_to_rev_graph(graph, filler->line);
6720 if (graph_parent_is_merge(graph->prev) &&
6721 i < graph->prev->pos + graph->parents->size)
6722 filler = &fillers[RSHARP];
6723 if (graph->prev->size > graph->size)
6724 filler = &fillers[LDIAG];
6725 }
6727 if (graph->prev->size > graph->size) {
6728 append_to_rev_graph(graph, filler->separator);
6729 if (filler->line != ' ')
6730 append_to_rev_graph(graph, filler->line);
6731 }
6732 }
6734 /* Prepare the next rev graph */
6735 static void
6736 prepare_rev_graph(struct rev_graph *graph)
6737 {
6738 size_t i;
6740 /* First, traverse all lines of revisions up to the active one. */
6741 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6742 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6743 break;
6745 push_rev_graph(graph->next, graph->rev[graph->pos]);
6746 }
6748 /* Interleave the new revision parent(s). */
6749 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6750 push_rev_graph(graph->next, graph->parents->rev[i]);
6752 /* Lastly, put any remaining revisions. */
6753 for (i = graph->pos + 1; i < graph->size; i++)
6754 push_rev_graph(graph->next, graph->rev[i]);
6755 }
6757 static void
6758 update_rev_graph(struct view *view, struct rev_graph *graph)
6759 {
6760 /* If this is the finalizing update ... */
6761 if (graph->commit)
6762 prepare_rev_graph(graph);
6764 /* Graph visualization needs a one rev look-ahead,
6765 * so the first update doesn't visualize anything. */
6766 if (!graph->prev->commit)
6767 return;
6769 if (view->lines > 2)
6770 view->line[view->lines - 3].dirty = 1;
6771 if (view->lines > 1)
6772 view->line[view->lines - 2].dirty = 1;
6773 draw_rev_graph(graph->prev);
6774 done_rev_graph(graph->prev->prev);
6775 }
6778 /*
6779 * Main view backend
6780 */
6782 static const char *main_argv[SIZEOF_ARG] = {
6783 "git", "log", "--no-color", "--pretty=raw", "--parents",
6784 "--topo-order", "%(diffargs)", "%(revargs)",
6785 "--", "%(fileargs)", NULL
6786 };
6788 static bool
6789 main_draw(struct view *view, struct line *line, unsigned int lineno)
6790 {
6791 struct commit *commit = line->data;
6793 if (!commit->author)
6794 return FALSE;
6796 if (opt_date && draw_date(view, &commit->time))
6797 return TRUE;
6799 if (opt_author && draw_author(view, commit->author))
6800 return TRUE;
6802 if (opt_rev_graph && commit->graph_size &&
6803 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6804 return TRUE;
6806 if (opt_show_refs && commit->refs) {
6807 size_t i;
6809 for (i = 0; i < commit->refs->size; i++) {
6810 struct ref *ref = commit->refs->refs[i];
6811 enum line_type type;
6813 if (ref->head)
6814 type = LINE_MAIN_HEAD;
6815 else if (ref->ltag)
6816 type = LINE_MAIN_LOCAL_TAG;
6817 else if (ref->tag)
6818 type = LINE_MAIN_TAG;
6819 else if (ref->tracked)
6820 type = LINE_MAIN_TRACKED;
6821 else if (ref->remote)
6822 type = LINE_MAIN_REMOTE;
6823 else
6824 type = LINE_MAIN_REF;
6826 if (draw_text(view, type, "[", TRUE) ||
6827 draw_text(view, type, ref->name, TRUE) ||
6828 draw_text(view, type, "]", TRUE))
6829 return TRUE;
6831 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6832 return TRUE;
6833 }
6834 }
6836 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6837 return TRUE;
6838 }
6840 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6841 static bool
6842 main_read(struct view *view, char *line)
6843 {
6844 static struct rev_graph *graph = graph_stacks;
6845 enum line_type type;
6846 struct commit *commit;
6848 if (!line) {
6849 int i;
6851 if (!view->lines && !view->prev)
6852 die("No revisions match the given arguments.");
6853 if (view->lines > 0) {
6854 commit = view->line[view->lines - 1].data;
6855 view->line[view->lines - 1].dirty = 1;
6856 if (!commit->author) {
6857 view->lines--;
6858 free(commit);
6859 graph->commit = NULL;
6860 }
6861 }
6862 update_rev_graph(view, graph);
6864 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6865 clear_rev_graph(&graph_stacks[i]);
6866 return TRUE;
6867 }
6869 type = get_line_type(line);
6870 if (type == LINE_COMMIT) {
6871 commit = calloc(1, sizeof(struct commit));
6872 if (!commit)
6873 return FALSE;
6875 line += STRING_SIZE("commit ");
6876 if (*line == '-') {
6877 graph->boundary = 1;
6878 line++;
6879 }
6881 string_copy_rev(commit->id, line);
6882 commit->refs = get_ref_list(commit->id);
6883 graph->commit = commit;
6884 add_line_data(view, commit, LINE_MAIN_COMMIT);
6886 while ((line = strchr(line, ' '))) {
6887 line++;
6888 push_rev_graph(graph->parents, line);
6889 commit->has_parents = TRUE;
6890 }
6891 return TRUE;
6892 }
6894 if (!view->lines)
6895 return TRUE;
6896 commit = view->line[view->lines - 1].data;
6898 switch (type) {
6899 case LINE_PARENT:
6900 if (commit->has_parents)
6901 break;
6902 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6903 break;
6905 case LINE_AUTHOR:
6906 parse_author_line(line + STRING_SIZE("author "),
6907 &commit->author, &commit->time);
6908 update_rev_graph(view, graph);
6909 graph = graph->next;
6910 break;
6912 default:
6913 /* Fill in the commit title if it has not already been set. */
6914 if (commit->title[0])
6915 break;
6917 /* Require titles to start with a non-space character at the
6918 * offset used by git log. */
6919 if (strncmp(line, " ", 4))
6920 break;
6921 line += 4;
6922 /* Well, if the title starts with a whitespace character,
6923 * try to be forgiving. Otherwise we end up with no title. */
6924 while (isspace(*line))
6925 line++;
6926 if (*line == '\0')
6927 break;
6928 /* FIXME: More graceful handling of titles; append "..." to
6929 * shortened titles, etc. */
6931 string_expand(commit->title, sizeof(commit->title), line, 1);
6932 view->line[view->lines - 1].dirty = 1;
6933 }
6935 return TRUE;
6936 }
6938 static enum request
6939 main_request(struct view *view, enum request request, struct line *line)
6940 {
6941 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6943 switch (request) {
6944 case REQ_ENTER:
6945 open_view(view, REQ_VIEW_DIFF, flags);
6946 break;
6947 case REQ_REFRESH:
6948 load_refs();
6949 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6950 break;
6951 default:
6952 return request;
6953 }
6955 return REQ_NONE;
6956 }
6958 static bool
6959 grep_refs(struct ref_list *list, regex_t *regex)
6960 {
6961 regmatch_t pmatch;
6962 size_t i;
6964 if (!opt_show_refs || !list)
6965 return FALSE;
6967 for (i = 0; i < list->size; i++) {
6968 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6969 return TRUE;
6970 }
6972 return FALSE;
6973 }
6975 static bool
6976 main_grep(struct view *view, struct line *line)
6977 {
6978 struct commit *commit = line->data;
6979 const char *text[] = {
6980 commit->title,
6981 opt_author ? commit->author : "",
6982 mkdate(&commit->time, opt_date),
6983 NULL
6984 };
6986 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6987 }
6989 static void
6990 main_select(struct view *view, struct line *line)
6991 {
6992 struct commit *commit = line->data;
6994 string_copy_rev(view->ref, commit->id);
6995 string_copy_rev(ref_commit, view->ref);
6996 }
6998 static struct view_ops main_ops = {
6999 "commit",
7000 main_argv,
7001 NULL,
7002 main_read,
7003 main_draw,
7004 main_request,
7005 main_grep,
7006 main_select,
7007 };
7010 /*
7011 * Status management
7012 */
7014 /* Whether or not the curses interface has been initialized. */
7015 static bool cursed = FALSE;
7017 /* Terminal hacks and workarounds. */
7018 static bool use_scroll_redrawwin;
7019 static bool use_scroll_status_wclear;
7021 /* The status window is used for polling keystrokes. */
7022 static WINDOW *status_win;
7024 /* Reading from the prompt? */
7025 static bool input_mode = FALSE;
7027 static bool status_empty = FALSE;
7029 /* Update status and title window. */
7030 static void
7031 report(const char *msg, ...)
7032 {
7033 struct view *view = display[current_view];
7035 if (input_mode)
7036 return;
7038 if (!view) {
7039 char buf[SIZEOF_STR];
7040 va_list args;
7042 va_start(args, msg);
7043 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7044 buf[sizeof(buf) - 1] = 0;
7045 buf[sizeof(buf) - 2] = '.';
7046 buf[sizeof(buf) - 3] = '.';
7047 buf[sizeof(buf) - 4] = '.';
7048 }
7049 va_end(args);
7050 die("%s", buf);
7051 }
7053 if (!status_empty || *msg) {
7054 va_list args;
7056 va_start(args, msg);
7058 wmove(status_win, 0, 0);
7059 if (view->has_scrolled && use_scroll_status_wclear)
7060 wclear(status_win);
7061 if (*msg) {
7062 vwprintw(status_win, msg, args);
7063 status_empty = FALSE;
7064 } else {
7065 status_empty = TRUE;
7066 }
7067 wclrtoeol(status_win);
7068 wnoutrefresh(status_win);
7070 va_end(args);
7071 }
7073 update_view_title(view);
7074 }
7076 static void
7077 init_display(void)
7078 {
7079 const char *term;
7080 int x, y;
7082 /* Initialize the curses library */
7083 if (isatty(STDIN_FILENO)) {
7084 cursed = !!initscr();
7085 opt_tty = stdin;
7086 } else {
7087 /* Leave stdin and stdout alone when acting as a pager. */
7088 opt_tty = fopen("/dev/tty", "r+");
7089 if (!opt_tty)
7090 die("Failed to open /dev/tty");
7091 cursed = !!newterm(NULL, opt_tty, opt_tty);
7092 }
7094 if (!cursed)
7095 die("Failed to initialize curses");
7097 nonl(); /* Disable conversion and detect newlines from input. */
7098 cbreak(); /* Take input chars one at a time, no wait for \n */
7099 noecho(); /* Don't echo input */
7100 leaveok(stdscr, FALSE);
7102 if (has_colors())
7103 init_colors();
7105 getmaxyx(stdscr, y, x);
7106 status_win = newwin(1, 0, y - 1, 0);
7107 if (!status_win)
7108 die("Failed to create status window");
7110 /* Enable keyboard mapping */
7111 keypad(status_win, TRUE);
7112 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7114 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7115 set_tabsize(opt_tab_size);
7116 #else
7117 TABSIZE = opt_tab_size;
7118 #endif
7120 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7121 if (term && !strcmp(term, "gnome-terminal")) {
7122 /* In the gnome-terminal-emulator, the message from
7123 * scrolling up one line when impossible followed by
7124 * scrolling down one line causes corruption of the
7125 * status line. This is fixed by calling wclear. */
7126 use_scroll_status_wclear = TRUE;
7127 use_scroll_redrawwin = FALSE;
7129 } else if (term && !strcmp(term, "xrvt-xpm")) {
7130 /* No problems with full optimizations in xrvt-(unicode)
7131 * and aterm. */
7132 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7134 } else {
7135 /* When scrolling in (u)xterm the last line in the
7136 * scrolling direction will update slowly. */
7137 use_scroll_redrawwin = TRUE;
7138 use_scroll_status_wclear = FALSE;
7139 }
7140 }
7142 static int
7143 get_input(int prompt_position)
7144 {
7145 struct view *view;
7146 int i, key, cursor_y, cursor_x;
7148 if (prompt_position)
7149 input_mode = TRUE;
7151 while (TRUE) {
7152 bool loading = FALSE;
7154 foreach_view (view, i) {
7155 update_view(view);
7156 if (view_is_displayed(view) && view->has_scrolled &&
7157 use_scroll_redrawwin)
7158 redrawwin(view->win);
7159 view->has_scrolled = FALSE;
7160 if (view->pipe)
7161 loading = TRUE;
7162 }
7164 /* Update the cursor position. */
7165 if (prompt_position) {
7166 getbegyx(status_win, cursor_y, cursor_x);
7167 cursor_x = prompt_position;
7168 } else {
7169 view = display[current_view];
7170 getbegyx(view->win, cursor_y, cursor_x);
7171 cursor_x = view->width - 1;
7172 cursor_y += view->lineno - view->offset;
7173 }
7174 setsyx(cursor_y, cursor_x);
7176 /* Refresh, accept single keystroke of input */
7177 doupdate();
7178 nodelay(status_win, loading);
7179 key = wgetch(status_win);
7181 /* wgetch() with nodelay() enabled returns ERR when
7182 * there's no input. */
7183 if (key == ERR) {
7185 } else if (key == KEY_RESIZE) {
7186 int height, width;
7188 getmaxyx(stdscr, height, width);
7190 wresize(status_win, 1, width);
7191 mvwin(status_win, height - 1, 0);
7192 wnoutrefresh(status_win);
7193 resize_display();
7194 redraw_display(TRUE);
7196 } else {
7197 input_mode = FALSE;
7198 return key;
7199 }
7200 }
7201 }
7203 static char *
7204 prompt_input(const char *prompt, input_handler handler, void *data)
7205 {
7206 enum input_status status = INPUT_OK;
7207 static char buf[SIZEOF_STR];
7208 size_t pos = 0;
7210 buf[pos] = 0;
7212 while (status == INPUT_OK || status == INPUT_SKIP) {
7213 int key;
7215 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7216 wclrtoeol(status_win);
7218 key = get_input(pos + 1);
7219 switch (key) {
7220 case KEY_RETURN:
7221 case KEY_ENTER:
7222 case '\n':
7223 status = pos ? INPUT_STOP : INPUT_CANCEL;
7224 break;
7226 case KEY_BACKSPACE:
7227 if (pos > 0)
7228 buf[--pos] = 0;
7229 else
7230 status = INPUT_CANCEL;
7231 break;
7233 case KEY_ESC:
7234 status = INPUT_CANCEL;
7235 break;
7237 default:
7238 if (pos >= sizeof(buf)) {
7239 report("Input string too long");
7240 return NULL;
7241 }
7243 status = handler(data, buf, key);
7244 if (status == INPUT_OK)
7245 buf[pos++] = (char) key;
7246 }
7247 }
7249 /* Clear the status window */
7250 status_empty = FALSE;
7251 report("");
7253 if (status == INPUT_CANCEL)
7254 return NULL;
7256 buf[pos++] = 0;
7258 return buf;
7259 }
7261 static enum input_status
7262 prompt_yesno_handler(void *data, char *buf, int c)
7263 {
7264 if (c == 'y' || c == 'Y')
7265 return INPUT_STOP;
7266 if (c == 'n' || c == 'N')
7267 return INPUT_CANCEL;
7268 return INPUT_SKIP;
7269 }
7271 static bool
7272 prompt_yesno(const char *prompt)
7273 {
7274 char prompt2[SIZEOF_STR];
7276 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7277 return FALSE;
7279 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7280 }
7282 static enum input_status
7283 read_prompt_handler(void *data, char *buf, int c)
7284 {
7285 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7286 }
7288 static char *
7289 read_prompt(const char *prompt)
7290 {
7291 return prompt_input(prompt, read_prompt_handler, NULL);
7292 }
7294 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7295 {
7296 enum input_status status = INPUT_OK;
7297 int size = 0;
7299 while (items[size].text)
7300 size++;
7302 while (status == INPUT_OK) {
7303 const struct menu_item *item = &items[*selected];
7304 int key;
7305 int i;
7307 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7308 prompt, *selected + 1, size);
7309 if (item->hotkey)
7310 wprintw(status_win, "[%c] ", (char) item->hotkey);
7311 wprintw(status_win, "%s", item->text);
7312 wclrtoeol(status_win);
7314 key = get_input(COLS - 1);
7315 switch (key) {
7316 case KEY_RETURN:
7317 case KEY_ENTER:
7318 case '\n':
7319 status = INPUT_STOP;
7320 break;
7322 case KEY_LEFT:
7323 case KEY_UP:
7324 *selected = *selected - 1;
7325 if (*selected < 0)
7326 *selected = size - 1;
7327 break;
7329 case KEY_RIGHT:
7330 case KEY_DOWN:
7331 *selected = (*selected + 1) % size;
7332 break;
7334 case KEY_ESC:
7335 status = INPUT_CANCEL;
7336 break;
7338 default:
7339 for (i = 0; items[i].text; i++)
7340 if (items[i].hotkey == key) {
7341 *selected = i;
7342 status = INPUT_STOP;
7343 break;
7344 }
7345 }
7346 }
7348 /* Clear the status window */
7349 status_empty = FALSE;
7350 report("");
7352 return status != INPUT_CANCEL;
7353 }
7355 /*
7356 * Repository properties
7357 */
7359 static struct ref **refs = NULL;
7360 static size_t refs_size = 0;
7361 static struct ref *refs_head = NULL;
7363 static struct ref_list **ref_lists = NULL;
7364 static size_t ref_lists_size = 0;
7366 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7367 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7368 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7370 static int
7371 compare_refs(const void *ref1_, const void *ref2_)
7372 {
7373 const struct ref *ref1 = *(const struct ref **)ref1_;
7374 const struct ref *ref2 = *(const struct ref **)ref2_;
7376 if (ref1->tag != ref2->tag)
7377 return ref2->tag - ref1->tag;
7378 if (ref1->ltag != ref2->ltag)
7379 return ref2->ltag - ref2->ltag;
7380 if (ref1->head != ref2->head)
7381 return ref2->head - ref1->head;
7382 if (ref1->tracked != ref2->tracked)
7383 return ref2->tracked - ref1->tracked;
7384 if (ref1->remote != ref2->remote)
7385 return ref2->remote - ref1->remote;
7386 return strcmp(ref1->name, ref2->name);
7387 }
7389 static void
7390 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7391 {
7392 size_t i;
7394 for (i = 0; i < refs_size; i++)
7395 if (!visitor(data, refs[i]))
7396 break;
7397 }
7399 static struct ref *
7400 get_ref_head()
7401 {
7402 return refs_head;
7403 }
7405 static struct ref_list *
7406 get_ref_list(const char *id)
7407 {
7408 struct ref_list *list;
7409 size_t i;
7411 for (i = 0; i < ref_lists_size; i++)
7412 if (!strcmp(id, ref_lists[i]->id))
7413 return ref_lists[i];
7415 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7416 return NULL;
7417 list = calloc(1, sizeof(*list));
7418 if (!list)
7419 return NULL;
7421 for (i = 0; i < refs_size; i++) {
7422 if (!strcmp(id, refs[i]->id) &&
7423 realloc_refs_list(&list->refs, list->size, 1))
7424 list->refs[list->size++] = refs[i];
7425 }
7427 if (!list->refs) {
7428 free(list);
7429 return NULL;
7430 }
7432 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7433 ref_lists[ref_lists_size++] = list;
7434 return list;
7435 }
7437 static int
7438 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7439 {
7440 struct ref *ref = NULL;
7441 bool tag = FALSE;
7442 bool ltag = FALSE;
7443 bool remote = FALSE;
7444 bool tracked = FALSE;
7445 bool head = FALSE;
7446 int from = 0, to = refs_size - 1;
7448 if (!prefixcmp(name, "refs/tags/")) {
7449 if (!suffixcmp(name, namelen, "^{}")) {
7450 namelen -= 3;
7451 name[namelen] = 0;
7452 } else {
7453 ltag = TRUE;
7454 }
7456 tag = TRUE;
7457 namelen -= STRING_SIZE("refs/tags/");
7458 name += STRING_SIZE("refs/tags/");
7460 } else if (!prefixcmp(name, "refs/remotes/")) {
7461 remote = TRUE;
7462 namelen -= STRING_SIZE("refs/remotes/");
7463 name += STRING_SIZE("refs/remotes/");
7464 tracked = !strcmp(opt_remote, name);
7466 } else if (!prefixcmp(name, "refs/heads/")) {
7467 namelen -= STRING_SIZE("refs/heads/");
7468 name += STRING_SIZE("refs/heads/");
7469 if (!strncmp(opt_head, name, namelen))
7470 return OK;
7472 } else if (!strcmp(name, "HEAD")) {
7473 head = TRUE;
7474 if (*opt_head) {
7475 namelen = strlen(opt_head);
7476 name = opt_head;
7477 }
7478 }
7480 /* If we are reloading or it's an annotated tag, replace the
7481 * previous SHA1 with the resolved commit id; relies on the fact
7482 * git-ls-remote lists the commit id of an annotated tag right
7483 * before the commit id it points to. */
7484 while (from <= to) {
7485 size_t pos = (to + from) / 2;
7486 int cmp = strcmp(name, refs[pos]->name);
7488 if (!cmp) {
7489 ref = refs[pos];
7490 break;
7491 }
7493 if (cmp < 0)
7494 to = pos - 1;
7495 else
7496 from = pos + 1;
7497 }
7499 if (!ref) {
7500 if (!realloc_refs(&refs, refs_size, 1))
7501 return ERR;
7502 ref = calloc(1, sizeof(*ref) + namelen);
7503 if (!ref)
7504 return ERR;
7505 memmove(refs + from + 1, refs + from,
7506 (refs_size - from) * sizeof(*refs));
7507 refs[from] = ref;
7508 strncpy(ref->name, name, namelen);
7509 refs_size++;
7510 }
7512 ref->head = head;
7513 ref->tag = tag;
7514 ref->ltag = ltag;
7515 ref->remote = remote;
7516 ref->tracked = tracked;
7517 string_copy_rev(ref->id, id);
7519 if (head)
7520 refs_head = ref;
7521 return OK;
7522 }
7524 static int
7525 load_refs(void)
7526 {
7527 const char *head_argv[] = {
7528 "git", "symbolic-ref", "HEAD", NULL
7529 };
7530 static const char *ls_remote_argv[SIZEOF_ARG] = {
7531 "git", "ls-remote", opt_git_dir, NULL
7532 };
7533 static bool init = FALSE;
7534 size_t i;
7536 if (!init) {
7537 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7538 die("TIG_LS_REMOTE contains too many arguments");
7539 init = TRUE;
7540 }
7542 if (!*opt_git_dir)
7543 return OK;
7545 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7546 !prefixcmp(opt_head, "refs/heads/")) {
7547 char *offset = opt_head + STRING_SIZE("refs/heads/");
7549 memmove(opt_head, offset, strlen(offset) + 1);
7550 }
7552 refs_head = NULL;
7553 for (i = 0; i < refs_size; i++)
7554 refs[i]->id[0] = 0;
7556 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7557 return ERR;
7559 /* Update the ref lists to reflect changes. */
7560 for (i = 0; i < ref_lists_size; i++) {
7561 struct ref_list *list = ref_lists[i];
7562 size_t old, new;
7564 for (old = new = 0; old < list->size; old++)
7565 if (!strcmp(list->id, list->refs[old]->id))
7566 list->refs[new++] = list->refs[old];
7567 list->size = new;
7568 }
7570 return OK;
7571 }
7573 static void
7574 set_remote_branch(const char *name, const char *value, size_t valuelen)
7575 {
7576 if (!strcmp(name, ".remote")) {
7577 string_ncopy(opt_remote, value, valuelen);
7579 } else if (*opt_remote && !strcmp(name, ".merge")) {
7580 size_t from = strlen(opt_remote);
7582 if (!prefixcmp(value, "refs/heads/"))
7583 value += STRING_SIZE("refs/heads/");
7585 if (!string_format_from(opt_remote, &from, "/%s", value))
7586 opt_remote[0] = 0;
7587 }
7588 }
7590 static void
7591 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7592 {
7593 const char *argv[SIZEOF_ARG] = { name, "=" };
7594 int argc = 1 + (cmd == option_set_command);
7595 int error = ERR;
7597 if (!argv_from_string(argv, &argc, value))
7598 config_msg = "Too many option arguments";
7599 else
7600 error = cmd(argc, argv);
7602 if (error == ERR)
7603 warn("Option 'tig.%s': %s", name, config_msg);
7604 }
7606 static bool
7607 set_environment_variable(const char *name, const char *value)
7608 {
7609 size_t len = strlen(name) + 1 + strlen(value) + 1;
7610 char *env = malloc(len);
7612 if (env &&
7613 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7614 putenv(env) == 0)
7615 return TRUE;
7616 free(env);
7617 return FALSE;
7618 }
7620 static void
7621 set_work_tree(const char *value)
7622 {
7623 char cwd[SIZEOF_STR];
7625 if (!getcwd(cwd, sizeof(cwd)))
7626 die("Failed to get cwd path: %s", strerror(errno));
7627 if (chdir(opt_git_dir) < 0)
7628 die("Failed to chdir(%s): %s", strerror(errno));
7629 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7630 die("Failed to get git path: %s", strerror(errno));
7631 if (chdir(cwd) < 0)
7632 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7633 if (chdir(value) < 0)
7634 die("Failed to chdir(%s): %s", value, strerror(errno));
7635 if (!getcwd(cwd, sizeof(cwd)))
7636 die("Failed to get cwd path: %s", strerror(errno));
7637 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7638 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7639 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7640 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7641 opt_is_inside_work_tree = TRUE;
7642 }
7644 static int
7645 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7646 {
7647 if (!strcmp(name, "i18n.commitencoding"))
7648 string_ncopy(opt_encoding, value, valuelen);
7650 else if (!strcmp(name, "core.editor"))
7651 string_ncopy(opt_editor, value, valuelen);
7653 else if (!strcmp(name, "core.worktree"))
7654 set_work_tree(value);
7656 else if (!prefixcmp(name, "tig.color."))
7657 set_repo_config_option(name + 10, value, option_color_command);
7659 else if (!prefixcmp(name, "tig.bind."))
7660 set_repo_config_option(name + 9, value, option_bind_command);
7662 else if (!prefixcmp(name, "tig."))
7663 set_repo_config_option(name + 4, value, option_set_command);
7665 else if (*opt_head && !prefixcmp(name, "branch.") &&
7666 !strncmp(name + 7, opt_head, strlen(opt_head)))
7667 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7669 return OK;
7670 }
7672 static int
7673 load_git_config(void)
7674 {
7675 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7677 return io_run_load(config_list_argv, "=", read_repo_config_option);
7678 }
7680 static int
7681 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7682 {
7683 if (!opt_git_dir[0]) {
7684 string_ncopy(opt_git_dir, name, namelen);
7686 } else if (opt_is_inside_work_tree == -1) {
7687 /* This can be 3 different values depending on the
7688 * version of git being used. If git-rev-parse does not
7689 * understand --is-inside-work-tree it will simply echo
7690 * the option else either "true" or "false" is printed.
7691 * Default to true for the unknown case. */
7692 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7694 } else if (*name == '.') {
7695 string_ncopy(opt_cdup, name, namelen);
7697 } else {
7698 string_ncopy(opt_prefix, name, namelen);
7699 }
7701 return OK;
7702 }
7704 static int
7705 load_repo_info(void)
7706 {
7707 const char *rev_parse_argv[] = {
7708 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7709 "--show-cdup", "--show-prefix", NULL
7710 };
7712 return io_run_load(rev_parse_argv, "=", read_repo_info);
7713 }
7716 /*
7717 * Main
7718 */
7720 static const char usage[] =
7721 "tig " TIG_VERSION " (" __DATE__ ")\n"
7722 "\n"
7723 "Usage: tig [options] [revs] [--] [paths]\n"
7724 " or: tig show [options] [revs] [--] [paths]\n"
7725 " or: tig blame [rev] path\n"
7726 " or: tig status\n"
7727 " or: tig < [git command output]\n"
7728 "\n"
7729 "Options:\n"
7730 " -v, --version Show version and exit\n"
7731 " -h, --help Show help message and exit";
7733 static void __NORETURN
7734 quit(int sig)
7735 {
7736 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7737 if (cursed)
7738 endwin();
7739 exit(0);
7740 }
7742 static void __NORETURN
7743 die(const char *err, ...)
7744 {
7745 va_list args;
7747 endwin();
7749 va_start(args, err);
7750 fputs("tig: ", stderr);
7751 vfprintf(stderr, err, args);
7752 fputs("\n", stderr);
7753 va_end(args);
7755 exit(1);
7756 }
7758 static void
7759 warn(const char *msg, ...)
7760 {
7761 va_list args;
7763 va_start(args, msg);
7764 fputs("tig warning: ", stderr);
7765 vfprintf(stderr, msg, args);
7766 fputs("\n", stderr);
7767 va_end(args);
7768 }
7770 static const char ***filter_args;
7772 static int
7773 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7774 {
7775 return argv_append(filter_args, name) ? OK : ERR;
7776 }
7778 static void
7779 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7780 {
7781 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7782 const char **all_argv = NULL;
7784 filter_args = args;
7785 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7786 !argv_append_array(&all_argv, argv) ||
7787 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7788 die("Failed to split arguments");
7789 argv_free(all_argv);
7790 free(all_argv);
7791 }
7793 static void
7794 filter_options(const char *argv[])
7795 {
7796 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7797 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7798 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7799 }
7801 static enum request
7802 parse_options(int argc, const char *argv[])
7803 {
7804 enum request request = REQ_VIEW_MAIN;
7805 const char *subcommand;
7806 bool seen_dashdash = FALSE;
7807 const char **filter_argv = NULL;
7808 int i;
7810 if (!isatty(STDIN_FILENO))
7811 return REQ_VIEW_PAGER;
7813 if (argc <= 1)
7814 return REQ_VIEW_MAIN;
7816 subcommand = argv[1];
7817 if (!strcmp(subcommand, "status")) {
7818 if (argc > 2)
7819 warn("ignoring arguments after `%s'", subcommand);
7820 return REQ_VIEW_STATUS;
7822 } else if (!strcmp(subcommand, "blame")) {
7823 if (argc <= 2 || argc > 4)
7824 die("invalid number of options to blame\n\n%s", usage);
7826 i = 2;
7827 if (argc == 4) {
7828 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7829 i++;
7830 }
7832 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7833 return REQ_VIEW_BLAME;
7835 } else if (!strcmp(subcommand, "show")) {
7836 request = REQ_VIEW_DIFF;
7838 } else {
7839 subcommand = NULL;
7840 }
7842 for (i = 1 + !!subcommand; i < argc; i++) {
7843 const char *opt = argv[i];
7845 if (seen_dashdash) {
7846 argv_append(&opt_file_args, opt);
7847 continue;
7849 } else if (!strcmp(opt, "--")) {
7850 seen_dashdash = TRUE;
7851 continue;
7853 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7854 printf("tig version %s\n", TIG_VERSION);
7855 quit(0);
7857 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7858 printf("%s\n", usage);
7859 quit(0);
7861 } else if (!strcmp(opt, "--all")) {
7862 argv_append(&opt_rev_args, opt);
7863 continue;
7864 }
7866 if (!argv_append(&filter_argv, opt))
7867 die("command too long");
7868 }
7870 if (filter_argv)
7871 filter_options(filter_argv);
7873 return request;
7874 }
7876 int
7877 main(int argc, const char *argv[])
7878 {
7879 const char *codeset = "UTF-8";
7880 enum request request = parse_options(argc, argv);
7881 struct view *view;
7882 size_t i;
7884 signal(SIGINT, quit);
7885 signal(SIGPIPE, SIG_IGN);
7887 if (setlocale(LC_ALL, "")) {
7888 codeset = nl_langinfo(CODESET);
7889 }
7891 if (load_repo_info() == ERR)
7892 die("Failed to load repo info.");
7894 if (load_options() == ERR)
7895 die("Failed to load user config.");
7897 if (load_git_config() == ERR)
7898 die("Failed to load repo config.");
7900 /* Require a git repository unless when running in pager mode. */
7901 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7902 die("Not a git repository");
7904 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7905 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7906 if (opt_iconv_in == ICONV_NONE)
7907 die("Failed to initialize character set conversion");
7908 }
7910 if (codeset && strcmp(codeset, "UTF-8")) {
7911 opt_iconv_out = iconv_open(codeset, "UTF-8");
7912 if (opt_iconv_out == ICONV_NONE)
7913 die("Failed to initialize character set conversion");
7914 }
7916 if (load_refs() == ERR)
7917 die("Failed to load refs.");
7919 foreach_view (view, i) {
7920 if (getenv(view->cmd_env))
7921 warn("Use of the %s environment variable is deprecated,"
7922 " use options or TIG_DIFF_ARGS instead",
7923 view->cmd_env);
7924 if (!argv_from_env(view->ops->argv, view->cmd_env))
7925 die("Too many arguments in the `%s` environment variable",
7926 view->cmd_env);
7927 }
7929 init_display();
7931 while (view_driver(display[current_view], request)) {
7932 int key = get_input(0);
7934 view = display[current_view];
7935 request = get_keybinding(view->keymap, key);
7937 /* Some low-level request handling. This keeps access to
7938 * status_win restricted. */
7939 switch (request) {
7940 case REQ_NONE:
7941 report("Unknown key, press %s for help",
7942 get_key(view->keymap, REQ_VIEW_HELP));
7943 break;
7944 case REQ_PROMPT:
7945 {
7946 char *cmd = read_prompt(":");
7948 if (cmd && isdigit(*cmd)) {
7949 int lineno = view->lineno + 1;
7951 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7952 select_view_line(view, lineno - 1);
7953 report("");
7954 } else {
7955 report("Unable to parse '%s' as a line number", cmd);
7956 }
7958 } else if (cmd) {
7959 struct view *next = VIEW(REQ_VIEW_PAGER);
7960 const char *argv[SIZEOF_ARG] = { "git" };
7961 int argc = 1;
7963 /* When running random commands, initially show the
7964 * command in the title. However, it maybe later be
7965 * overwritten if a commit line is selected. */
7966 string_ncopy(next->ref, cmd, strlen(cmd));
7968 if (!argv_from_string(argv, &argc, cmd)) {
7969 report("Too many arguments");
7970 } else if (!prepare_update(next, argv, NULL)) {
7971 report("Failed to format command");
7972 } else {
7973 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7974 }
7975 }
7977 request = REQ_NONE;
7978 break;
7979 }
7980 case REQ_SEARCH:
7981 case REQ_SEARCH_BACK:
7982 {
7983 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7984 char *search = read_prompt(prompt);
7986 if (search)
7987 string_ncopy(opt_search, search, strlen(search));
7988 else if (*opt_search)
7989 request = request == REQ_SEARCH ?
7990 REQ_FIND_NEXT :
7991 REQ_FIND_PREV;
7992 else
7993 request = REQ_NONE;
7994 break;
7995 }
7996 default:
7997 break;
7998 }
7999 }
8001 quit(0);
8003 return 0;
8004 }