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_DATE_SHORT, "Toggle short (date-only) dates"), \
1158 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1159 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1160 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1161 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1162 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1163 \
1164 REQ_GROUP("Misc") \
1165 REQ_(PROMPT, "Bring up the prompt"), \
1166 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1167 REQ_(SHOW_VERSION, "Show version information"), \
1168 REQ_(STOP_LOADING, "Stop all loading views"), \
1169 REQ_(EDIT, "Open in editor"), \
1170 REQ_(NONE, "Do nothing")
1173 /* User action requests. */
1174 enum request {
1175 #define REQ_GROUP(help)
1176 #define REQ_(req, help) REQ_##req
1178 /* Offset all requests to avoid conflicts with ncurses getch values. */
1179 REQ_UNKNOWN = KEY_MAX + 1,
1180 REQ_OFFSET,
1181 REQ_INFO
1183 #undef REQ_GROUP
1184 #undef REQ_
1185 };
1187 struct request_info {
1188 enum request request;
1189 const char *name;
1190 int namelen;
1191 const char *help;
1192 };
1194 static const struct request_info req_info[] = {
1195 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1196 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1197 REQ_INFO
1198 #undef REQ_GROUP
1199 #undef REQ_
1200 };
1202 static enum request
1203 get_request(const char *name)
1204 {
1205 int namelen = strlen(name);
1206 int i;
1208 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1209 if (enum_equals(req_info[i], name, namelen))
1210 return req_info[i].request;
1212 return REQ_UNKNOWN;
1213 }
1216 /*
1217 * Options
1218 */
1220 /* Option and state variables. */
1221 static enum date opt_date = DATE_DEFAULT;
1222 static enum author opt_author = AUTHOR_DEFAULT;
1223 static bool opt_line_number = FALSE;
1224 static bool opt_line_graphics = TRUE;
1225 static bool opt_rev_graph = FALSE;
1226 static bool opt_show_refs = 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 config_msg = "Unknown variable name";
2022 return ERR;
2023 }
2025 /* Wants: mode request key */
2026 static int
2027 option_bind_command(int argc, const char *argv[])
2028 {
2029 enum request request;
2030 int keymap = -1;
2031 int key;
2033 if (argc < 3) {
2034 config_msg = "Wrong number of arguments given to bind command";
2035 return ERR;
2036 }
2038 if (!set_keymap(&keymap, argv[0])) {
2039 config_msg = "Unknown key map";
2040 return ERR;
2041 }
2043 key = get_key_value(argv[1]);
2044 if (key == ERR) {
2045 config_msg = "Unknown key";
2046 return ERR;
2047 }
2049 request = get_request(argv[2]);
2050 if (request == REQ_UNKNOWN) {
2051 static const struct enum_map obsolete[] = {
2052 ENUM_MAP("cherry-pick", REQ_NONE),
2053 ENUM_MAP("screen-resize", REQ_NONE),
2054 ENUM_MAP("tree-parent", REQ_PARENT),
2055 };
2056 int alias;
2058 if (map_enum(&alias, obsolete, argv[2])) {
2059 if (alias != REQ_NONE)
2060 add_keybinding(keymap, alias, key);
2061 config_msg = "Obsolete request name";
2062 return ERR;
2063 }
2064 }
2065 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2066 request = add_run_request(keymap, key, argv + 2);
2067 if (request == REQ_UNKNOWN) {
2068 config_msg = "Unknown request name";
2069 return ERR;
2070 }
2072 add_keybinding(keymap, request, key);
2074 return OK;
2075 }
2077 static int
2078 set_option(const char *opt, char *value)
2079 {
2080 const char *argv[SIZEOF_ARG];
2081 int argc = 0;
2083 if (!argv_from_string(argv, &argc, value)) {
2084 config_msg = "Too many option arguments";
2085 return ERR;
2086 }
2088 if (!strcmp(opt, "color"))
2089 return option_color_command(argc, argv);
2091 if (!strcmp(opt, "set"))
2092 return option_set_command(argc, argv);
2094 if (!strcmp(opt, "bind"))
2095 return option_bind_command(argc, argv);
2097 config_msg = "Unknown option command";
2098 return ERR;
2099 }
2101 static int
2102 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2103 {
2104 int status = OK;
2106 config_lineno++;
2107 config_msg = "Internal error";
2109 /* Check for comment markers, since read_properties() will
2110 * only ensure opt and value are split at first " \t". */
2111 optlen = strcspn(opt, "#");
2112 if (optlen == 0)
2113 return OK;
2115 if (opt[optlen] != 0) {
2116 config_msg = "No option value";
2117 status = ERR;
2119 } else {
2120 /* Look for comment endings in the value. */
2121 size_t len = strcspn(value, "#");
2123 if (len < valuelen) {
2124 valuelen = len;
2125 value[valuelen] = 0;
2126 }
2128 status = set_option(opt, value);
2129 }
2131 if (status == ERR) {
2132 warn("Error on line %d, near '%.*s': %s",
2133 config_lineno, (int) optlen, opt, config_msg);
2134 config_errors = TRUE;
2135 }
2137 /* Always keep going if errors are encountered. */
2138 return OK;
2139 }
2141 static void
2142 load_option_file(const char *path)
2143 {
2144 struct io io;
2146 /* It's OK that the file doesn't exist. */
2147 if (!io_open(&io, "%s", path))
2148 return;
2150 config_lineno = 0;
2151 config_errors = FALSE;
2153 if (io_load(&io, " \t", read_option) == ERR ||
2154 config_errors == TRUE)
2155 warn("Errors while loading %s.", path);
2156 }
2158 static int
2159 load_options(void)
2160 {
2161 const char *home = getenv("HOME");
2162 const char *tigrc_user = getenv("TIGRC_USER");
2163 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2164 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2165 char buf[SIZEOF_STR];
2167 if (!tigrc_system)
2168 tigrc_system = SYSCONFDIR "/tigrc";
2169 load_option_file(tigrc_system);
2171 if (!tigrc_user) {
2172 if (!home || !string_format(buf, "%s/.tigrc", home))
2173 return ERR;
2174 tigrc_user = buf;
2175 }
2176 load_option_file(tigrc_user);
2178 /* Add _after_ loading config files to avoid adding run requests
2179 * that conflict with keybindings. */
2180 add_builtin_run_requests();
2182 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2183 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2184 int argc = 0;
2186 if (!string_format(buf, "%s", tig_diff_opts) ||
2187 !argv_from_string(diff_opts, &argc, buf))
2188 die("TIG_DIFF_OPTS contains too many arguments");
2189 else if (!argv_copy(&opt_diff_args, diff_opts))
2190 die("Failed to format TIG_DIFF_OPTS arguments");
2191 }
2193 return OK;
2194 }
2197 /*
2198 * The viewer
2199 */
2201 struct view;
2202 struct view_ops;
2204 /* The display array of active views and the index of the current view. */
2205 static struct view *display[2];
2206 static unsigned int current_view;
2208 #define foreach_displayed_view(view, i) \
2209 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2211 #define displayed_views() (display[1] != NULL ? 2 : 1)
2213 /* Current head and commit ID */
2214 static char ref_blob[SIZEOF_REF] = "";
2215 static char ref_commit[SIZEOF_REF] = "HEAD";
2216 static char ref_head[SIZEOF_REF] = "HEAD";
2217 static char ref_branch[SIZEOF_REF] = "";
2219 enum view_type {
2220 VIEW_MAIN,
2221 VIEW_DIFF,
2222 VIEW_LOG,
2223 VIEW_TREE,
2224 VIEW_BLOB,
2225 VIEW_BLAME,
2226 VIEW_BRANCH,
2227 VIEW_HELP,
2228 VIEW_PAGER,
2229 VIEW_STATUS,
2230 VIEW_STAGE,
2231 };
2233 struct view {
2234 enum view_type type; /* View type */
2235 const char *name; /* View name */
2236 const char *cmd_env; /* Command line set via environment */
2237 const char *id; /* Points to either of ref_{head,commit,blob} */
2239 struct view_ops *ops; /* View operations */
2241 enum keymap keymap; /* What keymap does this view have */
2242 bool git_dir; /* Whether the view requires a git directory. */
2244 char ref[SIZEOF_REF]; /* Hovered commit reference */
2245 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2247 int height, width; /* The width and height of the main window */
2248 WINDOW *win; /* The main window */
2249 WINDOW *title; /* The title window living below the main window */
2251 /* Navigation */
2252 unsigned long offset; /* Offset of the window top */
2253 unsigned long yoffset; /* Offset from the window side. */
2254 unsigned long lineno; /* Current line number */
2255 unsigned long p_offset; /* Previous offset of the window top */
2256 unsigned long p_yoffset;/* Previous offset from the window side */
2257 unsigned long p_lineno; /* Previous current line number */
2258 bool p_restore; /* Should the previous position be restored. */
2260 /* Searching */
2261 char grep[SIZEOF_STR]; /* Search string */
2262 regex_t *regex; /* Pre-compiled regexp */
2264 /* If non-NULL, points to the view that opened this view. If this view
2265 * is closed tig will switch back to the parent view. */
2266 struct view *parent;
2267 struct view *prev;
2269 /* Buffering */
2270 size_t lines; /* Total number of lines */
2271 struct line *line; /* Line index */
2272 unsigned int digits; /* Number of digits in the lines member. */
2274 /* Drawing */
2275 struct line *curline; /* Line currently being drawn. */
2276 enum line_type curtype; /* Attribute currently used for drawing. */
2277 unsigned long col; /* Column when drawing. */
2278 bool has_scrolled; /* View was scrolled. */
2280 /* Loading */
2281 const char **argv; /* Shell command arguments. */
2282 const char *dir; /* Directory from which to execute. */
2283 struct io io;
2284 struct io *pipe;
2285 time_t start_time;
2286 time_t update_secs;
2287 };
2289 struct view_ops {
2290 /* What type of content being displayed. Used in the title bar. */
2291 const char *type;
2292 /* Default command arguments. */
2293 const char **argv;
2294 /* Open and reads in all view content. */
2295 bool (*open)(struct view *view);
2296 /* Read one line; updates view->line. */
2297 bool (*read)(struct view *view, char *data);
2298 /* Draw one line; @lineno must be < view->height. */
2299 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2300 /* Depending on view handle a special requests. */
2301 enum request (*request)(struct view *view, enum request request, struct line *line);
2302 /* Search for regexp in a line. */
2303 bool (*grep)(struct view *view, struct line *line);
2304 /* Select line */
2305 void (*select)(struct view *view, struct line *line);
2306 /* Prepare view for loading */
2307 bool (*prepare)(struct view *view);
2308 };
2310 static struct view_ops blame_ops;
2311 static struct view_ops blob_ops;
2312 static struct view_ops diff_ops;
2313 static struct view_ops help_ops;
2314 static struct view_ops log_ops;
2315 static struct view_ops main_ops;
2316 static struct view_ops pager_ops;
2317 static struct view_ops stage_ops;
2318 static struct view_ops status_ops;
2319 static struct view_ops tree_ops;
2320 static struct view_ops branch_ops;
2322 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2323 { type, name, #env, ref, ops, map, git }
2325 #define VIEW_(id, name, ops, git, ref) \
2326 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2328 static struct view views[] = {
2329 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2330 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2331 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2332 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2333 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2334 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2335 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2336 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2337 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2338 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2339 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2340 };
2342 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2344 #define foreach_view(view, i) \
2345 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2347 #define view_is_displayed(view) \
2348 (view == display[0] || view == display[1])
2350 static enum request
2351 view_request(struct view *view, enum request request)
2352 {
2353 if (!view || !view->lines)
2354 return request;
2355 return view->ops->request(view, request, &view->line[view->lineno]);
2356 }
2359 /*
2360 * View drawing.
2361 */
2363 static inline void
2364 set_view_attr(struct view *view, enum line_type type)
2365 {
2366 if (!view->curline->selected && view->curtype != type) {
2367 (void) wattrset(view->win, get_line_attr(type));
2368 wchgat(view->win, -1, 0, type, NULL);
2369 view->curtype = type;
2370 }
2371 }
2373 static int
2374 draw_chars(struct view *view, enum line_type type, const char *string,
2375 int max_len, bool use_tilde)
2376 {
2377 static char out_buffer[BUFSIZ * 2];
2378 int len = 0;
2379 int col = 0;
2380 int trimmed = FALSE;
2381 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2383 if (max_len <= 0)
2384 return 0;
2386 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2388 set_view_attr(view, type);
2389 if (len > 0) {
2390 if (opt_iconv_out != ICONV_NONE) {
2391 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2392 size_t inlen = len + 1;
2394 char *outbuf = out_buffer;
2395 size_t outlen = sizeof(out_buffer);
2397 size_t ret;
2399 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2400 if (ret != (size_t) -1) {
2401 string = out_buffer;
2402 len = sizeof(out_buffer) - outlen;
2403 }
2404 }
2406 waddnstr(view->win, string, len);
2407 }
2408 if (trimmed && use_tilde) {
2409 set_view_attr(view, LINE_DELIMITER);
2410 waddch(view->win, '~');
2411 col++;
2412 }
2414 return col;
2415 }
2417 static int
2418 draw_space(struct view *view, enum line_type type, int max, int spaces)
2419 {
2420 static char space[] = " ";
2421 int col = 0;
2423 spaces = MIN(max, spaces);
2425 while (spaces > 0) {
2426 int len = MIN(spaces, sizeof(space) - 1);
2428 col += draw_chars(view, type, space, len, FALSE);
2429 spaces -= len;
2430 }
2432 return col;
2433 }
2435 static bool
2436 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2437 {
2438 char text[SIZEOF_STR];
2440 do {
2441 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2443 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2444 string += pos;
2445 } while (*string && view->width + view->yoffset > view->col);
2447 return view->width + view->yoffset <= view->col;
2448 }
2450 static bool
2451 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2452 {
2453 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2454 int max = view->width + view->yoffset - view->col;
2455 int i;
2457 if (max < size)
2458 size = max;
2460 set_view_attr(view, type);
2461 /* Using waddch() instead of waddnstr() ensures that
2462 * they'll be rendered correctly for the cursor line. */
2463 for (i = skip; i < size; i++)
2464 waddch(view->win, graphic[i]);
2466 view->col += size;
2467 if (size < max && skip <= size)
2468 waddch(view->win, ' ');
2469 view->col++;
2471 return view->width + view->yoffset <= view->col;
2472 }
2474 static bool
2475 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2476 {
2477 int max = MIN(view->width + view->yoffset - view->col, len);
2478 int col;
2480 if (text)
2481 col = draw_chars(view, type, text, max - 1, trim);
2482 else
2483 col = draw_space(view, type, max - 1, max - 1);
2485 view->col += col;
2486 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2487 return view->width + view->yoffset <= view->col;
2488 }
2490 static bool
2491 draw_date(struct view *view, struct time *time)
2492 {
2493 const char *date = mkdate(time, opt_date);
2494 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2496 return draw_field(view, LINE_DATE, date, cols, FALSE);
2497 }
2499 static bool
2500 draw_author(struct view *view, const char *author)
2501 {
2502 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2503 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2505 if (abbreviate && author)
2506 author = get_author_initials(author);
2508 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2509 }
2511 static bool
2512 draw_mode(struct view *view, mode_t mode)
2513 {
2514 const char *str;
2516 if (S_ISDIR(mode))
2517 str = "drwxr-xr-x";
2518 else if (S_ISLNK(mode))
2519 str = "lrwxrwxrwx";
2520 else if (S_ISGITLINK(mode))
2521 str = "m---------";
2522 else if (S_ISREG(mode) && mode & S_IXUSR)
2523 str = "-rwxr-xr-x";
2524 else if (S_ISREG(mode))
2525 str = "-rw-r--r--";
2526 else
2527 str = "----------";
2529 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2530 }
2532 static bool
2533 draw_lineno(struct view *view, unsigned int lineno)
2534 {
2535 char number[10];
2536 int digits3 = view->digits < 3 ? 3 : view->digits;
2537 int max = MIN(view->width + view->yoffset - view->col, digits3);
2538 char *text = NULL;
2539 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2541 lineno += view->offset + 1;
2542 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2543 static char fmt[] = "%1ld";
2545 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2546 if (string_format(number, fmt, lineno))
2547 text = number;
2548 }
2549 if (text)
2550 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2551 else
2552 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2553 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2554 }
2556 static bool
2557 draw_view_line(struct view *view, unsigned int lineno)
2558 {
2559 struct line *line;
2560 bool selected = (view->offset + lineno == view->lineno);
2562 assert(view_is_displayed(view));
2564 if (view->offset + lineno >= view->lines)
2565 return FALSE;
2567 line = &view->line[view->offset + lineno];
2569 wmove(view->win, lineno, 0);
2570 if (line->cleareol)
2571 wclrtoeol(view->win);
2572 view->col = 0;
2573 view->curline = line;
2574 view->curtype = LINE_NONE;
2575 line->selected = FALSE;
2576 line->dirty = line->cleareol = 0;
2578 if (selected) {
2579 set_view_attr(view, LINE_CURSOR);
2580 line->selected = TRUE;
2581 view->ops->select(view, line);
2582 }
2584 return view->ops->draw(view, line, lineno);
2585 }
2587 static void
2588 redraw_view_dirty(struct view *view)
2589 {
2590 bool dirty = FALSE;
2591 int lineno;
2593 for (lineno = 0; lineno < view->height; lineno++) {
2594 if (view->offset + lineno >= view->lines)
2595 break;
2596 if (!view->line[view->offset + lineno].dirty)
2597 continue;
2598 dirty = TRUE;
2599 if (!draw_view_line(view, lineno))
2600 break;
2601 }
2603 if (!dirty)
2604 return;
2605 wnoutrefresh(view->win);
2606 }
2608 static void
2609 redraw_view_from(struct view *view, int lineno)
2610 {
2611 assert(0 <= lineno && lineno < view->height);
2613 for (; lineno < view->height; lineno++) {
2614 if (!draw_view_line(view, lineno))
2615 break;
2616 }
2618 wnoutrefresh(view->win);
2619 }
2621 static void
2622 redraw_view(struct view *view)
2623 {
2624 werase(view->win);
2625 redraw_view_from(view, 0);
2626 }
2629 static void
2630 update_view_title(struct view *view)
2631 {
2632 char buf[SIZEOF_STR];
2633 char state[SIZEOF_STR];
2634 size_t bufpos = 0, statelen = 0;
2636 assert(view_is_displayed(view));
2638 if (view->type != VIEW_STATUS && view->lines) {
2639 unsigned int view_lines = view->offset + view->height;
2640 unsigned int lines = view->lines
2641 ? MIN(view_lines, view->lines) * 100 / view->lines
2642 : 0;
2644 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2645 view->ops->type,
2646 view->lineno + 1,
2647 view->lines,
2648 lines);
2650 }
2652 if (view->pipe) {
2653 time_t secs = time(NULL) - view->start_time;
2655 /* Three git seconds are a long time ... */
2656 if (secs > 2)
2657 string_format_from(state, &statelen, " loading %lds", secs);
2658 }
2660 string_format_from(buf, &bufpos, "[%s]", view->name);
2661 if (*view->ref && bufpos < view->width) {
2662 size_t refsize = strlen(view->ref);
2663 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2665 if (minsize < view->width)
2666 refsize = view->width - minsize + 7;
2667 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2668 }
2670 if (statelen && bufpos < view->width) {
2671 string_format_from(buf, &bufpos, "%s", state);
2672 }
2674 if (view == display[current_view])
2675 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2676 else
2677 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2679 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2680 wclrtoeol(view->title);
2681 wnoutrefresh(view->title);
2682 }
2684 static int
2685 apply_step(double step, int value)
2686 {
2687 if (step >= 1)
2688 return (int) step;
2689 value *= step + 0.01;
2690 return value ? value : 1;
2691 }
2693 static void
2694 resize_display(void)
2695 {
2696 int offset, i;
2697 struct view *base = display[0];
2698 struct view *view = display[1] ? display[1] : display[0];
2700 /* Setup window dimensions */
2702 getmaxyx(stdscr, base->height, base->width);
2704 /* Make room for the status window. */
2705 base->height -= 1;
2707 if (view != base) {
2708 /* Horizontal split. */
2709 view->width = base->width;
2710 view->height = apply_step(opt_scale_split_view, base->height);
2711 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2712 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2713 base->height -= view->height;
2715 /* Make room for the title bar. */
2716 view->height -= 1;
2717 }
2719 /* Make room for the title bar. */
2720 base->height -= 1;
2722 offset = 0;
2724 foreach_displayed_view (view, i) {
2725 if (!view->win) {
2726 view->win = newwin(view->height, 0, offset, 0);
2727 if (!view->win)
2728 die("Failed to create %s view", view->name);
2730 scrollok(view->win, FALSE);
2732 view->title = newwin(1, 0, offset + view->height, 0);
2733 if (!view->title)
2734 die("Failed to create title window");
2736 } else {
2737 wresize(view->win, view->height, view->width);
2738 mvwin(view->win, offset, 0);
2739 mvwin(view->title, offset + view->height, 0);
2740 }
2742 offset += view->height + 1;
2743 }
2744 }
2746 static void
2747 redraw_display(bool clear)
2748 {
2749 struct view *view;
2750 int i;
2752 foreach_displayed_view (view, i) {
2753 if (clear)
2754 wclear(view->win);
2755 redraw_view(view);
2756 update_view_title(view);
2757 }
2758 }
2761 /*
2762 * Option management
2763 */
2765 static void
2766 toggle_enum_option_do(unsigned int *opt, const char *help,
2767 const struct enum_map *map, size_t size)
2768 {
2769 *opt = (*opt + 1) % size;
2770 redraw_display(FALSE);
2771 report("Displaying %s %s", enum_name(map[*opt]), help);
2772 }
2774 #define toggle_enum_option(opt, help, map) \
2775 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2777 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2778 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2780 static void
2781 toggle_view_option(bool *option, const char *help)
2782 {
2783 *option = !*option;
2784 redraw_display(FALSE);
2785 report("%sabling %s", *option ? "En" : "Dis", help);
2786 }
2788 static void
2789 open_option_menu(void)
2790 {
2791 const struct menu_item menu[] = {
2792 { '.', "line numbers", &opt_line_number },
2793 { 'D', "date display", &opt_date },
2794 { 'A', "author display", &opt_author },
2795 { 'g', "revision graph display", &opt_rev_graph },
2796 { 'F', "reference display", &opt_show_refs },
2797 { 0 }
2798 };
2799 int selected = 0;
2801 if (prompt_menu("Toggle option", menu, &selected)) {
2802 if (menu[selected].data == &opt_date)
2803 toggle_date();
2804 else if (menu[selected].data == &opt_author)
2805 toggle_author();
2806 else
2807 toggle_view_option(menu[selected].data, menu[selected].text);
2808 }
2809 }
2811 static void
2812 maximize_view(struct view *view)
2813 {
2814 memset(display, 0, sizeof(display));
2815 current_view = 0;
2816 display[current_view] = view;
2817 resize_display();
2818 redraw_display(FALSE);
2819 report("");
2820 }
2823 /*
2824 * Navigation
2825 */
2827 static bool
2828 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2829 {
2830 if (lineno >= view->lines)
2831 lineno = view->lines > 0 ? view->lines - 1 : 0;
2833 if (offset > lineno || offset + view->height <= lineno) {
2834 unsigned long half = view->height / 2;
2836 if (lineno > half)
2837 offset = lineno - half;
2838 else
2839 offset = 0;
2840 }
2842 if (offset != view->offset || lineno != view->lineno) {
2843 view->offset = offset;
2844 view->lineno = lineno;
2845 return TRUE;
2846 }
2848 return FALSE;
2849 }
2851 /* Scrolling backend */
2852 static void
2853 do_scroll_view(struct view *view, int lines)
2854 {
2855 bool redraw_current_line = FALSE;
2857 /* The rendering expects the new offset. */
2858 view->offset += lines;
2860 assert(0 <= view->offset && view->offset < view->lines);
2861 assert(lines);
2863 /* Move current line into the view. */
2864 if (view->lineno < view->offset) {
2865 view->lineno = view->offset;
2866 redraw_current_line = TRUE;
2867 } else if (view->lineno >= view->offset + view->height) {
2868 view->lineno = view->offset + view->height - 1;
2869 redraw_current_line = TRUE;
2870 }
2872 assert(view->offset <= view->lineno && view->lineno < view->lines);
2874 /* Redraw the whole screen if scrolling is pointless. */
2875 if (view->height < ABS(lines)) {
2876 redraw_view(view);
2878 } else {
2879 int line = lines > 0 ? view->height - lines : 0;
2880 int end = line + ABS(lines);
2882 scrollok(view->win, TRUE);
2883 wscrl(view->win, lines);
2884 scrollok(view->win, FALSE);
2886 while (line < end && draw_view_line(view, line))
2887 line++;
2889 if (redraw_current_line)
2890 draw_view_line(view, view->lineno - view->offset);
2891 wnoutrefresh(view->win);
2892 }
2894 view->has_scrolled = TRUE;
2895 report("");
2896 }
2898 /* Scroll frontend */
2899 static void
2900 scroll_view(struct view *view, enum request request)
2901 {
2902 int lines = 1;
2904 assert(view_is_displayed(view));
2906 switch (request) {
2907 case REQ_SCROLL_FIRST_COL:
2908 view->yoffset = 0;
2909 redraw_view_from(view, 0);
2910 report("");
2911 return;
2912 case REQ_SCROLL_LEFT:
2913 if (view->yoffset == 0) {
2914 report("Cannot scroll beyond the first column");
2915 return;
2916 }
2917 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2918 view->yoffset = 0;
2919 else
2920 view->yoffset -= apply_step(opt_hscroll, view->width);
2921 redraw_view_from(view, 0);
2922 report("");
2923 return;
2924 case REQ_SCROLL_RIGHT:
2925 view->yoffset += apply_step(opt_hscroll, view->width);
2926 redraw_view(view);
2927 report("");
2928 return;
2929 case REQ_SCROLL_PAGE_DOWN:
2930 lines = view->height;
2931 case REQ_SCROLL_LINE_DOWN:
2932 if (view->offset + lines > view->lines)
2933 lines = view->lines - view->offset;
2935 if (lines == 0 || view->offset + view->height >= view->lines) {
2936 report("Cannot scroll beyond the last line");
2937 return;
2938 }
2939 break;
2941 case REQ_SCROLL_PAGE_UP:
2942 lines = view->height;
2943 case REQ_SCROLL_LINE_UP:
2944 if (lines > view->offset)
2945 lines = view->offset;
2947 if (lines == 0) {
2948 report("Cannot scroll beyond the first line");
2949 return;
2950 }
2952 lines = -lines;
2953 break;
2955 default:
2956 die("request %d not handled in switch", request);
2957 }
2959 do_scroll_view(view, lines);
2960 }
2962 /* Cursor moving */
2963 static void
2964 move_view(struct view *view, enum request request)
2965 {
2966 int scroll_steps = 0;
2967 int steps;
2969 switch (request) {
2970 case REQ_MOVE_FIRST_LINE:
2971 steps = -view->lineno;
2972 break;
2974 case REQ_MOVE_LAST_LINE:
2975 steps = view->lines - view->lineno - 1;
2976 break;
2978 case REQ_MOVE_PAGE_UP:
2979 steps = view->height > view->lineno
2980 ? -view->lineno : -view->height;
2981 break;
2983 case REQ_MOVE_PAGE_DOWN:
2984 steps = view->lineno + view->height >= view->lines
2985 ? view->lines - view->lineno - 1 : view->height;
2986 break;
2988 case REQ_MOVE_UP:
2989 steps = -1;
2990 break;
2992 case REQ_MOVE_DOWN:
2993 steps = 1;
2994 break;
2996 default:
2997 die("request %d not handled in switch", request);
2998 }
3000 if (steps <= 0 && view->lineno == 0) {
3001 report("Cannot move beyond the first line");
3002 return;
3004 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3005 report("Cannot move beyond the last line");
3006 return;
3007 }
3009 /* Move the current line */
3010 view->lineno += steps;
3011 assert(0 <= view->lineno && view->lineno < view->lines);
3013 /* Check whether the view needs to be scrolled */
3014 if (view->lineno < view->offset ||
3015 view->lineno >= view->offset + view->height) {
3016 scroll_steps = steps;
3017 if (steps < 0 && -steps > view->offset) {
3018 scroll_steps = -view->offset;
3020 } else if (steps > 0) {
3021 if (view->lineno == view->lines - 1 &&
3022 view->lines > view->height) {
3023 scroll_steps = view->lines - view->offset - 1;
3024 if (scroll_steps >= view->height)
3025 scroll_steps -= view->height - 1;
3026 }
3027 }
3028 }
3030 if (!view_is_displayed(view)) {
3031 view->offset += scroll_steps;
3032 assert(0 <= view->offset && view->offset < view->lines);
3033 view->ops->select(view, &view->line[view->lineno]);
3034 return;
3035 }
3037 /* Repaint the old "current" line if we be scrolling */
3038 if (ABS(steps) < view->height)
3039 draw_view_line(view, view->lineno - steps - view->offset);
3041 if (scroll_steps) {
3042 do_scroll_view(view, scroll_steps);
3043 return;
3044 }
3046 /* Draw the current line */
3047 draw_view_line(view, view->lineno - view->offset);
3049 wnoutrefresh(view->win);
3050 report("");
3051 }
3054 /*
3055 * Searching
3056 */
3058 static void search_view(struct view *view, enum request request);
3060 static bool
3061 grep_text(struct view *view, const char *text[])
3062 {
3063 regmatch_t pmatch;
3064 size_t i;
3066 for (i = 0; text[i]; i++)
3067 if (*text[i] &&
3068 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3069 return TRUE;
3070 return FALSE;
3071 }
3073 static void
3074 select_view_line(struct view *view, unsigned long lineno)
3075 {
3076 unsigned long old_lineno = view->lineno;
3077 unsigned long old_offset = view->offset;
3079 if (goto_view_line(view, view->offset, lineno)) {
3080 if (view_is_displayed(view)) {
3081 if (old_offset != view->offset) {
3082 redraw_view(view);
3083 } else {
3084 draw_view_line(view, old_lineno - view->offset);
3085 draw_view_line(view, view->lineno - view->offset);
3086 wnoutrefresh(view->win);
3087 }
3088 } else {
3089 view->ops->select(view, &view->line[view->lineno]);
3090 }
3091 }
3092 }
3094 static void
3095 find_next(struct view *view, enum request request)
3096 {
3097 unsigned long lineno = view->lineno;
3098 int direction;
3100 if (!*view->grep) {
3101 if (!*opt_search)
3102 report("No previous search");
3103 else
3104 search_view(view, request);
3105 return;
3106 }
3108 switch (request) {
3109 case REQ_SEARCH:
3110 case REQ_FIND_NEXT:
3111 direction = 1;
3112 break;
3114 case REQ_SEARCH_BACK:
3115 case REQ_FIND_PREV:
3116 direction = -1;
3117 break;
3119 default:
3120 return;
3121 }
3123 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3124 lineno += direction;
3126 /* Note, lineno is unsigned long so will wrap around in which case it
3127 * will become bigger than view->lines. */
3128 for (; lineno < view->lines; lineno += direction) {
3129 if (view->ops->grep(view, &view->line[lineno])) {
3130 select_view_line(view, lineno);
3131 report("Line %ld matches '%s'", lineno + 1, view->grep);
3132 return;
3133 }
3134 }
3136 report("No match found for '%s'", view->grep);
3137 }
3139 static void
3140 search_view(struct view *view, enum request request)
3141 {
3142 int regex_err;
3144 if (view->regex) {
3145 regfree(view->regex);
3146 *view->grep = 0;
3147 } else {
3148 view->regex = calloc(1, sizeof(*view->regex));
3149 if (!view->regex)
3150 return;
3151 }
3153 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3154 if (regex_err != 0) {
3155 char buf[SIZEOF_STR] = "unknown error";
3157 regerror(regex_err, view->regex, buf, sizeof(buf));
3158 report("Search failed: %s", buf);
3159 return;
3160 }
3162 string_copy(view->grep, opt_search);
3164 find_next(view, request);
3165 }
3167 /*
3168 * Incremental updating
3169 */
3171 static void
3172 reset_view(struct view *view)
3173 {
3174 int i;
3176 for (i = 0; i < view->lines; i++)
3177 free(view->line[i].data);
3178 free(view->line);
3180 view->p_offset = view->offset;
3181 view->p_yoffset = view->yoffset;
3182 view->p_lineno = view->lineno;
3184 view->line = NULL;
3185 view->offset = 0;
3186 view->yoffset = 0;
3187 view->lines = 0;
3188 view->lineno = 0;
3189 view->vid[0] = 0;
3190 view->update_secs = 0;
3191 }
3193 static const char *
3194 format_arg(const char *name)
3195 {
3196 static struct {
3197 const char *name;
3198 size_t namelen;
3199 const char *value;
3200 const char *value_if_empty;
3201 } vars[] = {
3202 #define FORMAT_VAR(name, value, value_if_empty) \
3203 { name, STRING_SIZE(name), value, value_if_empty }
3204 FORMAT_VAR("%(directory)", opt_path, ""),
3205 FORMAT_VAR("%(file)", opt_file, ""),
3206 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3207 FORMAT_VAR("%(head)", ref_head, ""),
3208 FORMAT_VAR("%(commit)", ref_commit, ""),
3209 FORMAT_VAR("%(blob)", ref_blob, ""),
3210 FORMAT_VAR("%(branch)", ref_branch, ""),
3211 };
3212 int i;
3214 for (i = 0; i < ARRAY_SIZE(vars); i++)
3215 if (!strncmp(name, vars[i].name, vars[i].namelen))
3216 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3218 report("Unknown replacement: `%s`", name);
3219 return NULL;
3220 }
3222 static bool
3223 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3224 {
3225 char buf[SIZEOF_STR];
3226 int argc;
3228 argv_free(*dst_argv);
3230 for (argc = 0; src_argv[argc]; argc++) {
3231 const char *arg = src_argv[argc];
3232 size_t bufpos = 0;
3234 if (!strcmp(arg, "%(fileargs)")) {
3235 if (!argv_append_array(dst_argv, opt_file_args))
3236 break;
3237 continue;
3239 } else if (!strcmp(arg, "%(diffargs)")) {
3240 if (!argv_append_array(dst_argv, opt_diff_args))
3241 break;
3242 continue;
3244 } else if (!strcmp(arg, "%(revargs)")) {
3245 if (!argv_append_array(dst_argv, opt_rev_args))
3246 break;
3247 continue;
3248 }
3250 while (arg) {
3251 char *next = strstr(arg, "%(");
3252 int len = next - arg;
3253 const char *value;
3255 if (!next || !replace) {
3256 len = strlen(arg);
3257 value = "";
3259 } else {
3260 value = format_arg(next);
3262 if (!value) {
3263 return FALSE;
3264 }
3265 }
3267 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3268 return FALSE;
3270 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3271 }
3273 if (!argv_append(dst_argv, buf))
3274 break;
3275 }
3277 return src_argv[argc] == NULL;
3278 }
3280 static bool
3281 restore_view_position(struct view *view)
3282 {
3283 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3284 return FALSE;
3286 /* Changing the view position cancels the restoring. */
3287 /* FIXME: Changing back to the first line is not detected. */
3288 if (view->offset != 0 || view->lineno != 0) {
3289 view->p_restore = FALSE;
3290 return FALSE;
3291 }
3293 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3294 view_is_displayed(view))
3295 werase(view->win);
3297 view->yoffset = view->p_yoffset;
3298 view->p_restore = FALSE;
3300 return TRUE;
3301 }
3303 static void
3304 end_update(struct view *view, bool force)
3305 {
3306 if (!view->pipe)
3307 return;
3308 while (!view->ops->read(view, NULL))
3309 if (!force)
3310 return;
3311 if (force)
3312 io_kill(view->pipe);
3313 io_done(view->pipe);
3314 view->pipe = NULL;
3315 }
3317 static void
3318 setup_update(struct view *view, const char *vid)
3319 {
3320 reset_view(view);
3321 string_copy_rev(view->vid, vid);
3322 view->pipe = &view->io;
3323 view->start_time = time(NULL);
3324 }
3326 static bool
3327 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3328 {
3329 view->dir = dir;
3330 return format_argv(&view->argv, argv, replace);
3331 }
3333 static bool
3334 prepare_update(struct view *view, const char *argv[], const char *dir)
3335 {
3336 if (view->pipe)
3337 end_update(view, TRUE);
3338 return prepare_io(view, dir, argv, FALSE);
3339 }
3341 static bool
3342 start_update(struct view *view, const char **argv, const char *dir)
3343 {
3344 if (view->pipe)
3345 io_done(view->pipe);
3346 return prepare_io(view, dir, argv, FALSE) &&
3347 io_run(&view->io, IO_RD, dir, view->argv);
3348 }
3350 static bool
3351 prepare_update_file(struct view *view, const char *name)
3352 {
3353 if (view->pipe)
3354 end_update(view, TRUE);
3355 argv_free(view->argv);
3356 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3357 }
3359 static bool
3360 begin_update(struct view *view, bool refresh)
3361 {
3362 if (view->pipe)
3363 end_update(view, TRUE);
3365 if (!refresh) {
3366 if (view->ops->prepare) {
3367 if (!view->ops->prepare(view))
3368 return FALSE;
3369 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3370 return FALSE;
3371 }
3373 /* Put the current ref_* value to the view title ref
3374 * member. This is needed by the blob view. Most other
3375 * views sets it automatically after loading because the
3376 * first line is a commit line. */
3377 string_copy_rev(view->ref, view->id);
3378 }
3380 if (view->argv && view->argv[0] &&
3381 !io_run(&view->io, IO_RD, view->dir, view->argv))
3382 return FALSE;
3384 setup_update(view, view->id);
3386 return TRUE;
3387 }
3389 static bool
3390 update_view(struct view *view)
3391 {
3392 char out_buffer[BUFSIZ * 2];
3393 char *line;
3394 /* Clear the view and redraw everything since the tree sorting
3395 * might have rearranged things. */
3396 bool redraw = view->lines == 0;
3397 bool can_read = TRUE;
3399 if (!view->pipe)
3400 return TRUE;
3402 if (!io_can_read(view->pipe)) {
3403 if (view->lines == 0 && view_is_displayed(view)) {
3404 time_t secs = time(NULL) - view->start_time;
3406 if (secs > 1 && secs > view->update_secs) {
3407 if (view->update_secs == 0)
3408 redraw_view(view);
3409 update_view_title(view);
3410 view->update_secs = secs;
3411 }
3412 }
3413 return TRUE;
3414 }
3416 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3417 if (opt_iconv_in != ICONV_NONE) {
3418 ICONV_CONST char *inbuf = line;
3419 size_t inlen = strlen(line) + 1;
3421 char *outbuf = out_buffer;
3422 size_t outlen = sizeof(out_buffer);
3424 size_t ret;
3426 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3427 if (ret != (size_t) -1)
3428 line = out_buffer;
3429 }
3431 if (!view->ops->read(view, line)) {
3432 report("Allocation failure");
3433 end_update(view, TRUE);
3434 return FALSE;
3435 }
3436 }
3438 {
3439 unsigned long lines = view->lines;
3440 int digits;
3442 for (digits = 0; lines; digits++)
3443 lines /= 10;
3445 /* Keep the displayed view in sync with line number scaling. */
3446 if (digits != view->digits) {
3447 view->digits = digits;
3448 if (opt_line_number || view->type == VIEW_BLAME)
3449 redraw = TRUE;
3450 }
3451 }
3453 if (io_error(view->pipe)) {
3454 report("Failed to read: %s", io_strerror(view->pipe));
3455 end_update(view, TRUE);
3457 } else if (io_eof(view->pipe)) {
3458 if (view_is_displayed(view))
3459 report("");
3460 end_update(view, FALSE);
3461 }
3463 if (restore_view_position(view))
3464 redraw = TRUE;
3466 if (!view_is_displayed(view))
3467 return TRUE;
3469 if (redraw)
3470 redraw_view_from(view, 0);
3471 else
3472 redraw_view_dirty(view);
3474 /* Update the title _after_ the redraw so that if the redraw picks up a
3475 * commit reference in view->ref it'll be available here. */
3476 update_view_title(view);
3477 return TRUE;
3478 }
3480 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3482 static struct line *
3483 add_line_data(struct view *view, void *data, enum line_type type)
3484 {
3485 struct line *line;
3487 if (!realloc_lines(&view->line, view->lines, 1))
3488 return NULL;
3490 line = &view->line[view->lines++];
3491 memset(line, 0, sizeof(*line));
3492 line->type = type;
3493 line->data = data;
3494 line->dirty = 1;
3496 return line;
3497 }
3499 static struct line *
3500 add_line_text(struct view *view, const char *text, enum line_type type)
3501 {
3502 char *data = text ? strdup(text) : NULL;
3504 return data ? add_line_data(view, data, type) : NULL;
3505 }
3507 static struct line *
3508 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3509 {
3510 char buf[SIZEOF_STR];
3511 va_list args;
3513 va_start(args, fmt);
3514 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3515 buf[0] = 0;
3516 va_end(args);
3518 return buf[0] ? add_line_text(view, buf, type) : NULL;
3519 }
3521 /*
3522 * View opening
3523 */
3525 enum open_flags {
3526 OPEN_DEFAULT = 0, /* Use default view switching. */
3527 OPEN_SPLIT = 1, /* Split current view. */
3528 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3529 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3530 OPEN_PREPARED = 32, /* Open already prepared command. */
3531 };
3533 static void
3534 open_view(struct view *prev, enum request request, enum open_flags flags)
3535 {
3536 bool split = !!(flags & OPEN_SPLIT);
3537 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3538 bool nomaximize = !!(flags & OPEN_REFRESH);
3539 struct view *view = VIEW(request);
3540 int nviews = displayed_views();
3541 struct view *base_view = display[0];
3543 if (view == prev && nviews == 1 && !reload) {
3544 report("Already in %s view", view->name);
3545 return;
3546 }
3548 if (view->git_dir && !opt_git_dir[0]) {
3549 report("The %s view is disabled in pager view", view->name);
3550 return;
3551 }
3553 if (split) {
3554 display[1] = view;
3555 current_view = 1;
3556 view->parent = prev;
3557 } else if (!nomaximize) {
3558 /* Maximize the current view. */
3559 memset(display, 0, sizeof(display));
3560 current_view = 0;
3561 display[current_view] = view;
3562 }
3564 /* No prev signals that this is the first loaded view. */
3565 if (prev && view != prev) {
3566 view->prev = prev;
3567 }
3569 /* Resize the view when switching between split- and full-screen,
3570 * or when switching between two different full-screen views. */
3571 if (nviews != displayed_views() ||
3572 (nviews == 1 && base_view != display[0]))
3573 resize_display();
3575 if (view->ops->open) {
3576 if (view->pipe)
3577 end_update(view, TRUE);
3578 if (!view->ops->open(view)) {
3579 report("Failed to load %s view", view->name);
3580 return;
3581 }
3582 restore_view_position(view);
3584 } else if ((reload || strcmp(view->vid, view->id)) &&
3585 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3586 report("Failed to load %s view", view->name);
3587 return;
3588 }
3590 if (split && prev->lineno - prev->offset >= prev->height) {
3591 /* Take the title line into account. */
3592 int lines = prev->lineno - prev->offset - prev->height + 1;
3594 /* Scroll the view that was split if the current line is
3595 * outside the new limited view. */
3596 do_scroll_view(prev, lines);
3597 }
3599 if (prev && view != prev && split && view_is_displayed(prev)) {
3600 /* "Blur" the previous view. */
3601 update_view_title(prev);
3602 }
3604 if (view->pipe && view->lines == 0) {
3605 /* Clear the old view and let the incremental updating refill
3606 * the screen. */
3607 werase(view->win);
3608 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3609 report("");
3610 } else if (view_is_displayed(view)) {
3611 redraw_view(view);
3612 report("");
3613 }
3614 }
3616 static void
3617 open_external_viewer(const char *argv[], const char *dir)
3618 {
3619 def_prog_mode(); /* save current tty modes */
3620 endwin(); /* restore original tty modes */
3621 io_run_fg(argv, dir);
3622 fprintf(stderr, "Press Enter to continue");
3623 getc(opt_tty);
3624 reset_prog_mode();
3625 redraw_display(TRUE);
3626 }
3628 static void
3629 open_mergetool(const char *file)
3630 {
3631 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3633 open_external_viewer(mergetool_argv, opt_cdup);
3634 }
3636 static void
3637 open_editor(const char *file)
3638 {
3639 const char *editor_argv[] = { "vi", file, NULL };
3640 const char *editor;
3642 editor = getenv("GIT_EDITOR");
3643 if (!editor && *opt_editor)
3644 editor = opt_editor;
3645 if (!editor)
3646 editor = getenv("VISUAL");
3647 if (!editor)
3648 editor = getenv("EDITOR");
3649 if (!editor)
3650 editor = "vi";
3652 editor_argv[0] = editor;
3653 open_external_viewer(editor_argv, opt_cdup);
3654 }
3656 static void
3657 open_run_request(enum request request)
3658 {
3659 struct run_request *req = get_run_request(request);
3660 const char **argv = NULL;
3662 if (!req) {
3663 report("Unknown run request");
3664 return;
3665 }
3667 if (format_argv(&argv, req->argv, TRUE))
3668 open_external_viewer(argv, NULL);
3669 if (argv)
3670 argv_free(argv);
3671 free(argv);
3672 }
3674 /*
3675 * User request switch noodle
3676 */
3678 static int
3679 view_driver(struct view *view, enum request request)
3680 {
3681 int i;
3683 if (request == REQ_NONE)
3684 return TRUE;
3686 if (request > REQ_NONE) {
3687 open_run_request(request);
3688 view_request(view, REQ_REFRESH);
3689 return TRUE;
3690 }
3692 request = view_request(view, request);
3693 if (request == REQ_NONE)
3694 return TRUE;
3696 switch (request) {
3697 case REQ_MOVE_UP:
3698 case REQ_MOVE_DOWN:
3699 case REQ_MOVE_PAGE_UP:
3700 case REQ_MOVE_PAGE_DOWN:
3701 case REQ_MOVE_FIRST_LINE:
3702 case REQ_MOVE_LAST_LINE:
3703 move_view(view, request);
3704 break;
3706 case REQ_SCROLL_FIRST_COL:
3707 case REQ_SCROLL_LEFT:
3708 case REQ_SCROLL_RIGHT:
3709 case REQ_SCROLL_LINE_DOWN:
3710 case REQ_SCROLL_LINE_UP:
3711 case REQ_SCROLL_PAGE_DOWN:
3712 case REQ_SCROLL_PAGE_UP:
3713 scroll_view(view, request);
3714 break;
3716 case REQ_VIEW_BLAME:
3717 if (!opt_file[0]) {
3718 report("No file chosen, press %s to open tree view",
3719 get_key(view->keymap, REQ_VIEW_TREE));
3720 break;
3721 }
3722 open_view(view, request, OPEN_DEFAULT);
3723 break;
3725 case REQ_VIEW_BLOB:
3726 if (!ref_blob[0]) {
3727 report("No file chosen, press %s to open tree view",
3728 get_key(view->keymap, REQ_VIEW_TREE));
3729 break;
3730 }
3731 open_view(view, request, OPEN_DEFAULT);
3732 break;
3734 case REQ_VIEW_PAGER:
3735 if (view == NULL) {
3736 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3737 die("Failed to open stdin");
3738 open_view(view, request, OPEN_PREPARED);
3739 break;
3740 }
3742 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3743 report("No pager content, press %s to run command from prompt",
3744 get_key(view->keymap, REQ_PROMPT));
3745 break;
3746 }
3747 open_view(view, request, OPEN_DEFAULT);
3748 break;
3750 case REQ_VIEW_STAGE:
3751 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3752 report("No stage content, press %s to open the status view and choose file",
3753 get_key(view->keymap, REQ_VIEW_STATUS));
3754 break;
3755 }
3756 open_view(view, request, OPEN_DEFAULT);
3757 break;
3759 case REQ_VIEW_STATUS:
3760 if (opt_is_inside_work_tree == FALSE) {
3761 report("The status view requires a working tree");
3762 break;
3763 }
3764 open_view(view, request, OPEN_DEFAULT);
3765 break;
3767 case REQ_VIEW_MAIN:
3768 case REQ_VIEW_DIFF:
3769 case REQ_VIEW_LOG:
3770 case REQ_VIEW_TREE:
3771 case REQ_VIEW_HELP:
3772 case REQ_VIEW_BRANCH:
3773 open_view(view, request, OPEN_DEFAULT);
3774 break;
3776 case REQ_NEXT:
3777 case REQ_PREVIOUS:
3778 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3780 if (view->parent) {
3781 int line;
3783 view = view->parent;
3784 line = view->lineno;
3785 move_view(view, request);
3786 if (view_is_displayed(view))
3787 update_view_title(view);
3788 if (line != view->lineno)
3789 view_request(view, REQ_ENTER);
3790 } else {
3791 move_view(view, request);
3792 }
3793 break;
3795 case REQ_VIEW_NEXT:
3796 {
3797 int nviews = displayed_views();
3798 int next_view = (current_view + 1) % nviews;
3800 if (next_view == current_view) {
3801 report("Only one view is displayed");
3802 break;
3803 }
3805 current_view = next_view;
3806 /* Blur out the title of the previous view. */
3807 update_view_title(view);
3808 report("");
3809 break;
3810 }
3811 case REQ_REFRESH:
3812 report("Refreshing is not yet supported for the %s view", view->name);
3813 break;
3815 case REQ_MAXIMIZE:
3816 if (displayed_views() == 2)
3817 maximize_view(view);
3818 break;
3820 case REQ_OPTIONS:
3821 open_option_menu();
3822 break;
3824 case REQ_TOGGLE_LINENO:
3825 toggle_view_option(&opt_line_number, "line numbers");
3826 break;
3828 case REQ_TOGGLE_DATE:
3829 toggle_date();
3830 break;
3832 case REQ_TOGGLE_AUTHOR:
3833 toggle_author();
3834 break;
3836 case REQ_TOGGLE_REV_GRAPH:
3837 toggle_view_option(&opt_rev_graph, "revision graph display");
3838 break;
3840 case REQ_TOGGLE_REFS:
3841 toggle_view_option(&opt_show_refs, "reference display");
3842 break;
3844 case REQ_TOGGLE_SORT_FIELD:
3845 case REQ_TOGGLE_SORT_ORDER:
3846 report("Sorting is not yet supported for the %s view", view->name);
3847 break;
3849 case REQ_SEARCH:
3850 case REQ_SEARCH_BACK:
3851 search_view(view, request);
3852 break;
3854 case REQ_FIND_NEXT:
3855 case REQ_FIND_PREV:
3856 find_next(view, request);
3857 break;
3859 case REQ_STOP_LOADING:
3860 foreach_view(view, i) {
3861 if (view->pipe)
3862 report("Stopped loading the %s view", view->name),
3863 end_update(view, TRUE);
3864 }
3865 break;
3867 case REQ_SHOW_VERSION:
3868 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3869 return TRUE;
3871 case REQ_SCREEN_REDRAW:
3872 redraw_display(TRUE);
3873 break;
3875 case REQ_EDIT:
3876 report("Nothing to edit");
3877 break;
3879 case REQ_ENTER:
3880 report("Nothing to enter");
3881 break;
3883 case REQ_VIEW_CLOSE:
3884 /* XXX: Mark closed views by letting view->prev point to the
3885 * view itself. Parents to closed view should never be
3886 * followed. */
3887 if (view->prev && view->prev != view) {
3888 maximize_view(view->prev);
3889 view->prev = view;
3890 break;
3891 }
3892 /* Fall-through */
3893 case REQ_QUIT:
3894 return FALSE;
3896 default:
3897 report("Unknown key, press %s for help",
3898 get_key(view->keymap, REQ_VIEW_HELP));
3899 return TRUE;
3900 }
3902 return TRUE;
3903 }
3906 /*
3907 * View backend utilities
3908 */
3910 enum sort_field {
3911 ORDERBY_NAME,
3912 ORDERBY_DATE,
3913 ORDERBY_AUTHOR,
3914 };
3916 struct sort_state {
3917 const enum sort_field *fields;
3918 size_t size, current;
3919 bool reverse;
3920 };
3922 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3923 #define get_sort_field(state) ((state).fields[(state).current])
3924 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3926 static void
3927 sort_view(struct view *view, enum request request, struct sort_state *state,
3928 int (*compare)(const void *, const void *))
3929 {
3930 switch (request) {
3931 case REQ_TOGGLE_SORT_FIELD:
3932 state->current = (state->current + 1) % state->size;
3933 break;
3935 case REQ_TOGGLE_SORT_ORDER:
3936 state->reverse = !state->reverse;
3937 break;
3938 default:
3939 die("Not a sort request");
3940 }
3942 qsort(view->line, view->lines, sizeof(*view->line), compare);
3943 redraw_view(view);
3944 }
3946 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3948 /* Small author cache to reduce memory consumption. It uses binary
3949 * search to lookup or find place to position new entries. No entries
3950 * are ever freed. */
3951 static const char *
3952 get_author(const char *name)
3953 {
3954 static const char **authors;
3955 static size_t authors_size;
3956 int from = 0, to = authors_size - 1;
3958 while (from <= to) {
3959 size_t pos = (to + from) / 2;
3960 int cmp = strcmp(name, authors[pos]);
3962 if (!cmp)
3963 return authors[pos];
3965 if (cmp < 0)
3966 to = pos - 1;
3967 else
3968 from = pos + 1;
3969 }
3971 if (!realloc_authors(&authors, authors_size, 1))
3972 return NULL;
3973 name = strdup(name);
3974 if (!name)
3975 return NULL;
3977 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3978 authors[from] = name;
3979 authors_size++;
3981 return name;
3982 }
3984 static void
3985 parse_timesec(struct time *time, const char *sec)
3986 {
3987 time->sec = (time_t) atol(sec);
3988 }
3990 static void
3991 parse_timezone(struct time *time, const char *zone)
3992 {
3993 long tz;
3995 tz = ('0' - zone[1]) * 60 * 60 * 10;
3996 tz += ('0' - zone[2]) * 60 * 60;
3997 tz += ('0' - zone[3]) * 60 * 10;
3998 tz += ('0' - zone[4]) * 60;
4000 if (zone[0] == '-')
4001 tz = -tz;
4003 time->tz = tz;
4004 time->sec -= tz;
4005 }
4007 /* Parse author lines where the name may be empty:
4008 * author <email@address.tld> 1138474660 +0100
4009 */
4010 static void
4011 parse_author_line(char *ident, const char **author, struct time *time)
4012 {
4013 char *nameend = strchr(ident, '<');
4014 char *emailend = strchr(ident, '>');
4016 if (nameend && emailend)
4017 *nameend = *emailend = 0;
4018 ident = chomp_string(ident);
4019 if (!*ident) {
4020 if (nameend)
4021 ident = chomp_string(nameend + 1);
4022 if (!*ident)
4023 ident = "Unknown";
4024 }
4026 *author = get_author(ident);
4028 /* Parse epoch and timezone */
4029 if (emailend && emailend[1] == ' ') {
4030 char *secs = emailend + 2;
4031 char *zone = strchr(secs, ' ');
4033 parse_timesec(time, secs);
4035 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4036 parse_timezone(time, zone + 1);
4037 }
4038 }
4040 /*
4041 * Pager backend
4042 */
4044 static bool
4045 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4046 {
4047 if (opt_line_number && draw_lineno(view, lineno))
4048 return TRUE;
4050 draw_text(view, line->type, line->data, TRUE);
4051 return TRUE;
4052 }
4054 static bool
4055 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4056 {
4057 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4058 char ref[SIZEOF_STR];
4060 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4061 return TRUE;
4063 /* This is the only fatal call, since it can "corrupt" the buffer. */
4064 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4065 return FALSE;
4067 return TRUE;
4068 }
4070 static void
4071 add_pager_refs(struct view *view, struct line *line)
4072 {
4073 char buf[SIZEOF_STR];
4074 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4075 struct ref_list *list;
4076 size_t bufpos = 0, i;
4077 const char *sep = "Refs: ";
4078 bool is_tag = FALSE;
4080 assert(line->type == LINE_COMMIT);
4082 list = get_ref_list(commit_id);
4083 if (!list) {
4084 if (view->type == VIEW_DIFF)
4085 goto try_add_describe_ref;
4086 return;
4087 }
4089 for (i = 0; i < list->size; i++) {
4090 struct ref *ref = list->refs[i];
4091 const char *fmt = ref->tag ? "%s[%s]" :
4092 ref->remote ? "%s<%s>" : "%s%s";
4094 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4095 return;
4096 sep = ", ";
4097 if (ref->tag)
4098 is_tag = TRUE;
4099 }
4101 if (!is_tag && view->type == VIEW_DIFF) {
4102 try_add_describe_ref:
4103 /* Add <tag>-g<commit_id> "fake" reference. */
4104 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4105 return;
4106 }
4108 if (bufpos == 0)
4109 return;
4111 add_line_text(view, buf, LINE_PP_REFS);
4112 }
4114 static bool
4115 pager_read(struct view *view, char *data)
4116 {
4117 struct line *line;
4119 if (!data)
4120 return TRUE;
4122 line = add_line_text(view, data, get_line_type(data));
4123 if (!line)
4124 return FALSE;
4126 if (line->type == LINE_COMMIT &&
4127 (view->type == VIEW_DIFF ||
4128 view->type == VIEW_LOG))
4129 add_pager_refs(view, line);
4131 return TRUE;
4132 }
4134 static enum request
4135 pager_request(struct view *view, enum request request, struct line *line)
4136 {
4137 int split = 0;
4139 if (request != REQ_ENTER)
4140 return request;
4142 if (line->type == LINE_COMMIT &&
4143 (view->type == VIEW_LOG ||
4144 view->type == VIEW_PAGER)) {
4145 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4146 split = 1;
4147 }
4149 /* Always scroll the view even if it was split. That way
4150 * you can use Enter to scroll through the log view and
4151 * split open each commit diff. */
4152 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4154 /* FIXME: A minor workaround. Scrolling the view will call report("")
4155 * but if we are scrolling a non-current view this won't properly
4156 * update the view title. */
4157 if (split)
4158 update_view_title(view);
4160 return REQ_NONE;
4161 }
4163 static bool
4164 pager_grep(struct view *view, struct line *line)
4165 {
4166 const char *text[] = { line->data, NULL };
4168 return grep_text(view, text);
4169 }
4171 static void
4172 pager_select(struct view *view, struct line *line)
4173 {
4174 if (line->type == LINE_COMMIT) {
4175 char *text = (char *)line->data + STRING_SIZE("commit ");
4177 if (view->type != VIEW_PAGER)
4178 string_copy_rev(view->ref, text);
4179 string_copy_rev(ref_commit, text);
4180 }
4181 }
4183 static struct view_ops pager_ops = {
4184 "line",
4185 NULL,
4186 NULL,
4187 pager_read,
4188 pager_draw,
4189 pager_request,
4190 pager_grep,
4191 pager_select,
4192 };
4194 static const char *log_argv[SIZEOF_ARG] = {
4195 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4196 };
4198 static enum request
4199 log_request(struct view *view, enum request request, struct line *line)
4200 {
4201 switch (request) {
4202 case REQ_REFRESH:
4203 load_refs();
4204 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4205 return REQ_NONE;
4206 default:
4207 return pager_request(view, request, line);
4208 }
4209 }
4211 static struct view_ops log_ops = {
4212 "line",
4213 log_argv,
4214 NULL,
4215 pager_read,
4216 pager_draw,
4217 log_request,
4218 pager_grep,
4219 pager_select,
4220 };
4222 static const char *diff_argv[SIZEOF_ARG] = {
4223 "git", "show", "--pretty=fuller", "--no-color", "--root",
4224 "--patch-with-stat", "--find-copies-harder", "-C",
4225 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4226 };
4228 static bool
4229 diff_read(struct view *view, char *data)
4230 {
4231 if (!data) {
4232 /* Fall back to retry if no diff will be shown. */
4233 if (view->lines == 0 && opt_file_args) {
4234 int pos = argv_size(view->argv)
4235 - argv_size(opt_file_args) - 1;
4237 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4238 for (; view->argv[pos]; pos++) {
4239 free((void *) view->argv[pos]);
4240 view->argv[pos] = NULL;
4241 }
4243 if (view->pipe)
4244 io_done(view->pipe);
4245 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4246 return FALSE;
4247 }
4248 }
4249 return TRUE;
4250 }
4252 return pager_read(view, data);
4253 }
4255 static struct view_ops diff_ops = {
4256 "line",
4257 diff_argv,
4258 NULL,
4259 diff_read,
4260 pager_draw,
4261 pager_request,
4262 pager_grep,
4263 pager_select,
4264 };
4266 /*
4267 * Help backend
4268 */
4270 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4272 static bool
4273 help_open_keymap_title(struct view *view, enum keymap keymap)
4274 {
4275 struct line *line;
4277 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4278 help_keymap_hidden[keymap] ? '+' : '-',
4279 enum_name(keymap_table[keymap]));
4280 if (line)
4281 line->other = keymap;
4283 return help_keymap_hidden[keymap];
4284 }
4286 static void
4287 help_open_keymap(struct view *view, enum keymap keymap)
4288 {
4289 const char *group = NULL;
4290 char buf[SIZEOF_STR];
4291 size_t bufpos;
4292 bool add_title = TRUE;
4293 int i;
4295 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4296 const char *key = NULL;
4298 if (req_info[i].request == REQ_NONE)
4299 continue;
4301 if (!req_info[i].request) {
4302 group = req_info[i].help;
4303 continue;
4304 }
4306 key = get_keys(keymap, req_info[i].request, TRUE);
4307 if (!key || !*key)
4308 continue;
4310 if (add_title && help_open_keymap_title(view, keymap))
4311 return;
4312 add_title = FALSE;
4314 if (group) {
4315 add_line_text(view, group, LINE_HELP_GROUP);
4316 group = NULL;
4317 }
4319 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4320 enum_name(req_info[i]), req_info[i].help);
4321 }
4323 group = "External commands:";
4325 for (i = 0; i < run_requests; i++) {
4326 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4327 const char *key;
4328 int argc;
4330 if (!req || req->keymap != keymap)
4331 continue;
4333 key = get_key_name(req->key);
4334 if (!*key)
4335 key = "(no key defined)";
4337 if (add_title && help_open_keymap_title(view, keymap))
4338 return;
4339 if (group) {
4340 add_line_text(view, group, LINE_HELP_GROUP);
4341 group = NULL;
4342 }
4344 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4345 if (!string_format_from(buf, &bufpos, "%s%s",
4346 argc ? " " : "", req->argv[argc]))
4347 return;
4349 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4350 }
4351 }
4353 static bool
4354 help_open(struct view *view)
4355 {
4356 enum keymap keymap;
4358 reset_view(view);
4359 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4360 add_line_text(view, "", LINE_DEFAULT);
4362 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4363 help_open_keymap(view, keymap);
4365 return TRUE;
4366 }
4368 static enum request
4369 help_request(struct view *view, enum request request, struct line *line)
4370 {
4371 switch (request) {
4372 case REQ_ENTER:
4373 if (line->type == LINE_HELP_KEYMAP) {
4374 help_keymap_hidden[line->other] =
4375 !help_keymap_hidden[line->other];
4376 view->p_restore = TRUE;
4377 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4378 }
4380 return REQ_NONE;
4381 default:
4382 return pager_request(view, request, line);
4383 }
4384 }
4386 static struct view_ops help_ops = {
4387 "line",
4388 NULL,
4389 help_open,
4390 NULL,
4391 pager_draw,
4392 help_request,
4393 pager_grep,
4394 pager_select,
4395 };
4398 /*
4399 * Tree backend
4400 */
4402 struct tree_stack_entry {
4403 struct tree_stack_entry *prev; /* Entry below this in the stack */
4404 unsigned long lineno; /* Line number to restore */
4405 char *name; /* Position of name in opt_path */
4406 };
4408 /* The top of the path stack. */
4409 static struct tree_stack_entry *tree_stack = NULL;
4410 unsigned long tree_lineno = 0;
4412 static void
4413 pop_tree_stack_entry(void)
4414 {
4415 struct tree_stack_entry *entry = tree_stack;
4417 tree_lineno = entry->lineno;
4418 entry->name[0] = 0;
4419 tree_stack = entry->prev;
4420 free(entry);
4421 }
4423 static void
4424 push_tree_stack_entry(const char *name, unsigned long lineno)
4425 {
4426 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4427 size_t pathlen = strlen(opt_path);
4429 if (!entry)
4430 return;
4432 entry->prev = tree_stack;
4433 entry->name = opt_path + pathlen;
4434 tree_stack = entry;
4436 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4437 pop_tree_stack_entry();
4438 return;
4439 }
4441 /* Move the current line to the first tree entry. */
4442 tree_lineno = 1;
4443 entry->lineno = lineno;
4444 }
4446 /* Parse output from git-ls-tree(1):
4447 *
4448 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4449 */
4451 #define SIZEOF_TREE_ATTR \
4452 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4454 #define SIZEOF_TREE_MODE \
4455 STRING_SIZE("100644 ")
4457 #define TREE_ID_OFFSET \
4458 STRING_SIZE("100644 blob ")
4460 struct tree_entry {
4461 char id[SIZEOF_REV];
4462 mode_t mode;
4463 struct time time; /* Date from the author ident. */
4464 const char *author; /* Author of the commit. */
4465 char name[1];
4466 };
4468 static const char *
4469 tree_path(const struct line *line)
4470 {
4471 return ((struct tree_entry *) line->data)->name;
4472 }
4474 static int
4475 tree_compare_entry(const struct line *line1, const struct line *line2)
4476 {
4477 if (line1->type != line2->type)
4478 return line1->type == LINE_TREE_DIR ? -1 : 1;
4479 return strcmp(tree_path(line1), tree_path(line2));
4480 }
4482 static const enum sort_field tree_sort_fields[] = {
4483 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4484 };
4485 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4487 static int
4488 tree_compare(const void *l1, const void *l2)
4489 {
4490 const struct line *line1 = (const struct line *) l1;
4491 const struct line *line2 = (const struct line *) l2;
4492 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4493 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4495 if (line1->type == LINE_TREE_HEAD)
4496 return -1;
4497 if (line2->type == LINE_TREE_HEAD)
4498 return 1;
4500 switch (get_sort_field(tree_sort_state)) {
4501 case ORDERBY_DATE:
4502 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4504 case ORDERBY_AUTHOR:
4505 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4507 case ORDERBY_NAME:
4508 default:
4509 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4510 }
4511 }
4514 static struct line *
4515 tree_entry(struct view *view, enum line_type type, const char *path,
4516 const char *mode, const char *id)
4517 {
4518 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4519 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4521 if (!entry || !line) {
4522 free(entry);
4523 return NULL;
4524 }
4526 strncpy(entry->name, path, strlen(path));
4527 if (mode)
4528 entry->mode = strtoul(mode, NULL, 8);
4529 if (id)
4530 string_copy_rev(entry->id, id);
4532 return line;
4533 }
4535 static bool
4536 tree_read_date(struct view *view, char *text, bool *read_date)
4537 {
4538 static const char *author_name;
4539 static struct time author_time;
4541 if (!text && *read_date) {
4542 *read_date = FALSE;
4543 return TRUE;
4545 } else if (!text) {
4546 char *path = *opt_path ? opt_path : ".";
4547 /* Find next entry to process */
4548 const char *log_file[] = {
4549 "git", "log", "--no-color", "--pretty=raw",
4550 "--cc", "--raw", view->id, "--", path, NULL
4551 };
4553 if (!view->lines) {
4554 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4555 report("Tree is empty");
4556 return TRUE;
4557 }
4559 if (!start_update(view, log_file, opt_cdup)) {
4560 report("Failed to load tree data");
4561 return TRUE;
4562 }
4564 *read_date = TRUE;
4565 return FALSE;
4567 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4568 parse_author_line(text + STRING_SIZE("author "),
4569 &author_name, &author_time);
4571 } else if (*text == ':') {
4572 char *pos;
4573 size_t annotated = 1;
4574 size_t i;
4576 pos = strchr(text, '\t');
4577 if (!pos)
4578 return TRUE;
4579 text = pos + 1;
4580 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4581 text += strlen(opt_path);
4582 pos = strchr(text, '/');
4583 if (pos)
4584 *pos = 0;
4586 for (i = 1; i < view->lines; i++) {
4587 struct line *line = &view->line[i];
4588 struct tree_entry *entry = line->data;
4590 annotated += !!entry->author;
4591 if (entry->author || strcmp(entry->name, text))
4592 continue;
4594 entry->author = author_name;
4595 entry->time = author_time;
4596 line->dirty = 1;
4597 break;
4598 }
4600 if (annotated == view->lines)
4601 io_kill(view->pipe);
4602 }
4603 return TRUE;
4604 }
4606 static bool
4607 tree_read(struct view *view, char *text)
4608 {
4609 static bool read_date = FALSE;
4610 struct tree_entry *data;
4611 struct line *entry, *line;
4612 enum line_type type;
4613 size_t textlen = text ? strlen(text) : 0;
4614 char *path = text + SIZEOF_TREE_ATTR;
4616 if (read_date || !text)
4617 return tree_read_date(view, text, &read_date);
4619 if (textlen <= SIZEOF_TREE_ATTR)
4620 return FALSE;
4621 if (view->lines == 0 &&
4622 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4623 return FALSE;
4625 /* Strip the path part ... */
4626 if (*opt_path) {
4627 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4628 size_t striplen = strlen(opt_path);
4630 if (pathlen > striplen)
4631 memmove(path, path + striplen,
4632 pathlen - striplen + 1);
4634 /* Insert "link" to parent directory. */
4635 if (view->lines == 1 &&
4636 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4637 return FALSE;
4638 }
4640 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4641 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4642 if (!entry)
4643 return FALSE;
4644 data = entry->data;
4646 /* Skip "Directory ..." and ".." line. */
4647 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4648 if (tree_compare_entry(line, entry) <= 0)
4649 continue;
4651 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4653 line->data = data;
4654 line->type = type;
4655 for (; line <= entry; line++)
4656 line->dirty = line->cleareol = 1;
4657 return TRUE;
4658 }
4660 if (tree_lineno > view->lineno) {
4661 view->lineno = tree_lineno;
4662 tree_lineno = 0;
4663 }
4665 return TRUE;
4666 }
4668 static bool
4669 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4670 {
4671 struct tree_entry *entry = line->data;
4673 if (line->type == LINE_TREE_HEAD) {
4674 if (draw_text(view, line->type, "Directory path /", TRUE))
4675 return TRUE;
4676 } else {
4677 if (draw_mode(view, entry->mode))
4678 return TRUE;
4680 if (opt_author && draw_author(view, entry->author))
4681 return TRUE;
4683 if (opt_date && draw_date(view, &entry->time))
4684 return TRUE;
4685 }
4686 if (draw_text(view, line->type, entry->name, TRUE))
4687 return TRUE;
4688 return TRUE;
4689 }
4691 static void
4692 open_blob_editor(const char *id)
4693 {
4694 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4695 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4696 int fd = mkstemp(file);
4698 if (fd == -1)
4699 report("Failed to create temporary file");
4700 else if (!io_run_append(blob_argv, fd))
4701 report("Failed to save blob data to file");
4702 else
4703 open_editor(file);
4704 if (fd != -1)
4705 unlink(file);
4706 }
4708 static enum request
4709 tree_request(struct view *view, enum request request, struct line *line)
4710 {
4711 enum open_flags flags;
4712 struct tree_entry *entry = line->data;
4714 switch (request) {
4715 case REQ_VIEW_BLAME:
4716 if (line->type != LINE_TREE_FILE) {
4717 report("Blame only supported for files");
4718 return REQ_NONE;
4719 }
4721 string_copy(opt_ref, view->vid);
4722 return request;
4724 case REQ_EDIT:
4725 if (line->type != LINE_TREE_FILE) {
4726 report("Edit only supported for files");
4727 } else if (!is_head_commit(view->vid)) {
4728 open_blob_editor(entry->id);
4729 } else {
4730 open_editor(opt_file);
4731 }
4732 return REQ_NONE;
4734 case REQ_TOGGLE_SORT_FIELD:
4735 case REQ_TOGGLE_SORT_ORDER:
4736 sort_view(view, request, &tree_sort_state, tree_compare);
4737 return REQ_NONE;
4739 case REQ_PARENT:
4740 if (!*opt_path) {
4741 /* quit view if at top of tree */
4742 return REQ_VIEW_CLOSE;
4743 }
4744 /* fake 'cd ..' */
4745 line = &view->line[1];
4746 break;
4748 case REQ_ENTER:
4749 break;
4751 default:
4752 return request;
4753 }
4755 /* Cleanup the stack if the tree view is at a different tree. */
4756 while (!*opt_path && tree_stack)
4757 pop_tree_stack_entry();
4759 switch (line->type) {
4760 case LINE_TREE_DIR:
4761 /* Depending on whether it is a subdirectory or parent link
4762 * mangle the path buffer. */
4763 if (line == &view->line[1] && *opt_path) {
4764 pop_tree_stack_entry();
4766 } else {
4767 const char *basename = tree_path(line);
4769 push_tree_stack_entry(basename, view->lineno);
4770 }
4772 /* Trees and subtrees share the same ID, so they are not not
4773 * unique like blobs. */
4774 flags = OPEN_RELOAD;
4775 request = REQ_VIEW_TREE;
4776 break;
4778 case LINE_TREE_FILE:
4779 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4780 request = REQ_VIEW_BLOB;
4781 break;
4783 default:
4784 return REQ_NONE;
4785 }
4787 open_view(view, request, flags);
4788 if (request == REQ_VIEW_TREE)
4789 view->lineno = tree_lineno;
4791 return REQ_NONE;
4792 }
4794 static bool
4795 tree_grep(struct view *view, struct line *line)
4796 {
4797 struct tree_entry *entry = line->data;
4798 const char *text[] = {
4799 entry->name,
4800 opt_author ? entry->author : "",
4801 mkdate(&entry->time, opt_date),
4802 NULL
4803 };
4805 return grep_text(view, text);
4806 }
4808 static void
4809 tree_select(struct view *view, struct line *line)
4810 {
4811 struct tree_entry *entry = line->data;
4813 if (line->type == LINE_TREE_FILE) {
4814 string_copy_rev(ref_blob, entry->id);
4815 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4817 } else if (line->type != LINE_TREE_DIR) {
4818 return;
4819 }
4821 string_copy_rev(view->ref, entry->id);
4822 }
4824 static bool
4825 tree_prepare(struct view *view)
4826 {
4827 if (view->lines == 0 && opt_prefix[0]) {
4828 char *pos = opt_prefix;
4830 while (pos && *pos) {
4831 char *end = strchr(pos, '/');
4833 if (end)
4834 *end = 0;
4835 push_tree_stack_entry(pos, 0);
4836 pos = end;
4837 if (end) {
4838 *end = '/';
4839 pos++;
4840 }
4841 }
4843 } else if (strcmp(view->vid, view->id)) {
4844 opt_path[0] = 0;
4845 }
4847 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4848 }
4850 static const char *tree_argv[SIZEOF_ARG] = {
4851 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4852 };
4854 static struct view_ops tree_ops = {
4855 "file",
4856 tree_argv,
4857 NULL,
4858 tree_read,
4859 tree_draw,
4860 tree_request,
4861 tree_grep,
4862 tree_select,
4863 tree_prepare,
4864 };
4866 static bool
4867 blob_read(struct view *view, char *line)
4868 {
4869 if (!line)
4870 return TRUE;
4871 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4872 }
4874 static enum request
4875 blob_request(struct view *view, enum request request, struct line *line)
4876 {
4877 switch (request) {
4878 case REQ_EDIT:
4879 open_blob_editor(view->vid);
4880 return REQ_NONE;
4881 default:
4882 return pager_request(view, request, line);
4883 }
4884 }
4886 static const char *blob_argv[SIZEOF_ARG] = {
4887 "git", "cat-file", "blob", "%(blob)", NULL
4888 };
4890 static struct view_ops blob_ops = {
4891 "line",
4892 blob_argv,
4893 NULL,
4894 blob_read,
4895 pager_draw,
4896 blob_request,
4897 pager_grep,
4898 pager_select,
4899 };
4901 /*
4902 * Blame backend
4903 *
4904 * Loading the blame view is a two phase job:
4905 *
4906 * 1. File content is read either using opt_file from the
4907 * filesystem or using git-cat-file.
4908 * 2. Then blame information is incrementally added by
4909 * reading output from git-blame.
4910 */
4912 struct blame_commit {
4913 char id[SIZEOF_REV]; /* SHA1 ID. */
4914 char title[128]; /* First line of the commit message. */
4915 const char *author; /* Author of the commit. */
4916 struct time time; /* Date from the author ident. */
4917 char filename[128]; /* Name of file. */
4918 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4919 char parent_filename[128]; /* Parent/previous name of file. */
4920 };
4922 struct blame {
4923 struct blame_commit *commit;
4924 unsigned long lineno;
4925 char text[1];
4926 };
4928 static bool
4929 blame_open(struct view *view)
4930 {
4931 char path[SIZEOF_STR];
4932 size_t i;
4934 if (!view->prev && *opt_prefix) {
4935 string_copy(path, opt_file);
4936 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4937 return FALSE;
4938 }
4940 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4941 const char *blame_cat_file_argv[] = {
4942 "git", "cat-file", "blob", path, NULL
4943 };
4945 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4946 !start_update(view, blame_cat_file_argv, opt_cdup))
4947 return FALSE;
4948 }
4950 /* First pass: remove multiple references to the same commit. */
4951 for (i = 0; i < view->lines; i++) {
4952 struct blame *blame = view->line[i].data;
4954 if (blame->commit && blame->commit->id[0])
4955 blame->commit->id[0] = 0;
4956 else
4957 blame->commit = NULL;
4958 }
4960 /* Second pass: free existing references. */
4961 for (i = 0; i < view->lines; i++) {
4962 struct blame *blame = view->line[i].data;
4964 if (blame->commit)
4965 free(blame->commit);
4966 }
4968 setup_update(view, opt_file);
4969 string_format(view->ref, "%s ...", opt_file);
4971 return TRUE;
4972 }
4974 static struct blame_commit *
4975 get_blame_commit(struct view *view, const char *id)
4976 {
4977 size_t i;
4979 for (i = 0; i < view->lines; i++) {
4980 struct blame *blame = view->line[i].data;
4982 if (!blame->commit)
4983 continue;
4985 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4986 return blame->commit;
4987 }
4989 {
4990 struct blame_commit *commit = calloc(1, sizeof(*commit));
4992 if (commit)
4993 string_ncopy(commit->id, id, SIZEOF_REV);
4994 return commit;
4995 }
4996 }
4998 static bool
4999 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5000 {
5001 const char *pos = *posref;
5003 *posref = NULL;
5004 pos = strchr(pos + 1, ' ');
5005 if (!pos || !isdigit(pos[1]))
5006 return FALSE;
5007 *number = atoi(pos + 1);
5008 if (*number < min || *number > max)
5009 return FALSE;
5011 *posref = pos;
5012 return TRUE;
5013 }
5015 static struct blame_commit *
5016 parse_blame_commit(struct view *view, const char *text, int *blamed)
5017 {
5018 struct blame_commit *commit;
5019 struct blame *blame;
5020 const char *pos = text + SIZEOF_REV - 2;
5021 size_t orig_lineno = 0;
5022 size_t lineno;
5023 size_t group;
5025 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5026 return NULL;
5028 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5029 !parse_number(&pos, &lineno, 1, view->lines) ||
5030 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5031 return NULL;
5033 commit = get_blame_commit(view, text);
5034 if (!commit)
5035 return NULL;
5037 *blamed += group;
5038 while (group--) {
5039 struct line *line = &view->line[lineno + group - 1];
5041 blame = line->data;
5042 blame->commit = commit;
5043 blame->lineno = orig_lineno + group - 1;
5044 line->dirty = 1;
5045 }
5047 return commit;
5048 }
5050 static bool
5051 blame_read_file(struct view *view, const char *line, bool *read_file)
5052 {
5053 if (!line) {
5054 const char *blame_argv[] = {
5055 "git", "blame", "--incremental",
5056 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5057 };
5059 if (view->lines == 0 && !view->prev)
5060 die("No blame exist for %s", view->vid);
5062 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5063 report("Failed to load blame data");
5064 return TRUE;
5065 }
5067 *read_file = FALSE;
5068 return FALSE;
5070 } else {
5071 size_t linelen = strlen(line);
5072 struct blame *blame = malloc(sizeof(*blame) + linelen);
5074 if (!blame)
5075 return FALSE;
5077 blame->commit = NULL;
5078 strncpy(blame->text, line, linelen);
5079 blame->text[linelen] = 0;
5080 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5081 }
5082 }
5084 static bool
5085 match_blame_header(const char *name, char **line)
5086 {
5087 size_t namelen = strlen(name);
5088 bool matched = !strncmp(name, *line, namelen);
5090 if (matched)
5091 *line += namelen;
5093 return matched;
5094 }
5096 static bool
5097 blame_read(struct view *view, char *line)
5098 {
5099 static struct blame_commit *commit = NULL;
5100 static int blamed = 0;
5101 static bool read_file = TRUE;
5103 if (read_file)
5104 return blame_read_file(view, line, &read_file);
5106 if (!line) {
5107 /* Reset all! */
5108 commit = NULL;
5109 blamed = 0;
5110 read_file = TRUE;
5111 string_format(view->ref, "%s", view->vid);
5112 if (view_is_displayed(view)) {
5113 update_view_title(view);
5114 redraw_view_from(view, 0);
5115 }
5116 return TRUE;
5117 }
5119 if (!commit) {
5120 commit = parse_blame_commit(view, line, &blamed);
5121 string_format(view->ref, "%s %2d%%", view->vid,
5122 view->lines ? blamed * 100 / view->lines : 0);
5124 } else if (match_blame_header("author ", &line)) {
5125 commit->author = get_author(line);
5127 } else if (match_blame_header("author-time ", &line)) {
5128 parse_timesec(&commit->time, line);
5130 } else if (match_blame_header("author-tz ", &line)) {
5131 parse_timezone(&commit->time, line);
5133 } else if (match_blame_header("summary ", &line)) {
5134 string_ncopy(commit->title, line, strlen(line));
5136 } else if (match_blame_header("previous ", &line)) {
5137 if (strlen(line) <= SIZEOF_REV)
5138 return FALSE;
5139 string_copy_rev(commit->parent_id, line);
5140 line += SIZEOF_REV;
5141 string_ncopy(commit->parent_filename, line, strlen(line));
5143 } else if (match_blame_header("filename ", &line)) {
5144 string_ncopy(commit->filename, line, strlen(line));
5145 commit = NULL;
5146 }
5148 return TRUE;
5149 }
5151 static bool
5152 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5153 {
5154 struct blame *blame = line->data;
5155 struct time *time = NULL;
5156 const char *id = NULL, *author = NULL;
5158 if (blame->commit && *blame->commit->filename) {
5159 id = blame->commit->id;
5160 author = blame->commit->author;
5161 time = &blame->commit->time;
5162 }
5164 if (opt_date && draw_date(view, time))
5165 return TRUE;
5167 if (opt_author && draw_author(view, author))
5168 return TRUE;
5170 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5171 return TRUE;
5173 if (draw_lineno(view, lineno))
5174 return TRUE;
5176 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5177 return TRUE;
5178 }
5180 static bool
5181 check_blame_commit(struct blame *blame, bool check_null_id)
5182 {
5183 if (!blame->commit)
5184 report("Commit data not loaded yet");
5185 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5186 report("No commit exist for the selected line");
5187 else
5188 return TRUE;
5189 return FALSE;
5190 }
5192 static void
5193 setup_blame_parent_line(struct view *view, struct blame *blame)
5194 {
5195 char from[SIZEOF_REF + SIZEOF_STR];
5196 char to[SIZEOF_REF + SIZEOF_STR];
5197 const char *diff_tree_argv[] = {
5198 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5199 "-U0", from, to, "--", NULL
5200 };
5201 struct io io;
5202 int parent_lineno = -1;
5203 int blamed_lineno = -1;
5204 char *line;
5206 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5207 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5208 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5209 return;
5211 while ((line = io_get(&io, '\n', TRUE))) {
5212 if (*line == '@') {
5213 char *pos = strchr(line, '+');
5215 parent_lineno = atoi(line + 4);
5216 if (pos)
5217 blamed_lineno = atoi(pos + 1);
5219 } else if (*line == '+' && parent_lineno != -1) {
5220 if (blame->lineno == blamed_lineno - 1 &&
5221 !strcmp(blame->text, line + 1)) {
5222 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5223 break;
5224 }
5225 blamed_lineno++;
5226 }
5227 }
5229 io_done(&io);
5230 }
5232 static enum request
5233 blame_request(struct view *view, enum request request, struct line *line)
5234 {
5235 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5236 struct blame *blame = line->data;
5238 switch (request) {
5239 case REQ_VIEW_BLAME:
5240 if (check_blame_commit(blame, TRUE)) {
5241 string_copy(opt_ref, blame->commit->id);
5242 string_copy(opt_file, blame->commit->filename);
5243 if (blame->lineno)
5244 view->lineno = blame->lineno;
5245 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5246 }
5247 break;
5249 case REQ_PARENT:
5250 if (!check_blame_commit(blame, TRUE))
5251 break;
5252 if (!*blame->commit->parent_id) {
5253 report("The selected commit has no parents");
5254 } else {
5255 string_copy_rev(opt_ref, blame->commit->parent_id);
5256 string_copy(opt_file, blame->commit->parent_filename);
5257 setup_blame_parent_line(view, blame);
5258 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5259 }
5260 break;
5262 case REQ_ENTER:
5263 if (!check_blame_commit(blame, FALSE))
5264 break;
5266 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5267 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5268 break;
5270 if (!strcmp(blame->commit->id, NULL_ID)) {
5271 struct view *diff = VIEW(REQ_VIEW_DIFF);
5272 const char *diff_index_argv[] = {
5273 "git", "diff-index", "--root", "--patch-with-stat",
5274 "-C", "-M", "HEAD", "--", view->vid, NULL
5275 };
5277 if (!*blame->commit->parent_id) {
5278 diff_index_argv[1] = "diff";
5279 diff_index_argv[2] = "--no-color";
5280 diff_index_argv[6] = "--";
5281 diff_index_argv[7] = "/dev/null";
5282 }
5284 if (!prepare_update(diff, diff_index_argv, NULL)) {
5285 report("Failed to allocate diff command");
5286 break;
5287 }
5288 flags |= OPEN_PREPARED;
5289 }
5291 open_view(view, REQ_VIEW_DIFF, flags);
5292 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5293 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5294 break;
5296 default:
5297 return request;
5298 }
5300 return REQ_NONE;
5301 }
5303 static bool
5304 blame_grep(struct view *view, struct line *line)
5305 {
5306 struct blame *blame = line->data;
5307 struct blame_commit *commit = blame->commit;
5308 const char *text[] = {
5309 blame->text,
5310 commit ? commit->title : "",
5311 commit ? commit->id : "",
5312 commit && opt_author ? commit->author : "",
5313 commit ? mkdate(&commit->time, opt_date) : "",
5314 NULL
5315 };
5317 return grep_text(view, text);
5318 }
5320 static void
5321 blame_select(struct view *view, struct line *line)
5322 {
5323 struct blame *blame = line->data;
5324 struct blame_commit *commit = blame->commit;
5326 if (!commit)
5327 return;
5329 if (!strcmp(commit->id, NULL_ID))
5330 string_ncopy(ref_commit, "HEAD", 4);
5331 else
5332 string_copy_rev(ref_commit, commit->id);
5333 }
5335 static struct view_ops blame_ops = {
5336 "line",
5337 NULL,
5338 blame_open,
5339 blame_read,
5340 blame_draw,
5341 blame_request,
5342 blame_grep,
5343 blame_select,
5344 };
5346 /*
5347 * Branch backend
5348 */
5350 struct branch {
5351 const char *author; /* Author of the last commit. */
5352 struct time time; /* Date of the last activity. */
5353 const struct ref *ref; /* Name and commit ID information. */
5354 };
5356 static const struct ref branch_all;
5358 static const enum sort_field branch_sort_fields[] = {
5359 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5360 };
5361 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5363 static int
5364 branch_compare(const void *l1, const void *l2)
5365 {
5366 const struct branch *branch1 = ((const struct line *) l1)->data;
5367 const struct branch *branch2 = ((const struct line *) l2)->data;
5369 switch (get_sort_field(branch_sort_state)) {
5370 case ORDERBY_DATE:
5371 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5373 case ORDERBY_AUTHOR:
5374 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5376 case ORDERBY_NAME:
5377 default:
5378 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5379 }
5380 }
5382 static bool
5383 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5384 {
5385 struct branch *branch = line->data;
5386 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5388 if (opt_date && draw_date(view, &branch->time))
5389 return TRUE;
5391 if (opt_author && draw_author(view, branch->author))
5392 return TRUE;
5394 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5395 return TRUE;
5396 }
5398 static enum request
5399 branch_request(struct view *view, enum request request, struct line *line)
5400 {
5401 struct branch *branch = line->data;
5403 switch (request) {
5404 case REQ_REFRESH:
5405 load_refs();
5406 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5407 return REQ_NONE;
5409 case REQ_TOGGLE_SORT_FIELD:
5410 case REQ_TOGGLE_SORT_ORDER:
5411 sort_view(view, request, &branch_sort_state, branch_compare);
5412 return REQ_NONE;
5414 case REQ_ENTER:
5415 {
5416 const struct ref *ref = branch->ref;
5417 const char *all_branches_argv[] = {
5418 "git", "log", "--no-color", "--pretty=raw", "--parents",
5419 "--topo-order",
5420 ref == &branch_all ? "--all" : ref->name, NULL
5421 };
5422 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5424 if (!prepare_update(main_view, all_branches_argv, NULL))
5425 report("Failed to load view of all branches");
5426 else
5427 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5428 return REQ_NONE;
5429 }
5430 default:
5431 return request;
5432 }
5433 }
5435 static bool
5436 branch_read(struct view *view, char *line)
5437 {
5438 static char id[SIZEOF_REV];
5439 struct branch *reference;
5440 size_t i;
5442 if (!line)
5443 return TRUE;
5445 switch (get_line_type(line)) {
5446 case LINE_COMMIT:
5447 string_copy_rev(id, line + STRING_SIZE("commit "));
5448 return TRUE;
5450 case LINE_AUTHOR:
5451 for (i = 0, reference = NULL; i < view->lines; i++) {
5452 struct branch *branch = view->line[i].data;
5454 if (strcmp(branch->ref->id, id))
5455 continue;
5457 view->line[i].dirty = TRUE;
5458 if (reference) {
5459 branch->author = reference->author;
5460 branch->time = reference->time;
5461 continue;
5462 }
5464 parse_author_line(line + STRING_SIZE("author "),
5465 &branch->author, &branch->time);
5466 reference = branch;
5467 }
5468 return TRUE;
5470 default:
5471 return TRUE;
5472 }
5474 }
5476 static bool
5477 branch_open_visitor(void *data, const struct ref *ref)
5478 {
5479 struct view *view = data;
5480 struct branch *branch;
5482 if (ref->tag || ref->ltag || ref->remote)
5483 return TRUE;
5485 branch = calloc(1, sizeof(*branch));
5486 if (!branch)
5487 return FALSE;
5489 branch->ref = ref;
5490 return !!add_line_data(view, branch, LINE_DEFAULT);
5491 }
5493 static bool
5494 branch_open(struct view *view)
5495 {
5496 const char *branch_log[] = {
5497 "git", "log", "--no-color", "--pretty=raw",
5498 "--simplify-by-decoration", "--all", NULL
5499 };
5501 if (!start_update(view, branch_log, NULL)) {
5502 report("Failed to load branch data");
5503 return TRUE;
5504 }
5506 setup_update(view, view->id);
5507 branch_open_visitor(view, &branch_all);
5508 foreach_ref(branch_open_visitor, view);
5509 view->p_restore = TRUE;
5511 return TRUE;
5512 }
5514 static bool
5515 branch_grep(struct view *view, struct line *line)
5516 {
5517 struct branch *branch = line->data;
5518 const char *text[] = {
5519 branch->ref->name,
5520 branch->author,
5521 NULL
5522 };
5524 return grep_text(view, text);
5525 }
5527 static void
5528 branch_select(struct view *view, struct line *line)
5529 {
5530 struct branch *branch = line->data;
5532 string_copy_rev(view->ref, branch->ref->id);
5533 string_copy_rev(ref_commit, branch->ref->id);
5534 string_copy_rev(ref_head, branch->ref->id);
5535 string_copy_rev(ref_branch, branch->ref->name);
5536 }
5538 static struct view_ops branch_ops = {
5539 "branch",
5540 NULL,
5541 branch_open,
5542 branch_read,
5543 branch_draw,
5544 branch_request,
5545 branch_grep,
5546 branch_select,
5547 };
5549 /*
5550 * Status backend
5551 */
5553 struct status {
5554 char status;
5555 struct {
5556 mode_t mode;
5557 char rev[SIZEOF_REV];
5558 char name[SIZEOF_STR];
5559 } old;
5560 struct {
5561 mode_t mode;
5562 char rev[SIZEOF_REV];
5563 char name[SIZEOF_STR];
5564 } new;
5565 };
5567 static char status_onbranch[SIZEOF_STR];
5568 static struct status stage_status;
5569 static enum line_type stage_line_type;
5570 static size_t stage_chunks;
5571 static int *stage_chunk;
5573 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5575 /* This should work even for the "On branch" line. */
5576 static inline bool
5577 status_has_none(struct view *view, struct line *line)
5578 {
5579 return line < view->line + view->lines && !line[1].data;
5580 }
5582 /* Get fields from the diff line:
5583 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5584 */
5585 static inline bool
5586 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5587 {
5588 const char *old_mode = buf + 1;
5589 const char *new_mode = buf + 8;
5590 const char *old_rev = buf + 15;
5591 const char *new_rev = buf + 56;
5592 const char *status = buf + 97;
5594 if (bufsize < 98 ||
5595 old_mode[-1] != ':' ||
5596 new_mode[-1] != ' ' ||
5597 old_rev[-1] != ' ' ||
5598 new_rev[-1] != ' ' ||
5599 status[-1] != ' ')
5600 return FALSE;
5602 file->status = *status;
5604 string_copy_rev(file->old.rev, old_rev);
5605 string_copy_rev(file->new.rev, new_rev);
5607 file->old.mode = strtoul(old_mode, NULL, 8);
5608 file->new.mode = strtoul(new_mode, NULL, 8);
5610 file->old.name[0] = file->new.name[0] = 0;
5612 return TRUE;
5613 }
5615 static bool
5616 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5617 {
5618 struct status *unmerged = NULL;
5619 char *buf;
5620 struct io io;
5622 if (!io_run(&io, IO_RD, opt_cdup, argv))
5623 return FALSE;
5625 add_line_data(view, NULL, type);
5627 while ((buf = io_get(&io, 0, TRUE))) {
5628 struct status *file = unmerged;
5630 if (!file) {
5631 file = calloc(1, sizeof(*file));
5632 if (!file || !add_line_data(view, file, type))
5633 goto error_out;
5634 }
5636 /* Parse diff info part. */
5637 if (status) {
5638 file->status = status;
5639 if (status == 'A')
5640 string_copy(file->old.rev, NULL_ID);
5642 } else if (!file->status || file == unmerged) {
5643 if (!status_get_diff(file, buf, strlen(buf)))
5644 goto error_out;
5646 buf = io_get(&io, 0, TRUE);
5647 if (!buf)
5648 break;
5650 /* Collapse all modified entries that follow an
5651 * associated unmerged entry. */
5652 if (unmerged == file) {
5653 unmerged->status = 'U';
5654 unmerged = NULL;
5655 } else if (file->status == 'U') {
5656 unmerged = file;
5657 }
5658 }
5660 /* Grab the old name for rename/copy. */
5661 if (!*file->old.name &&
5662 (file->status == 'R' || file->status == 'C')) {
5663 string_ncopy(file->old.name, buf, strlen(buf));
5665 buf = io_get(&io, 0, TRUE);
5666 if (!buf)
5667 break;
5668 }
5670 /* git-ls-files just delivers a NUL separated list of
5671 * file names similar to the second half of the
5672 * git-diff-* output. */
5673 string_ncopy(file->new.name, buf, strlen(buf));
5674 if (!*file->old.name)
5675 string_copy(file->old.name, file->new.name);
5676 file = NULL;
5677 }
5679 if (io_error(&io)) {
5680 error_out:
5681 io_done(&io);
5682 return FALSE;
5683 }
5685 if (!view->line[view->lines - 1].data)
5686 add_line_data(view, NULL, LINE_STAT_NONE);
5688 io_done(&io);
5689 return TRUE;
5690 }
5692 /* Don't show unmerged entries in the staged section. */
5693 static const char *status_diff_index_argv[] = {
5694 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5695 "--cached", "-M", "HEAD", NULL
5696 };
5698 static const char *status_diff_files_argv[] = {
5699 "git", "diff-files", "-z", NULL
5700 };
5702 static const char *status_list_other_argv[] = {
5703 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5704 };
5706 static const char *status_list_no_head_argv[] = {
5707 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5708 };
5710 static const char *update_index_argv[] = {
5711 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5712 };
5714 /* Restore the previous line number to stay in the context or select a
5715 * line with something that can be updated. */
5716 static void
5717 status_restore(struct view *view)
5718 {
5719 if (view->p_lineno >= view->lines)
5720 view->p_lineno = view->lines - 1;
5721 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5722 view->p_lineno++;
5723 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5724 view->p_lineno--;
5726 /* If the above fails, always skip the "On branch" line. */
5727 if (view->p_lineno < view->lines)
5728 view->lineno = view->p_lineno;
5729 else
5730 view->lineno = 1;
5732 if (view->lineno < view->offset)
5733 view->offset = view->lineno;
5734 else if (view->offset + view->height <= view->lineno)
5735 view->offset = view->lineno - view->height + 1;
5737 view->p_restore = FALSE;
5738 }
5740 static void
5741 status_update_onbranch(void)
5742 {
5743 static const char *paths[][2] = {
5744 { "rebase-apply/rebasing", "Rebasing" },
5745 { "rebase-apply/applying", "Applying mailbox" },
5746 { "rebase-apply/", "Rebasing mailbox" },
5747 { "rebase-merge/interactive", "Interactive rebase" },
5748 { "rebase-merge/", "Rebase merge" },
5749 { "MERGE_HEAD", "Merging" },
5750 { "BISECT_LOG", "Bisecting" },
5751 { "HEAD", "On branch" },
5752 };
5753 char buf[SIZEOF_STR];
5754 struct stat stat;
5755 int i;
5757 if (is_initial_commit()) {
5758 string_copy(status_onbranch, "Initial commit");
5759 return;
5760 }
5762 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5763 char *head = opt_head;
5765 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5766 lstat(buf, &stat) < 0)
5767 continue;
5769 if (!*opt_head) {
5770 struct io io;
5772 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5773 io_read_buf(&io, buf, sizeof(buf))) {
5774 head = buf;
5775 if (!prefixcmp(head, "refs/heads/"))
5776 head += STRING_SIZE("refs/heads/");
5777 }
5778 }
5780 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5781 string_copy(status_onbranch, opt_head);
5782 return;
5783 }
5785 string_copy(status_onbranch, "Not currently on any branch");
5786 }
5788 /* First parse staged info using git-diff-index(1), then parse unstaged
5789 * info using git-diff-files(1), and finally untracked files using
5790 * git-ls-files(1). */
5791 static bool
5792 status_open(struct view *view)
5793 {
5794 reset_view(view);
5796 add_line_data(view, NULL, LINE_STAT_HEAD);
5797 status_update_onbranch();
5799 io_run_bg(update_index_argv);
5801 if (is_initial_commit()) {
5802 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5803 return FALSE;
5804 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5805 return FALSE;
5806 }
5808 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5809 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5810 return FALSE;
5812 /* Restore the exact position or use the specialized restore
5813 * mode? */
5814 if (!view->p_restore)
5815 status_restore(view);
5816 return TRUE;
5817 }
5819 static bool
5820 status_draw(struct view *view, struct line *line, unsigned int lineno)
5821 {
5822 struct status *status = line->data;
5823 enum line_type type;
5824 const char *text;
5826 if (!status) {
5827 switch (line->type) {
5828 case LINE_STAT_STAGED:
5829 type = LINE_STAT_SECTION;
5830 text = "Changes to be committed:";
5831 break;
5833 case LINE_STAT_UNSTAGED:
5834 type = LINE_STAT_SECTION;
5835 text = "Changed but not updated:";
5836 break;
5838 case LINE_STAT_UNTRACKED:
5839 type = LINE_STAT_SECTION;
5840 text = "Untracked files:";
5841 break;
5843 case LINE_STAT_NONE:
5844 type = LINE_DEFAULT;
5845 text = " (no files)";
5846 break;
5848 case LINE_STAT_HEAD:
5849 type = LINE_STAT_HEAD;
5850 text = status_onbranch;
5851 break;
5853 default:
5854 return FALSE;
5855 }
5856 } else {
5857 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5859 buf[0] = status->status;
5860 if (draw_text(view, line->type, buf, TRUE))
5861 return TRUE;
5862 type = LINE_DEFAULT;
5863 text = status->new.name;
5864 }
5866 draw_text(view, type, text, TRUE);
5867 return TRUE;
5868 }
5870 static enum request
5871 status_load_error(struct view *view, struct view *stage, const char *path)
5872 {
5873 if (displayed_views() == 2 || display[current_view] != view)
5874 maximize_view(view);
5875 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5876 return REQ_NONE;
5877 }
5879 static enum request
5880 status_enter(struct view *view, struct line *line)
5881 {
5882 struct status *status = line->data;
5883 const char *oldpath = status ? status->old.name : NULL;
5884 /* Diffs for unmerged entries are empty when passing the new
5885 * path, so leave it empty. */
5886 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5887 const char *info;
5888 enum open_flags split;
5889 struct view *stage = VIEW(REQ_VIEW_STAGE);
5891 if (line->type == LINE_STAT_NONE ||
5892 (!status && line[1].type == LINE_STAT_NONE)) {
5893 report("No file to diff");
5894 return REQ_NONE;
5895 }
5897 switch (line->type) {
5898 case LINE_STAT_STAGED:
5899 if (is_initial_commit()) {
5900 const char *no_head_diff_argv[] = {
5901 "git", "diff", "--no-color", "--patch-with-stat",
5902 "--", "/dev/null", newpath, NULL
5903 };
5905 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5906 return status_load_error(view, stage, newpath);
5907 } else {
5908 const char *index_show_argv[] = {
5909 "git", "diff-index", "--root", "--patch-with-stat",
5910 "-C", "-M", "--cached", "HEAD", "--",
5911 oldpath, newpath, NULL
5912 };
5914 if (!prepare_update(stage, index_show_argv, opt_cdup))
5915 return status_load_error(view, stage, newpath);
5916 }
5918 if (status)
5919 info = "Staged changes to %s";
5920 else
5921 info = "Staged changes";
5922 break;
5924 case LINE_STAT_UNSTAGED:
5925 {
5926 const char *files_show_argv[] = {
5927 "git", "diff-files", "--root", "--patch-with-stat",
5928 "-C", "-M", "--", oldpath, newpath, NULL
5929 };
5931 if (!prepare_update(stage, files_show_argv, opt_cdup))
5932 return status_load_error(view, stage, newpath);
5933 if (status)
5934 info = "Unstaged changes to %s";
5935 else
5936 info = "Unstaged changes";
5937 break;
5938 }
5939 case LINE_STAT_UNTRACKED:
5940 if (!newpath) {
5941 report("No file to show");
5942 return REQ_NONE;
5943 }
5945 if (!suffixcmp(status->new.name, -1, "/")) {
5946 report("Cannot display a directory");
5947 return REQ_NONE;
5948 }
5950 if (!prepare_update_file(stage, newpath))
5951 return status_load_error(view, stage, newpath);
5952 info = "Untracked file %s";
5953 break;
5955 case LINE_STAT_HEAD:
5956 return REQ_NONE;
5958 default:
5959 die("line type %d not handled in switch", line->type);
5960 }
5962 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5963 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5964 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5965 if (status) {
5966 stage_status = *status;
5967 } else {
5968 memset(&stage_status, 0, sizeof(stage_status));
5969 }
5971 stage_line_type = line->type;
5972 stage_chunks = 0;
5973 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5974 }
5976 return REQ_NONE;
5977 }
5979 static bool
5980 status_exists(struct status *status, enum line_type type)
5981 {
5982 struct view *view = VIEW(REQ_VIEW_STATUS);
5983 unsigned long lineno;
5985 for (lineno = 0; lineno < view->lines; lineno++) {
5986 struct line *line = &view->line[lineno];
5987 struct status *pos = line->data;
5989 if (line->type != type)
5990 continue;
5991 if (!pos && (!status || !status->status) && line[1].data) {
5992 select_view_line(view, lineno);
5993 return TRUE;
5994 }
5995 if (pos && !strcmp(status->new.name, pos->new.name)) {
5996 select_view_line(view, lineno);
5997 return TRUE;
5998 }
5999 }
6001 return FALSE;
6002 }
6005 static bool
6006 status_update_prepare(struct io *io, enum line_type type)
6007 {
6008 const char *staged_argv[] = {
6009 "git", "update-index", "-z", "--index-info", NULL
6010 };
6011 const char *others_argv[] = {
6012 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6013 };
6015 switch (type) {
6016 case LINE_STAT_STAGED:
6017 return io_run(io, IO_WR, opt_cdup, staged_argv);
6019 case LINE_STAT_UNSTAGED:
6020 case LINE_STAT_UNTRACKED:
6021 return io_run(io, IO_WR, opt_cdup, others_argv);
6023 default:
6024 die("line type %d not handled in switch", type);
6025 return FALSE;
6026 }
6027 }
6029 static bool
6030 status_update_write(struct io *io, struct status *status, enum line_type type)
6031 {
6032 char buf[SIZEOF_STR];
6033 size_t bufsize = 0;
6035 switch (type) {
6036 case LINE_STAT_STAGED:
6037 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6038 status->old.mode,
6039 status->old.rev,
6040 status->old.name, 0))
6041 return FALSE;
6042 break;
6044 case LINE_STAT_UNSTAGED:
6045 case LINE_STAT_UNTRACKED:
6046 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6047 return FALSE;
6048 break;
6050 default:
6051 die("line type %d not handled in switch", type);
6052 }
6054 return io_write(io, buf, bufsize);
6055 }
6057 static bool
6058 status_update_file(struct status *status, enum line_type type)
6059 {
6060 struct io io;
6061 bool result;
6063 if (!status_update_prepare(&io, type))
6064 return FALSE;
6066 result = status_update_write(&io, status, type);
6067 return io_done(&io) && result;
6068 }
6070 static bool
6071 status_update_files(struct view *view, struct line *line)
6072 {
6073 char buf[sizeof(view->ref)];
6074 struct io io;
6075 bool result = TRUE;
6076 struct line *pos = view->line + view->lines;
6077 int files = 0;
6078 int file, done;
6079 int cursor_y = -1, cursor_x = -1;
6081 if (!status_update_prepare(&io, line->type))
6082 return FALSE;
6084 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6085 files++;
6087 string_copy(buf, view->ref);
6088 getsyx(cursor_y, cursor_x);
6089 for (file = 0, done = 5; result && file < files; line++, file++) {
6090 int almost_done = file * 100 / files;
6092 if (almost_done > done) {
6093 done = almost_done;
6094 string_format(view->ref, "updating file %u of %u (%d%% done)",
6095 file, files, done);
6096 update_view_title(view);
6097 setsyx(cursor_y, cursor_x);
6098 doupdate();
6099 }
6100 result = status_update_write(&io, line->data, line->type);
6101 }
6102 string_copy(view->ref, buf);
6104 return io_done(&io) && result;
6105 }
6107 static bool
6108 status_update(struct view *view)
6109 {
6110 struct line *line = &view->line[view->lineno];
6112 assert(view->lines);
6114 if (!line->data) {
6115 /* This should work even for the "On branch" line. */
6116 if (line < view->line + view->lines && !line[1].data) {
6117 report("Nothing to update");
6118 return FALSE;
6119 }
6121 if (!status_update_files(view, line + 1)) {
6122 report("Failed to update file status");
6123 return FALSE;
6124 }
6126 } else if (!status_update_file(line->data, line->type)) {
6127 report("Failed to update file status");
6128 return FALSE;
6129 }
6131 return TRUE;
6132 }
6134 static bool
6135 status_revert(struct status *status, enum line_type type, bool has_none)
6136 {
6137 if (!status || type != LINE_STAT_UNSTAGED) {
6138 if (type == LINE_STAT_STAGED) {
6139 report("Cannot revert changes to staged files");
6140 } else if (type == LINE_STAT_UNTRACKED) {
6141 report("Cannot revert changes to untracked files");
6142 } else if (has_none) {
6143 report("Nothing to revert");
6144 } else {
6145 report("Cannot revert changes to multiple files");
6146 }
6148 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6149 char mode[10] = "100644";
6150 const char *reset_argv[] = {
6151 "git", "update-index", "--cacheinfo", mode,
6152 status->old.rev, status->old.name, NULL
6153 };
6154 const char *checkout_argv[] = {
6155 "git", "checkout", "--", status->old.name, NULL
6156 };
6158 if (status->status == 'U') {
6159 string_format(mode, "%5o", status->old.mode);
6161 if (status->old.mode == 0 && status->new.mode == 0) {
6162 reset_argv[2] = "--force-remove";
6163 reset_argv[3] = status->old.name;
6164 reset_argv[4] = NULL;
6165 }
6167 if (!io_run_fg(reset_argv, opt_cdup))
6168 return FALSE;
6169 if (status->old.mode == 0 && status->new.mode == 0)
6170 return TRUE;
6171 }
6173 return io_run_fg(checkout_argv, opt_cdup);
6174 }
6176 return FALSE;
6177 }
6179 static enum request
6180 status_request(struct view *view, enum request request, struct line *line)
6181 {
6182 struct status *status = line->data;
6184 switch (request) {
6185 case REQ_STATUS_UPDATE:
6186 if (!status_update(view))
6187 return REQ_NONE;
6188 break;
6190 case REQ_STATUS_REVERT:
6191 if (!status_revert(status, line->type, status_has_none(view, line)))
6192 return REQ_NONE;
6193 break;
6195 case REQ_STATUS_MERGE:
6196 if (!status || status->status != 'U') {
6197 report("Merging only possible for files with unmerged status ('U').");
6198 return REQ_NONE;
6199 }
6200 open_mergetool(status->new.name);
6201 break;
6203 case REQ_EDIT:
6204 if (!status)
6205 return request;
6206 if (status->status == 'D') {
6207 report("File has been deleted.");
6208 return REQ_NONE;
6209 }
6211 open_editor(status->new.name);
6212 break;
6214 case REQ_VIEW_BLAME:
6215 if (status)
6216 opt_ref[0] = 0;
6217 return request;
6219 case REQ_ENTER:
6220 /* After returning the status view has been split to
6221 * show the stage view. No further reloading is
6222 * necessary. */
6223 return status_enter(view, line);
6225 case REQ_REFRESH:
6226 /* Simply reload the view. */
6227 break;
6229 default:
6230 return request;
6231 }
6233 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6235 return REQ_NONE;
6236 }
6238 static void
6239 status_select(struct view *view, struct line *line)
6240 {
6241 struct status *status = line->data;
6242 char file[SIZEOF_STR] = "all files";
6243 const char *text;
6244 const char *key;
6246 if (status && !string_format(file, "'%s'", status->new.name))
6247 return;
6249 if (!status && line[1].type == LINE_STAT_NONE)
6250 line++;
6252 switch (line->type) {
6253 case LINE_STAT_STAGED:
6254 text = "Press %s to unstage %s for commit";
6255 break;
6257 case LINE_STAT_UNSTAGED:
6258 text = "Press %s to stage %s for commit";
6259 break;
6261 case LINE_STAT_UNTRACKED:
6262 text = "Press %s to stage %s for addition";
6263 break;
6265 case LINE_STAT_HEAD:
6266 case LINE_STAT_NONE:
6267 text = "Nothing to update";
6268 break;
6270 default:
6271 die("line type %d not handled in switch", line->type);
6272 }
6274 if (status && status->status == 'U') {
6275 text = "Press %s to resolve conflict in %s";
6276 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6278 } else {
6279 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6280 }
6282 string_format(view->ref, text, key, file);
6283 if (status)
6284 string_copy(opt_file, status->new.name);
6285 }
6287 static bool
6288 status_grep(struct view *view, struct line *line)
6289 {
6290 struct status *status = line->data;
6292 if (status) {
6293 const char buf[2] = { status->status, 0 };
6294 const char *text[] = { status->new.name, buf, NULL };
6296 return grep_text(view, text);
6297 }
6299 return FALSE;
6300 }
6302 static struct view_ops status_ops = {
6303 "file",
6304 NULL,
6305 status_open,
6306 NULL,
6307 status_draw,
6308 status_request,
6309 status_grep,
6310 status_select,
6311 };
6314 static bool
6315 stage_diff_write(struct io *io, struct line *line, struct line *end)
6316 {
6317 while (line < end) {
6318 if (!io_write(io, line->data, strlen(line->data)) ||
6319 !io_write(io, "\n", 1))
6320 return FALSE;
6321 line++;
6322 if (line->type == LINE_DIFF_CHUNK ||
6323 line->type == LINE_DIFF_HEADER)
6324 break;
6325 }
6327 return TRUE;
6328 }
6330 static struct line *
6331 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6332 {
6333 for (; view->line < line; line--)
6334 if (line->type == type)
6335 return line;
6337 return NULL;
6338 }
6340 static bool
6341 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6342 {
6343 const char *apply_argv[SIZEOF_ARG] = {
6344 "git", "apply", "--whitespace=nowarn", NULL
6345 };
6346 struct line *diff_hdr;
6347 struct io io;
6348 int argc = 3;
6350 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6351 if (!diff_hdr)
6352 return FALSE;
6354 if (!revert)
6355 apply_argv[argc++] = "--cached";
6356 if (revert || stage_line_type == LINE_STAT_STAGED)
6357 apply_argv[argc++] = "-R";
6358 apply_argv[argc++] = "-";
6359 apply_argv[argc++] = NULL;
6360 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6361 return FALSE;
6363 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6364 !stage_diff_write(&io, chunk, view->line + view->lines))
6365 chunk = NULL;
6367 io_done(&io);
6368 io_run_bg(update_index_argv);
6370 return chunk ? TRUE : FALSE;
6371 }
6373 static bool
6374 stage_update(struct view *view, struct line *line)
6375 {
6376 struct line *chunk = NULL;
6378 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6379 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6381 if (chunk) {
6382 if (!stage_apply_chunk(view, chunk, FALSE)) {
6383 report("Failed to apply chunk");
6384 return FALSE;
6385 }
6387 } else if (!stage_status.status) {
6388 view = VIEW(REQ_VIEW_STATUS);
6390 for (line = view->line; line < view->line + view->lines; line++)
6391 if (line->type == stage_line_type)
6392 break;
6394 if (!status_update_files(view, line + 1)) {
6395 report("Failed to update files");
6396 return FALSE;
6397 }
6399 } else if (!status_update_file(&stage_status, stage_line_type)) {
6400 report("Failed to update file");
6401 return FALSE;
6402 }
6404 return TRUE;
6405 }
6407 static bool
6408 stage_revert(struct view *view, struct line *line)
6409 {
6410 struct line *chunk = NULL;
6412 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6413 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6415 if (chunk) {
6416 if (!prompt_yesno("Are you sure you want to revert changes?"))
6417 return FALSE;
6419 if (!stage_apply_chunk(view, chunk, TRUE)) {
6420 report("Failed to revert chunk");
6421 return FALSE;
6422 }
6423 return TRUE;
6425 } else {
6426 return status_revert(stage_status.status ? &stage_status : NULL,
6427 stage_line_type, FALSE);
6428 }
6429 }
6432 static void
6433 stage_next(struct view *view, struct line *line)
6434 {
6435 int i;
6437 if (!stage_chunks) {
6438 for (line = view->line; line < view->line + view->lines; line++) {
6439 if (line->type != LINE_DIFF_CHUNK)
6440 continue;
6442 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6443 report("Allocation failure");
6444 return;
6445 }
6447 stage_chunk[stage_chunks++] = line - view->line;
6448 }
6449 }
6451 for (i = 0; i < stage_chunks; i++) {
6452 if (stage_chunk[i] > view->lineno) {
6453 do_scroll_view(view, stage_chunk[i] - view->lineno);
6454 report("Chunk %d of %d", i + 1, stage_chunks);
6455 return;
6456 }
6457 }
6459 report("No next chunk found");
6460 }
6462 static enum request
6463 stage_request(struct view *view, enum request request, struct line *line)
6464 {
6465 switch (request) {
6466 case REQ_STATUS_UPDATE:
6467 if (!stage_update(view, line))
6468 return REQ_NONE;
6469 break;
6471 case REQ_STATUS_REVERT:
6472 if (!stage_revert(view, line))
6473 return REQ_NONE;
6474 break;
6476 case REQ_STAGE_NEXT:
6477 if (stage_line_type == LINE_STAT_UNTRACKED) {
6478 report("File is untracked; press %s to add",
6479 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6480 return REQ_NONE;
6481 }
6482 stage_next(view, line);
6483 return REQ_NONE;
6485 case REQ_EDIT:
6486 if (!stage_status.new.name[0])
6487 return request;
6488 if (stage_status.status == 'D') {
6489 report("File has been deleted.");
6490 return REQ_NONE;
6491 }
6493 open_editor(stage_status.new.name);
6494 break;
6496 case REQ_REFRESH:
6497 /* Reload everything ... */
6498 break;
6500 case REQ_VIEW_BLAME:
6501 if (stage_status.new.name[0]) {
6502 string_copy(opt_file, stage_status.new.name);
6503 opt_ref[0] = 0;
6504 }
6505 return request;
6507 case REQ_ENTER:
6508 return pager_request(view, request, line);
6510 default:
6511 return request;
6512 }
6514 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6515 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6517 /* Check whether the staged entry still exists, and close the
6518 * stage view if it doesn't. */
6519 if (!status_exists(&stage_status, stage_line_type)) {
6520 status_restore(VIEW(REQ_VIEW_STATUS));
6521 return REQ_VIEW_CLOSE;
6522 }
6524 if (stage_line_type == LINE_STAT_UNTRACKED) {
6525 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6526 report("Cannot display a directory");
6527 return REQ_NONE;
6528 }
6530 if (!prepare_update_file(view, stage_status.new.name)) {
6531 report("Failed to open file: %s", strerror(errno));
6532 return REQ_NONE;
6533 }
6534 }
6535 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6537 return REQ_NONE;
6538 }
6540 static struct view_ops stage_ops = {
6541 "line",
6542 NULL,
6543 NULL,
6544 pager_read,
6545 pager_draw,
6546 stage_request,
6547 pager_grep,
6548 pager_select,
6549 };
6552 /*
6553 * Revision graph
6554 */
6556 struct commit {
6557 char id[SIZEOF_REV]; /* SHA1 ID. */
6558 char title[128]; /* First line of the commit message. */
6559 const char *author; /* Author of the commit. */
6560 struct time time; /* Date from the author ident. */
6561 struct ref_list *refs; /* Repository references. */
6562 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6563 size_t graph_size; /* The width of the graph array. */
6564 bool has_parents; /* Rewritten --parents seen. */
6565 };
6567 /* Size of rev graph with no "padding" columns */
6568 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6570 struct rev_graph {
6571 struct rev_graph *prev, *next, *parents;
6572 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6573 size_t size;
6574 struct commit *commit;
6575 size_t pos;
6576 unsigned int boundary:1;
6577 };
6579 /* Parents of the commit being visualized. */
6580 static struct rev_graph graph_parents[4];
6582 /* The current stack of revisions on the graph. */
6583 static struct rev_graph graph_stacks[4] = {
6584 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6585 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6586 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6587 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6588 };
6590 static inline bool
6591 graph_parent_is_merge(struct rev_graph *graph)
6592 {
6593 return graph->parents->size > 1;
6594 }
6596 static inline void
6597 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6598 {
6599 struct commit *commit = graph->commit;
6601 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6602 commit->graph[commit->graph_size++] = symbol;
6603 }
6605 static void
6606 clear_rev_graph(struct rev_graph *graph)
6607 {
6608 graph->boundary = 0;
6609 graph->size = graph->pos = 0;
6610 graph->commit = NULL;
6611 memset(graph->parents, 0, sizeof(*graph->parents));
6612 }
6614 static void
6615 done_rev_graph(struct rev_graph *graph)
6616 {
6617 if (graph_parent_is_merge(graph) &&
6618 graph->pos < graph->size - 1 &&
6619 graph->next->size == graph->size + graph->parents->size - 1) {
6620 size_t i = graph->pos + graph->parents->size - 1;
6622 graph->commit->graph_size = i * 2;
6623 while (i < graph->next->size - 1) {
6624 append_to_rev_graph(graph, ' ');
6625 append_to_rev_graph(graph, '\\');
6626 i++;
6627 }
6628 }
6630 clear_rev_graph(graph);
6631 }
6633 static void
6634 push_rev_graph(struct rev_graph *graph, const char *parent)
6635 {
6636 int i;
6638 /* "Collapse" duplicate parents lines.
6639 *
6640 * FIXME: This needs to also update update the drawn graph but
6641 * for now it just serves as a method for pruning graph lines. */
6642 for (i = 0; i < graph->size; i++)
6643 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6644 return;
6646 if (graph->size < SIZEOF_REVITEMS) {
6647 string_copy_rev(graph->rev[graph->size++], parent);
6648 }
6649 }
6651 static chtype
6652 get_rev_graph_symbol(struct rev_graph *graph)
6653 {
6654 chtype symbol;
6656 if (graph->boundary)
6657 symbol = REVGRAPH_BOUND;
6658 else if (graph->parents->size == 0)
6659 symbol = REVGRAPH_INIT;
6660 else if (graph_parent_is_merge(graph))
6661 symbol = REVGRAPH_MERGE;
6662 else if (graph->pos >= graph->size)
6663 symbol = REVGRAPH_BRANCH;
6664 else
6665 symbol = REVGRAPH_COMMIT;
6667 return symbol;
6668 }
6670 static void
6671 draw_rev_graph(struct rev_graph *graph)
6672 {
6673 struct rev_filler {
6674 chtype separator, line;
6675 };
6676 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6677 static struct rev_filler fillers[] = {
6678 { ' ', '|' },
6679 { '`', '.' },
6680 { '\'', ' ' },
6681 { '/', ' ' },
6682 };
6683 chtype symbol = get_rev_graph_symbol(graph);
6684 struct rev_filler *filler;
6685 size_t i;
6687 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6688 filler = &fillers[DEFAULT];
6690 for (i = 0; i < graph->pos; i++) {
6691 append_to_rev_graph(graph, filler->line);
6692 if (graph_parent_is_merge(graph->prev) &&
6693 graph->prev->pos == i)
6694 filler = &fillers[RSHARP];
6696 append_to_rev_graph(graph, filler->separator);
6697 }
6699 /* Place the symbol for this revision. */
6700 append_to_rev_graph(graph, symbol);
6702 if (graph->prev->size > graph->size)
6703 filler = &fillers[RDIAG];
6704 else
6705 filler = &fillers[DEFAULT];
6707 i++;
6709 for (; i < graph->size; i++) {
6710 append_to_rev_graph(graph, filler->separator);
6711 append_to_rev_graph(graph, filler->line);
6712 if (graph_parent_is_merge(graph->prev) &&
6713 i < graph->prev->pos + graph->parents->size)
6714 filler = &fillers[RSHARP];
6715 if (graph->prev->size > graph->size)
6716 filler = &fillers[LDIAG];
6717 }
6719 if (graph->prev->size > graph->size) {
6720 append_to_rev_graph(graph, filler->separator);
6721 if (filler->line != ' ')
6722 append_to_rev_graph(graph, filler->line);
6723 }
6724 }
6726 /* Prepare the next rev graph */
6727 static void
6728 prepare_rev_graph(struct rev_graph *graph)
6729 {
6730 size_t i;
6732 /* First, traverse all lines of revisions up to the active one. */
6733 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6734 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6735 break;
6737 push_rev_graph(graph->next, graph->rev[graph->pos]);
6738 }
6740 /* Interleave the new revision parent(s). */
6741 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6742 push_rev_graph(graph->next, graph->parents->rev[i]);
6744 /* Lastly, put any remaining revisions. */
6745 for (i = graph->pos + 1; i < graph->size; i++)
6746 push_rev_graph(graph->next, graph->rev[i]);
6747 }
6749 static void
6750 update_rev_graph(struct view *view, struct rev_graph *graph)
6751 {
6752 /* If this is the finalizing update ... */
6753 if (graph->commit)
6754 prepare_rev_graph(graph);
6756 /* Graph visualization needs a one rev look-ahead,
6757 * so the first update doesn't visualize anything. */
6758 if (!graph->prev->commit)
6759 return;
6761 if (view->lines > 2)
6762 view->line[view->lines - 3].dirty = 1;
6763 if (view->lines > 1)
6764 view->line[view->lines - 2].dirty = 1;
6765 draw_rev_graph(graph->prev);
6766 done_rev_graph(graph->prev->prev);
6767 }
6770 /*
6771 * Main view backend
6772 */
6774 static const char *main_argv[SIZEOF_ARG] = {
6775 "git", "log", "--no-color", "--pretty=raw", "--parents",
6776 "--topo-order", "%(diffargs)", "%(revargs)",
6777 "--", "%(fileargs)", NULL
6778 };
6780 static bool
6781 main_draw(struct view *view, struct line *line, unsigned int lineno)
6782 {
6783 struct commit *commit = line->data;
6785 if (!commit->author)
6786 return FALSE;
6788 if (opt_date && draw_date(view, &commit->time))
6789 return TRUE;
6791 if (opt_author && draw_author(view, commit->author))
6792 return TRUE;
6794 if (opt_rev_graph && commit->graph_size &&
6795 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6796 return TRUE;
6798 if (opt_show_refs && commit->refs) {
6799 size_t i;
6801 for (i = 0; i < commit->refs->size; i++) {
6802 struct ref *ref = commit->refs->refs[i];
6803 enum line_type type;
6805 if (ref->head)
6806 type = LINE_MAIN_HEAD;
6807 else if (ref->ltag)
6808 type = LINE_MAIN_LOCAL_TAG;
6809 else if (ref->tag)
6810 type = LINE_MAIN_TAG;
6811 else if (ref->tracked)
6812 type = LINE_MAIN_TRACKED;
6813 else if (ref->remote)
6814 type = LINE_MAIN_REMOTE;
6815 else
6816 type = LINE_MAIN_REF;
6818 if (draw_text(view, type, "[", TRUE) ||
6819 draw_text(view, type, ref->name, TRUE) ||
6820 draw_text(view, type, "]", TRUE))
6821 return TRUE;
6823 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6824 return TRUE;
6825 }
6826 }
6828 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6829 return TRUE;
6830 }
6832 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6833 static bool
6834 main_read(struct view *view, char *line)
6835 {
6836 static struct rev_graph *graph = graph_stacks;
6837 enum line_type type;
6838 struct commit *commit;
6840 if (!line) {
6841 int i;
6843 if (!view->lines && !view->prev)
6844 die("No revisions match the given arguments.");
6845 if (view->lines > 0) {
6846 commit = view->line[view->lines - 1].data;
6847 view->line[view->lines - 1].dirty = 1;
6848 if (!commit->author) {
6849 view->lines--;
6850 free(commit);
6851 graph->commit = NULL;
6852 }
6853 }
6854 update_rev_graph(view, graph);
6856 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6857 clear_rev_graph(&graph_stacks[i]);
6858 return TRUE;
6859 }
6861 type = get_line_type(line);
6862 if (type == LINE_COMMIT) {
6863 commit = calloc(1, sizeof(struct commit));
6864 if (!commit)
6865 return FALSE;
6867 line += STRING_SIZE("commit ");
6868 if (*line == '-') {
6869 graph->boundary = 1;
6870 line++;
6871 }
6873 string_copy_rev(commit->id, line);
6874 commit->refs = get_ref_list(commit->id);
6875 graph->commit = commit;
6876 add_line_data(view, commit, LINE_MAIN_COMMIT);
6878 while ((line = strchr(line, ' '))) {
6879 line++;
6880 push_rev_graph(graph->parents, line);
6881 commit->has_parents = TRUE;
6882 }
6883 return TRUE;
6884 }
6886 if (!view->lines)
6887 return TRUE;
6888 commit = view->line[view->lines - 1].data;
6890 switch (type) {
6891 case LINE_PARENT:
6892 if (commit->has_parents)
6893 break;
6894 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6895 break;
6897 case LINE_AUTHOR:
6898 parse_author_line(line + STRING_SIZE("author "),
6899 &commit->author, &commit->time);
6900 update_rev_graph(view, graph);
6901 graph = graph->next;
6902 break;
6904 default:
6905 /* Fill in the commit title if it has not already been set. */
6906 if (commit->title[0])
6907 break;
6909 /* Require titles to start with a non-space character at the
6910 * offset used by git log. */
6911 if (strncmp(line, " ", 4))
6912 break;
6913 line += 4;
6914 /* Well, if the title starts with a whitespace character,
6915 * try to be forgiving. Otherwise we end up with no title. */
6916 while (isspace(*line))
6917 line++;
6918 if (*line == '\0')
6919 break;
6920 /* FIXME: More graceful handling of titles; append "..." to
6921 * shortened titles, etc. */
6923 string_expand(commit->title, sizeof(commit->title), line, 1);
6924 view->line[view->lines - 1].dirty = 1;
6925 }
6927 return TRUE;
6928 }
6930 static enum request
6931 main_request(struct view *view, enum request request, struct line *line)
6932 {
6933 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6935 switch (request) {
6936 case REQ_ENTER:
6937 open_view(view, REQ_VIEW_DIFF, flags);
6938 break;
6939 case REQ_REFRESH:
6940 load_refs();
6941 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6942 break;
6943 default:
6944 return request;
6945 }
6947 return REQ_NONE;
6948 }
6950 static bool
6951 grep_refs(struct ref_list *list, regex_t *regex)
6952 {
6953 regmatch_t pmatch;
6954 size_t i;
6956 if (!opt_show_refs || !list)
6957 return FALSE;
6959 for (i = 0; i < list->size; i++) {
6960 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6961 return TRUE;
6962 }
6964 return FALSE;
6965 }
6967 static bool
6968 main_grep(struct view *view, struct line *line)
6969 {
6970 struct commit *commit = line->data;
6971 const char *text[] = {
6972 commit->title,
6973 opt_author ? commit->author : "",
6974 mkdate(&commit->time, opt_date),
6975 NULL
6976 };
6978 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6979 }
6981 static void
6982 main_select(struct view *view, struct line *line)
6983 {
6984 struct commit *commit = line->data;
6986 string_copy_rev(view->ref, commit->id);
6987 string_copy_rev(ref_commit, view->ref);
6988 }
6990 static struct view_ops main_ops = {
6991 "commit",
6992 main_argv,
6993 NULL,
6994 main_read,
6995 main_draw,
6996 main_request,
6997 main_grep,
6998 main_select,
6999 };
7002 /*
7003 * Status management
7004 */
7006 /* Whether or not the curses interface has been initialized. */
7007 static bool cursed = FALSE;
7009 /* Terminal hacks and workarounds. */
7010 static bool use_scroll_redrawwin;
7011 static bool use_scroll_status_wclear;
7013 /* The status window is used for polling keystrokes. */
7014 static WINDOW *status_win;
7016 /* Reading from the prompt? */
7017 static bool input_mode = FALSE;
7019 static bool status_empty = FALSE;
7021 /* Update status and title window. */
7022 static void
7023 report(const char *msg, ...)
7024 {
7025 struct view *view = display[current_view];
7027 if (input_mode)
7028 return;
7030 if (!view) {
7031 char buf[SIZEOF_STR];
7032 va_list args;
7034 va_start(args, msg);
7035 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7036 buf[sizeof(buf) - 1] = 0;
7037 buf[sizeof(buf) - 2] = '.';
7038 buf[sizeof(buf) - 3] = '.';
7039 buf[sizeof(buf) - 4] = '.';
7040 }
7041 va_end(args);
7042 die("%s", buf);
7043 }
7045 if (!status_empty || *msg) {
7046 va_list args;
7048 va_start(args, msg);
7050 wmove(status_win, 0, 0);
7051 if (view->has_scrolled && use_scroll_status_wclear)
7052 wclear(status_win);
7053 if (*msg) {
7054 vwprintw(status_win, msg, args);
7055 status_empty = FALSE;
7056 } else {
7057 status_empty = TRUE;
7058 }
7059 wclrtoeol(status_win);
7060 wnoutrefresh(status_win);
7062 va_end(args);
7063 }
7065 update_view_title(view);
7066 }
7068 static void
7069 init_display(void)
7070 {
7071 const char *term;
7072 int x, y;
7074 /* Initialize the curses library */
7075 if (isatty(STDIN_FILENO)) {
7076 cursed = !!initscr();
7077 opt_tty = stdin;
7078 } else {
7079 /* Leave stdin and stdout alone when acting as a pager. */
7080 opt_tty = fopen("/dev/tty", "r+");
7081 if (!opt_tty)
7082 die("Failed to open /dev/tty");
7083 cursed = !!newterm(NULL, opt_tty, opt_tty);
7084 }
7086 if (!cursed)
7087 die("Failed to initialize curses");
7089 nonl(); /* Disable conversion and detect newlines from input. */
7090 cbreak(); /* Take input chars one at a time, no wait for \n */
7091 noecho(); /* Don't echo input */
7092 leaveok(stdscr, FALSE);
7094 if (has_colors())
7095 init_colors();
7097 getmaxyx(stdscr, y, x);
7098 status_win = newwin(1, 0, y - 1, 0);
7099 if (!status_win)
7100 die("Failed to create status window");
7102 /* Enable keyboard mapping */
7103 keypad(status_win, TRUE);
7104 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7106 TABSIZE = opt_tab_size;
7108 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7109 if (term && !strcmp(term, "gnome-terminal")) {
7110 /* In the gnome-terminal-emulator, the message from
7111 * scrolling up one line when impossible followed by
7112 * scrolling down one line causes corruption of the
7113 * status line. This is fixed by calling wclear. */
7114 use_scroll_status_wclear = TRUE;
7115 use_scroll_redrawwin = FALSE;
7117 } else if (term && !strcmp(term, "xrvt-xpm")) {
7118 /* No problems with full optimizations in xrvt-(unicode)
7119 * and aterm. */
7120 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7122 } else {
7123 /* When scrolling in (u)xterm the last line in the
7124 * scrolling direction will update slowly. */
7125 use_scroll_redrawwin = TRUE;
7126 use_scroll_status_wclear = FALSE;
7127 }
7128 }
7130 static int
7131 get_input(int prompt_position)
7132 {
7133 struct view *view;
7134 int i, key, cursor_y, cursor_x;
7136 if (prompt_position)
7137 input_mode = TRUE;
7139 while (TRUE) {
7140 bool loading = FALSE;
7142 foreach_view (view, i) {
7143 update_view(view);
7144 if (view_is_displayed(view) && view->has_scrolled &&
7145 use_scroll_redrawwin)
7146 redrawwin(view->win);
7147 view->has_scrolled = FALSE;
7148 if (view->pipe)
7149 loading = TRUE;
7150 }
7152 /* Update the cursor position. */
7153 if (prompt_position) {
7154 getbegyx(status_win, cursor_y, cursor_x);
7155 cursor_x = prompt_position;
7156 } else {
7157 view = display[current_view];
7158 getbegyx(view->win, cursor_y, cursor_x);
7159 cursor_x = view->width - 1;
7160 cursor_y += view->lineno - view->offset;
7161 }
7162 setsyx(cursor_y, cursor_x);
7164 /* Refresh, accept single keystroke of input */
7165 doupdate();
7166 nodelay(status_win, loading);
7167 key = wgetch(status_win);
7169 /* wgetch() with nodelay() enabled returns ERR when
7170 * there's no input. */
7171 if (key == ERR) {
7173 } else if (key == KEY_RESIZE) {
7174 int height, width;
7176 getmaxyx(stdscr, height, width);
7178 wresize(status_win, 1, width);
7179 mvwin(status_win, height - 1, 0);
7180 wnoutrefresh(status_win);
7181 resize_display();
7182 redraw_display(TRUE);
7184 } else {
7185 input_mode = FALSE;
7186 return key;
7187 }
7188 }
7189 }
7191 static char *
7192 prompt_input(const char *prompt, input_handler handler, void *data)
7193 {
7194 enum input_status status = INPUT_OK;
7195 static char buf[SIZEOF_STR];
7196 size_t pos = 0;
7198 buf[pos] = 0;
7200 while (status == INPUT_OK || status == INPUT_SKIP) {
7201 int key;
7203 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7204 wclrtoeol(status_win);
7206 key = get_input(pos + 1);
7207 switch (key) {
7208 case KEY_RETURN:
7209 case KEY_ENTER:
7210 case '\n':
7211 status = pos ? INPUT_STOP : INPUT_CANCEL;
7212 break;
7214 case KEY_BACKSPACE:
7215 if (pos > 0)
7216 buf[--pos] = 0;
7217 else
7218 status = INPUT_CANCEL;
7219 break;
7221 case KEY_ESC:
7222 status = INPUT_CANCEL;
7223 break;
7225 default:
7226 if (pos >= sizeof(buf)) {
7227 report("Input string too long");
7228 return NULL;
7229 }
7231 status = handler(data, buf, key);
7232 if (status == INPUT_OK)
7233 buf[pos++] = (char) key;
7234 }
7235 }
7237 /* Clear the status window */
7238 status_empty = FALSE;
7239 report("");
7241 if (status == INPUT_CANCEL)
7242 return NULL;
7244 buf[pos++] = 0;
7246 return buf;
7247 }
7249 static enum input_status
7250 prompt_yesno_handler(void *data, char *buf, int c)
7251 {
7252 if (c == 'y' || c == 'Y')
7253 return INPUT_STOP;
7254 if (c == 'n' || c == 'N')
7255 return INPUT_CANCEL;
7256 return INPUT_SKIP;
7257 }
7259 static bool
7260 prompt_yesno(const char *prompt)
7261 {
7262 char prompt2[SIZEOF_STR];
7264 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7265 return FALSE;
7267 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7268 }
7270 static enum input_status
7271 read_prompt_handler(void *data, char *buf, int c)
7272 {
7273 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7274 }
7276 static char *
7277 read_prompt(const char *prompt)
7278 {
7279 return prompt_input(prompt, read_prompt_handler, NULL);
7280 }
7282 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7283 {
7284 enum input_status status = INPUT_OK;
7285 int size = 0;
7287 while (items[size].text)
7288 size++;
7290 while (status == INPUT_OK) {
7291 const struct menu_item *item = &items[*selected];
7292 int key;
7293 int i;
7295 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7296 prompt, *selected + 1, size);
7297 if (item->hotkey)
7298 wprintw(status_win, "[%c] ", (char) item->hotkey);
7299 wprintw(status_win, "%s", item->text);
7300 wclrtoeol(status_win);
7302 key = get_input(COLS - 1);
7303 switch (key) {
7304 case KEY_RETURN:
7305 case KEY_ENTER:
7306 case '\n':
7307 status = INPUT_STOP;
7308 break;
7310 case KEY_LEFT:
7311 case KEY_UP:
7312 *selected = *selected - 1;
7313 if (*selected < 0)
7314 *selected = size - 1;
7315 break;
7317 case KEY_RIGHT:
7318 case KEY_DOWN:
7319 *selected = (*selected + 1) % size;
7320 break;
7322 case KEY_ESC:
7323 status = INPUT_CANCEL;
7324 break;
7326 default:
7327 for (i = 0; items[i].text; i++)
7328 if (items[i].hotkey == key) {
7329 *selected = i;
7330 status = INPUT_STOP;
7331 break;
7332 }
7333 }
7334 }
7336 /* Clear the status window */
7337 status_empty = FALSE;
7338 report("");
7340 return status != INPUT_CANCEL;
7341 }
7343 /*
7344 * Repository properties
7345 */
7347 static struct ref **refs = NULL;
7348 static size_t refs_size = 0;
7349 static struct ref *refs_head = NULL;
7351 static struct ref_list **ref_lists = NULL;
7352 static size_t ref_lists_size = 0;
7354 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7355 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7356 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7358 static int
7359 compare_refs(const void *ref1_, const void *ref2_)
7360 {
7361 const struct ref *ref1 = *(const struct ref **)ref1_;
7362 const struct ref *ref2 = *(const struct ref **)ref2_;
7364 if (ref1->tag != ref2->tag)
7365 return ref2->tag - ref1->tag;
7366 if (ref1->ltag != ref2->ltag)
7367 return ref2->ltag - ref2->ltag;
7368 if (ref1->head != ref2->head)
7369 return ref2->head - ref1->head;
7370 if (ref1->tracked != ref2->tracked)
7371 return ref2->tracked - ref1->tracked;
7372 if (ref1->remote != ref2->remote)
7373 return ref2->remote - ref1->remote;
7374 return strcmp(ref1->name, ref2->name);
7375 }
7377 static void
7378 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7379 {
7380 size_t i;
7382 for (i = 0; i < refs_size; i++)
7383 if (!visitor(data, refs[i]))
7384 break;
7385 }
7387 static struct ref *
7388 get_ref_head()
7389 {
7390 return refs_head;
7391 }
7393 static struct ref_list *
7394 get_ref_list(const char *id)
7395 {
7396 struct ref_list *list;
7397 size_t i;
7399 for (i = 0; i < ref_lists_size; i++)
7400 if (!strcmp(id, ref_lists[i]->id))
7401 return ref_lists[i];
7403 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7404 return NULL;
7405 list = calloc(1, sizeof(*list));
7406 if (!list)
7407 return NULL;
7409 for (i = 0; i < refs_size; i++) {
7410 if (!strcmp(id, refs[i]->id) &&
7411 realloc_refs_list(&list->refs, list->size, 1))
7412 list->refs[list->size++] = refs[i];
7413 }
7415 if (!list->refs) {
7416 free(list);
7417 return NULL;
7418 }
7420 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7421 ref_lists[ref_lists_size++] = list;
7422 return list;
7423 }
7425 static int
7426 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7427 {
7428 struct ref *ref = NULL;
7429 bool tag = FALSE;
7430 bool ltag = FALSE;
7431 bool remote = FALSE;
7432 bool tracked = FALSE;
7433 bool head = FALSE;
7434 int from = 0, to = refs_size - 1;
7436 if (!prefixcmp(name, "refs/tags/")) {
7437 if (!suffixcmp(name, namelen, "^{}")) {
7438 namelen -= 3;
7439 name[namelen] = 0;
7440 } else {
7441 ltag = TRUE;
7442 }
7444 tag = TRUE;
7445 namelen -= STRING_SIZE("refs/tags/");
7446 name += STRING_SIZE("refs/tags/");
7448 } else if (!prefixcmp(name, "refs/remotes/")) {
7449 remote = TRUE;
7450 namelen -= STRING_SIZE("refs/remotes/");
7451 name += STRING_SIZE("refs/remotes/");
7452 tracked = !strcmp(opt_remote, name);
7454 } else if (!prefixcmp(name, "refs/heads/")) {
7455 namelen -= STRING_SIZE("refs/heads/");
7456 name += STRING_SIZE("refs/heads/");
7457 if (!strncmp(opt_head, name, namelen))
7458 return OK;
7460 } else if (!strcmp(name, "HEAD")) {
7461 head = TRUE;
7462 if (*opt_head) {
7463 namelen = strlen(opt_head);
7464 name = opt_head;
7465 }
7466 }
7468 /* If we are reloading or it's an annotated tag, replace the
7469 * previous SHA1 with the resolved commit id; relies on the fact
7470 * git-ls-remote lists the commit id of an annotated tag right
7471 * before the commit id it points to. */
7472 while (from <= to) {
7473 size_t pos = (to + from) / 2;
7474 int cmp = strcmp(name, refs[pos]->name);
7476 if (!cmp) {
7477 ref = refs[pos];
7478 break;
7479 }
7481 if (cmp < 0)
7482 to = pos - 1;
7483 else
7484 from = pos + 1;
7485 }
7487 if (!ref) {
7488 if (!realloc_refs(&refs, refs_size, 1))
7489 return ERR;
7490 ref = calloc(1, sizeof(*ref) + namelen);
7491 if (!ref)
7492 return ERR;
7493 memmove(refs + from + 1, refs + from,
7494 (refs_size - from) * sizeof(*refs));
7495 refs[from] = ref;
7496 strncpy(ref->name, name, namelen);
7497 refs_size++;
7498 }
7500 ref->head = head;
7501 ref->tag = tag;
7502 ref->ltag = ltag;
7503 ref->remote = remote;
7504 ref->tracked = tracked;
7505 string_copy_rev(ref->id, id);
7507 if (head)
7508 refs_head = ref;
7509 return OK;
7510 }
7512 static int
7513 load_refs(void)
7514 {
7515 const char *head_argv[] = {
7516 "git", "symbolic-ref", "HEAD", NULL
7517 };
7518 static const char *ls_remote_argv[SIZEOF_ARG] = {
7519 "git", "ls-remote", opt_git_dir, NULL
7520 };
7521 static bool init = FALSE;
7522 size_t i;
7524 if (!init) {
7525 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7526 die("TIG_LS_REMOTE contains too many arguments");
7527 init = TRUE;
7528 }
7530 if (!*opt_git_dir)
7531 return OK;
7533 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7534 !prefixcmp(opt_head, "refs/heads/")) {
7535 char *offset = opt_head + STRING_SIZE("refs/heads/");
7537 memmove(opt_head, offset, strlen(offset) + 1);
7538 }
7540 refs_head = NULL;
7541 for (i = 0; i < refs_size; i++)
7542 refs[i]->id[0] = 0;
7544 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7545 return ERR;
7547 /* Update the ref lists to reflect changes. */
7548 for (i = 0; i < ref_lists_size; i++) {
7549 struct ref_list *list = ref_lists[i];
7550 size_t old, new;
7552 for (old = new = 0; old < list->size; old++)
7553 if (!strcmp(list->id, list->refs[old]->id))
7554 list->refs[new++] = list->refs[old];
7555 list->size = new;
7556 }
7558 return OK;
7559 }
7561 static void
7562 set_remote_branch(const char *name, const char *value, size_t valuelen)
7563 {
7564 if (!strcmp(name, ".remote")) {
7565 string_ncopy(opt_remote, value, valuelen);
7567 } else if (*opt_remote && !strcmp(name, ".merge")) {
7568 size_t from = strlen(opt_remote);
7570 if (!prefixcmp(value, "refs/heads/"))
7571 value += STRING_SIZE("refs/heads/");
7573 if (!string_format_from(opt_remote, &from, "/%s", value))
7574 opt_remote[0] = 0;
7575 }
7576 }
7578 static void
7579 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7580 {
7581 const char *argv[SIZEOF_ARG] = { name, "=" };
7582 int argc = 1 + (cmd == option_set_command);
7583 int error = ERR;
7585 if (!argv_from_string(argv, &argc, value))
7586 config_msg = "Too many option arguments";
7587 else
7588 error = cmd(argc, argv);
7590 if (error == ERR)
7591 warn("Option 'tig.%s': %s", name, config_msg);
7592 }
7594 static bool
7595 set_environment_variable(const char *name, const char *value)
7596 {
7597 size_t len = strlen(name) + 1 + strlen(value) + 1;
7598 char *env = malloc(len);
7600 if (env &&
7601 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7602 putenv(env) == 0)
7603 return TRUE;
7604 free(env);
7605 return FALSE;
7606 }
7608 static void
7609 set_work_tree(const char *value)
7610 {
7611 char cwd[SIZEOF_STR];
7613 if (!getcwd(cwd, sizeof(cwd)))
7614 die("Failed to get cwd path: %s", strerror(errno));
7615 if (chdir(opt_git_dir) < 0)
7616 die("Failed to chdir(%s): %s", strerror(errno));
7617 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7618 die("Failed to get git path: %s", strerror(errno));
7619 if (chdir(cwd) < 0)
7620 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7621 if (chdir(value) < 0)
7622 die("Failed to chdir(%s): %s", value, strerror(errno));
7623 if (!getcwd(cwd, sizeof(cwd)))
7624 die("Failed to get cwd path: %s", strerror(errno));
7625 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7626 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7627 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7628 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7629 opt_is_inside_work_tree = TRUE;
7630 }
7632 static int
7633 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7634 {
7635 if (!strcmp(name, "i18n.commitencoding"))
7636 string_ncopy(opt_encoding, value, valuelen);
7638 else if (!strcmp(name, "core.editor"))
7639 string_ncopy(opt_editor, value, valuelen);
7641 else if (!strcmp(name, "core.worktree"))
7642 set_work_tree(value);
7644 else if (!prefixcmp(name, "tig.color."))
7645 set_repo_config_option(name + 10, value, option_color_command);
7647 else if (!prefixcmp(name, "tig.bind."))
7648 set_repo_config_option(name + 9, value, option_bind_command);
7650 else if (!prefixcmp(name, "tig."))
7651 set_repo_config_option(name + 4, value, option_set_command);
7653 else if (*opt_head && !prefixcmp(name, "branch.") &&
7654 !strncmp(name + 7, opt_head, strlen(opt_head)))
7655 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7657 return OK;
7658 }
7660 static int
7661 load_git_config(void)
7662 {
7663 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7665 return io_run_load(config_list_argv, "=", read_repo_config_option);
7666 }
7668 static int
7669 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7670 {
7671 if (!opt_git_dir[0]) {
7672 string_ncopy(opt_git_dir, name, namelen);
7674 } else if (opt_is_inside_work_tree == -1) {
7675 /* This can be 3 different values depending on the
7676 * version of git being used. If git-rev-parse does not
7677 * understand --is-inside-work-tree it will simply echo
7678 * the option else either "true" or "false" is printed.
7679 * Default to true for the unknown case. */
7680 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7682 } else if (*name == '.') {
7683 string_ncopy(opt_cdup, name, namelen);
7685 } else {
7686 string_ncopy(opt_prefix, name, namelen);
7687 }
7689 return OK;
7690 }
7692 static int
7693 load_repo_info(void)
7694 {
7695 const char *rev_parse_argv[] = {
7696 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7697 "--show-cdup", "--show-prefix", NULL
7698 };
7700 return io_run_load(rev_parse_argv, "=", read_repo_info);
7701 }
7704 /*
7705 * Main
7706 */
7708 static const char usage[] =
7709 "tig " TIG_VERSION " (" __DATE__ ")\n"
7710 "\n"
7711 "Usage: tig [options] [revs] [--] [paths]\n"
7712 " or: tig show [options] [revs] [--] [paths]\n"
7713 " or: tig blame [rev] path\n"
7714 " or: tig status\n"
7715 " or: tig < [git command output]\n"
7716 "\n"
7717 "Options:\n"
7718 " -v, --version Show version and exit\n"
7719 " -h, --help Show help message and exit";
7721 static void __NORETURN
7722 quit(int sig)
7723 {
7724 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7725 if (cursed)
7726 endwin();
7727 exit(0);
7728 }
7730 static void __NORETURN
7731 die(const char *err, ...)
7732 {
7733 va_list args;
7735 endwin();
7737 va_start(args, err);
7738 fputs("tig: ", stderr);
7739 vfprintf(stderr, err, args);
7740 fputs("\n", stderr);
7741 va_end(args);
7743 exit(1);
7744 }
7746 static void
7747 warn(const char *msg, ...)
7748 {
7749 va_list args;
7751 va_start(args, msg);
7752 fputs("tig warning: ", stderr);
7753 vfprintf(stderr, msg, args);
7754 fputs("\n", stderr);
7755 va_end(args);
7756 }
7758 static const char ***filter_args;
7760 static int
7761 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7762 {
7763 return argv_append(filter_args, name) ? OK : ERR;
7764 }
7766 static void
7767 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7768 {
7769 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7770 const char **all_argv = NULL;
7772 filter_args = args;
7773 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7774 !argv_append_array(&all_argv, argv) ||
7775 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7776 die("Failed to split arguments");
7777 argv_free(all_argv);
7778 free(all_argv);
7779 }
7781 static void
7782 filter_options(const char *argv[])
7783 {
7784 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7785 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7786 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7787 }
7789 static enum request
7790 parse_options(int argc, const char *argv[])
7791 {
7792 enum request request = REQ_VIEW_MAIN;
7793 const char *subcommand;
7794 bool seen_dashdash = FALSE;
7795 const char **filter_argv = NULL;
7796 int i;
7798 if (!isatty(STDIN_FILENO))
7799 return REQ_VIEW_PAGER;
7801 if (argc <= 1)
7802 return REQ_VIEW_MAIN;
7804 subcommand = argv[1];
7805 if (!strcmp(subcommand, "status")) {
7806 if (argc > 2)
7807 warn("ignoring arguments after `%s'", subcommand);
7808 return REQ_VIEW_STATUS;
7810 } else if (!strcmp(subcommand, "blame")) {
7811 if (argc <= 2 || argc > 4)
7812 die("invalid number of options to blame\n\n%s", usage);
7814 i = 2;
7815 if (argc == 4) {
7816 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7817 i++;
7818 }
7820 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7821 return REQ_VIEW_BLAME;
7823 } else if (!strcmp(subcommand, "show")) {
7824 request = REQ_VIEW_DIFF;
7826 } else {
7827 subcommand = NULL;
7828 }
7830 for (i = 1 + !!subcommand; i < argc; i++) {
7831 const char *opt = argv[i];
7833 if (seen_dashdash) {
7834 argv_append(&opt_file_args, opt);
7835 continue;
7837 } else if (!strcmp(opt, "--")) {
7838 seen_dashdash = TRUE;
7839 continue;
7841 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7842 printf("tig version %s\n", TIG_VERSION);
7843 quit(0);
7845 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7846 printf("%s\n", usage);
7847 quit(0);
7849 } else if (!strcmp(opt, "--all")) {
7850 argv_append(&opt_rev_args, opt);
7851 continue;
7852 }
7854 if (!argv_append(&filter_argv, opt))
7855 die("command too long");
7856 }
7858 if (filter_argv)
7859 filter_options(filter_argv);
7861 return request;
7862 }
7864 int
7865 main(int argc, const char *argv[])
7866 {
7867 const char *codeset = "UTF-8";
7868 enum request request = parse_options(argc, argv);
7869 struct view *view;
7870 size_t i;
7872 signal(SIGINT, quit);
7873 signal(SIGPIPE, SIG_IGN);
7875 if (setlocale(LC_ALL, "")) {
7876 codeset = nl_langinfo(CODESET);
7877 }
7879 if (load_repo_info() == ERR)
7880 die("Failed to load repo info.");
7882 if (load_options() == ERR)
7883 die("Failed to load user config.");
7885 if (load_git_config() == ERR)
7886 die("Failed to load repo config.");
7888 /* Require a git repository unless when running in pager mode. */
7889 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7890 die("Not a git repository");
7892 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7893 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7894 if (opt_iconv_in == ICONV_NONE)
7895 die("Failed to initialize character set conversion");
7896 }
7898 if (codeset && strcmp(codeset, "UTF-8")) {
7899 opt_iconv_out = iconv_open(codeset, "UTF-8");
7900 if (opt_iconv_out == ICONV_NONE)
7901 die("Failed to initialize character set conversion");
7902 }
7904 if (load_refs() == ERR)
7905 die("Failed to load refs.");
7907 foreach_view (view, i) {
7908 if (getenv(view->cmd_env))
7909 warn("Use of the %s environment variable is deprecated,"
7910 " use options or TIG_DIFF_ARGS instead",
7911 view->cmd_env);
7912 if (!argv_from_env(view->ops->argv, view->cmd_env))
7913 die("Too many arguments in the `%s` environment variable",
7914 view->cmd_env);
7915 }
7917 init_display();
7919 while (view_driver(display[current_view], request)) {
7920 int key = get_input(0);
7922 view = display[current_view];
7923 request = get_keybinding(view->keymap, key);
7925 /* Some low-level request handling. This keeps access to
7926 * status_win restricted. */
7927 switch (request) {
7928 case REQ_NONE:
7929 report("Unknown key, press %s for help",
7930 get_key(view->keymap, REQ_VIEW_HELP));
7931 break;
7932 case REQ_PROMPT:
7933 {
7934 char *cmd = read_prompt(":");
7936 if (cmd && isdigit(*cmd)) {
7937 int lineno = view->lineno + 1;
7939 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7940 select_view_line(view, lineno - 1);
7941 report("");
7942 } else {
7943 report("Unable to parse '%s' as a line number", cmd);
7944 }
7946 } else if (cmd) {
7947 struct view *next = VIEW(REQ_VIEW_PAGER);
7948 const char *argv[SIZEOF_ARG] = { "git" };
7949 int argc = 1;
7951 /* When running random commands, initially show the
7952 * command in the title. However, it maybe later be
7953 * overwritten if a commit line is selected. */
7954 string_ncopy(next->ref, cmd, strlen(cmd));
7956 if (!argv_from_string(argv, &argc, cmd)) {
7957 report("Too many arguments");
7958 } else if (!prepare_update(next, argv, NULL)) {
7959 report("Failed to format command");
7960 } else {
7961 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7962 }
7963 }
7965 request = REQ_NONE;
7966 break;
7967 }
7968 case REQ_SEARCH:
7969 case REQ_SEARCH_BACK:
7970 {
7971 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7972 char *search = read_prompt(prompt);
7974 if (search)
7975 string_ncopy(opt_search, search, strlen(search));
7976 else if (*opt_search)
7977 request = request == REQ_SEARCH ?
7978 REQ_FIND_NEXT :
7979 REQ_FIND_PREV;
7980 else
7981 request = REQ_NONE;
7982 break;
7983 }
7984 default:
7985 break;
7986 }
7987 }
7989 quit(0);
7991 return 0;
7992 }