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_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164 * Allocation helpers ... Entering macro hell to never be seen again.
165 */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
170 { \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
174 \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
179 } \
180 \
181 return tmp; \
182 }
184 /*
185 * String helpers
186 */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
190 {
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
196 }
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static size_t
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
214 {
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
227 }
228 }
230 dst[size] = 0;
231 return pos;
232 }
234 static char *
235 chomp_string(char *name)
236 {
237 int namelen;
239 while (isspace(*name))
240 name++;
242 namelen = strlen(name) - 1;
243 while (namelen > 0 && isspace(name[namelen]))
244 name[namelen--] = 0;
246 return name;
247 }
249 static bool
250 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
251 {
252 va_list args;
253 size_t pos = bufpos ? *bufpos : 0;
255 va_start(args, fmt);
256 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
257 va_end(args);
259 if (bufpos)
260 *bufpos = pos;
262 return pos >= bufsize ? FALSE : TRUE;
263 }
265 #define string_format(buf, fmt, args...) \
266 string_nformat(buf, sizeof(buf), NULL, fmt, args)
268 #define string_format_from(buf, from, fmt, args...) \
269 string_nformat(buf, sizeof(buf), from, fmt, args)
271 static int
272 string_enum_compare(const char *str1, const char *str2, int len)
273 {
274 size_t i;
276 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
278 /* Diff-Header == DIFF_HEADER */
279 for (i = 0; i < len; i++) {
280 if (toupper(str1[i]) == toupper(str2[i]))
281 continue;
283 if (string_enum_sep(str1[i]) &&
284 string_enum_sep(str2[i]))
285 continue;
287 return str1[i] - str2[i];
288 }
290 return 0;
291 }
293 #define enum_equals(entry, str, len) \
294 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
296 struct enum_map {
297 const char *name;
298 int namelen;
299 int value;
300 };
302 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
304 static char *
305 enum_map_name(const char *name, size_t namelen)
306 {
307 static char buf[SIZEOF_STR];
308 int bufpos;
310 for (bufpos = 0; bufpos <= namelen; bufpos++) {
311 buf[bufpos] = tolower(name[bufpos]);
312 if (buf[bufpos] == '_')
313 buf[bufpos] = '-';
314 }
316 buf[bufpos] = 0;
317 return buf;
318 }
320 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
322 static bool
323 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
324 {
325 size_t namelen = strlen(name);
326 int i;
328 for (i = 0; i < map_size; i++)
329 if (enum_equals(map[i], name, namelen)) {
330 *value = map[i].value;
331 return TRUE;
332 }
334 return FALSE;
335 }
337 #define map_enum(attr, map, name) \
338 map_enum_do(map, ARRAY_SIZE(map), attr, name)
340 #define prefixcmp(str1, str2) \
341 strncmp(str1, str2, STRING_SIZE(str2))
343 static inline int
344 suffixcmp(const char *str, int slen, const char *suffix)
345 {
346 size_t len = slen >= 0 ? slen : strlen(str);
347 size_t suffixlen = strlen(suffix);
349 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
350 }
353 /*
354 * Unicode / UTF-8 handling
355 *
356 * NOTE: Much of the following code for dealing with Unicode is derived from
357 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
358 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
359 */
361 static inline int
362 unicode_width(unsigned long c, int tab_size)
363 {
364 if (c >= 0x1100 &&
365 (c <= 0x115f /* Hangul Jamo */
366 || c == 0x2329
367 || c == 0x232a
368 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
369 /* CJK ... Yi */
370 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
371 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
372 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
373 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
374 || (c >= 0xffe0 && c <= 0xffe6)
375 || (c >= 0x20000 && c <= 0x2fffd)
376 || (c >= 0x30000 && c <= 0x3fffd)))
377 return 2;
379 if (c == '\t')
380 return tab_size;
382 return 1;
383 }
385 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
386 * Illegal bytes are set one. */
387 static const unsigned char utf8_bytes[256] = {
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 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 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,
395 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,
396 };
398 static inline unsigned char
399 utf8_char_length(const char *string, const char *end)
400 {
401 int c = *(unsigned char *) string;
403 return utf8_bytes[c];
404 }
406 /* Decode UTF-8 multi-byte representation into a Unicode character. */
407 static inline unsigned long
408 utf8_to_unicode(const char *string, size_t length)
409 {
410 unsigned long unicode;
412 switch (length) {
413 case 1:
414 unicode = string[0];
415 break;
416 case 2:
417 unicode = (string[0] & 0x1f) << 6;
418 unicode += (string[1] & 0x3f);
419 break;
420 case 3:
421 unicode = (string[0] & 0x0f) << 12;
422 unicode += ((string[1] & 0x3f) << 6);
423 unicode += (string[2] & 0x3f);
424 break;
425 case 4:
426 unicode = (string[0] & 0x0f) << 18;
427 unicode += ((string[1] & 0x3f) << 12);
428 unicode += ((string[2] & 0x3f) << 6);
429 unicode += (string[3] & 0x3f);
430 break;
431 case 5:
432 unicode = (string[0] & 0x0f) << 24;
433 unicode += ((string[1] & 0x3f) << 18);
434 unicode += ((string[2] & 0x3f) << 12);
435 unicode += ((string[3] & 0x3f) << 6);
436 unicode += (string[4] & 0x3f);
437 break;
438 case 6:
439 unicode = (string[0] & 0x01) << 30;
440 unicode += ((string[1] & 0x3f) << 24);
441 unicode += ((string[2] & 0x3f) << 18);
442 unicode += ((string[3] & 0x3f) << 12);
443 unicode += ((string[4] & 0x3f) << 6);
444 unicode += (string[5] & 0x3f);
445 break;
446 default:
447 return 0;
448 }
450 /* Invalid characters could return the special 0xfffd value but NUL
451 * should be just as good. */
452 return unicode > 0xffff ? 0 : unicode;
453 }
455 /* Calculates how much of string can be shown within the given maximum width
456 * and sets trimmed parameter to non-zero value if all of string could not be
457 * shown. If the reserve flag is TRUE, it will reserve at least one
458 * trailing character, which can be useful when drawing a delimiter.
459 *
460 * Returns the number of bytes to output from string to satisfy max_width. */
461 static size_t
462 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
463 {
464 const char *string = *start;
465 const char *end = strchr(string, '\0');
466 unsigned char last_bytes = 0;
467 size_t last_ucwidth = 0;
469 *width = 0;
470 *trimmed = 0;
472 while (string < end) {
473 unsigned char bytes = utf8_char_length(string, end);
474 size_t ucwidth;
475 unsigned long unicode;
477 if (string + bytes > end)
478 break;
480 /* Change representation to figure out whether
481 * it is a single- or double-width character. */
483 unicode = utf8_to_unicode(string, bytes);
484 /* FIXME: Graceful handling of invalid Unicode character. */
485 if (!unicode)
486 break;
488 ucwidth = unicode_width(unicode, tab_size);
489 if (skip > 0) {
490 skip -= ucwidth <= skip ? ucwidth : skip;
491 *start += bytes;
492 }
493 *width += ucwidth;
494 if (*width > max_width) {
495 *trimmed = 1;
496 *width -= ucwidth;
497 if (reserve && *width == max_width) {
498 string -= last_bytes;
499 *width -= last_ucwidth;
500 }
501 break;
502 }
504 string += bytes;
505 last_bytes = ucwidth ? bytes : 0;
506 last_ucwidth = ucwidth;
507 }
509 return string - *start;
510 }
513 #define DATE_INFO \
514 DATE_(NO), \
515 DATE_(DEFAULT), \
516 DATE_(LOCAL), \
517 DATE_(RELATIVE), \
518 DATE_(SHORT)
520 enum date {
521 #define DATE_(name) DATE_##name
522 DATE_INFO
523 #undef DATE_
524 };
526 static const struct enum_map date_map[] = {
527 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
528 DATE_INFO
529 #undef DATE_
530 };
532 struct time {
533 time_t sec;
534 int tz;
535 };
537 static inline int timecmp(const struct time *t1, const struct time *t2)
538 {
539 return t1->sec - t2->sec;
540 }
542 static const char *
543 mkdate(const struct time *time, enum date date)
544 {
545 static char buf[DATE_COLS + 1];
546 static const struct enum_map reldate[] = {
547 { "second", 1, 60 * 2 },
548 { "minute", 60, 60 * 60 * 2 },
549 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
550 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
551 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
552 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
553 };
554 struct tm tm;
556 if (!date || !time || !time->sec)
557 return "";
559 if (date == DATE_RELATIVE) {
560 struct timeval now;
561 time_t date = time->sec + time->tz;
562 time_t seconds;
563 int i;
565 gettimeofday(&now, NULL);
566 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
567 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
568 if (seconds >= reldate[i].value)
569 continue;
571 seconds /= reldate[i].namelen;
572 if (!string_format(buf, "%ld %s%s %s",
573 seconds, reldate[i].name,
574 seconds > 1 ? "s" : "",
575 now.tv_sec >= date ? "ago" : "ahead"))
576 break;
577 return buf;
578 }
579 }
581 if (date == DATE_LOCAL) {
582 time_t date = time->sec + time->tz;
583 localtime_r(&date, &tm);
584 }
585 else {
586 gmtime_r(&time->sec, &tm);
587 }
588 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
589 }
592 #define AUTHOR_VALUES \
593 AUTHOR_(NO), \
594 AUTHOR_(FULL), \
595 AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599 AUTHOR_VALUES,
600 #undef AUTHOR_
601 AUTHOR_DEFAULT = AUTHOR_FULL
602 };
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606 AUTHOR_VALUES
607 #undef AUTHOR_
608 };
610 static const char *
611 get_author_initials(const char *author)
612 {
613 static char initials[AUTHOR_COLS * 6 + 1];
614 size_t pos = 0;
615 const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619 memset(initials, 0, sizeof(initials));
620 while (author < end) {
621 unsigned char bytes;
622 size_t i;
624 while (is_initial_sep(*author))
625 author++;
627 bytes = utf8_char_length(author, end);
628 if (bytes < sizeof(initials) - 1 - pos) {
629 while (bytes--) {
630 initials[pos++] = *author++;
631 }
632 }
634 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635 if (i < sizeof(initials) - 1)
636 initials[i++] = *author;
637 }
639 initials[i++] = 0;
640 }
642 return initials;
643 }
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648 {
649 int valuelen;
651 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652 bool advance = cmd[valuelen] != 0;
654 cmd[valuelen] = 0;
655 argv[(*argc)++] = chomp_string(cmd);
656 cmd = chomp_string(cmd + valuelen + advance);
657 }
659 if (*argc < SIZEOF_ARG)
660 argv[*argc] = NULL;
661 return *argc < SIZEOF_ARG;
662 }
664 static bool
665 argv_from_env(const char **argv, const char *name)
666 {
667 char *env = argv ? getenv(name) : NULL;
668 int argc = 0;
670 if (env && *env)
671 env = strdup(env);
672 return !env || argv_from_string(argv, &argc, env);
673 }
675 static void
676 argv_free(const char *argv[])
677 {
678 int argc;
680 if (!argv)
681 return;
682 for (argc = 0; argv[argc]; argc++)
683 free((void *) argv[argc]);
684 argv[0] = NULL;
685 }
687 static size_t
688 argv_size(const char **argv)
689 {
690 int argc = 0;
692 while (argv && argv[argc])
693 argc++;
695 return argc;
696 }
698 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
700 static bool
701 argv_append(const char ***argv, const char *arg)
702 {
703 size_t argc = argv_size(*argv);
705 if (!argv_realloc(argv, argc, 2))
706 return FALSE;
708 (*argv)[argc++] = strdup(arg);
709 (*argv)[argc] = NULL;
710 return TRUE;
711 }
713 static bool
714 argv_append_array(const char ***dst_argv, const char *src_argv[])
715 {
716 int i;
718 for (i = 0; src_argv && src_argv[i]; i++)
719 if (!argv_append(dst_argv, src_argv[i]))
720 return FALSE;
721 return TRUE;
722 }
724 static bool
725 argv_copy(const char ***dst, const char *src[])
726 {
727 int argc;
729 for (argc = 0; src[argc]; argc++)
730 if (!argv_append(dst, src[argc]))
731 return FALSE;
732 return TRUE;
733 }
736 /*
737 * Executing external commands.
738 */
740 enum io_type {
741 IO_FD, /* File descriptor based IO. */
742 IO_BG, /* Execute command in the background. */
743 IO_FG, /* Execute command with same std{in,out,err}. */
744 IO_RD, /* Read only fork+exec IO. */
745 IO_WR, /* Write only fork+exec IO. */
746 IO_AP, /* Append fork+exec output to file. */
747 };
749 struct io {
750 int pipe; /* Pipe end for reading or writing. */
751 pid_t pid; /* PID of spawned process. */
752 int error; /* Error status. */
753 char *buf; /* Read buffer. */
754 size_t bufalloc; /* Allocated buffer size. */
755 size_t bufsize; /* Buffer content size. */
756 char *bufpos; /* Current buffer position. */
757 unsigned int eof:1; /* Has end of file been reached. */
758 };
760 static void
761 io_init(struct io *io)
762 {
763 memset(io, 0, sizeof(*io));
764 io->pipe = -1;
765 }
767 static bool
768 io_open(struct io *io, const char *fmt, ...)
769 {
770 char name[SIZEOF_STR] = "";
771 bool fits;
772 va_list args;
774 io_init(io);
776 va_start(args, fmt);
777 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
778 va_end(args);
780 if (!fits) {
781 io->error = ENAMETOOLONG;
782 return FALSE;
783 }
784 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
785 if (io->pipe == -1)
786 io->error = errno;
787 return io->pipe != -1;
788 }
790 static bool
791 io_kill(struct io *io)
792 {
793 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
794 }
796 static bool
797 io_done(struct io *io)
798 {
799 pid_t pid = io->pid;
801 if (io->pipe != -1)
802 close(io->pipe);
803 free(io->buf);
804 io_init(io);
806 while (pid > 0) {
807 int status;
808 pid_t waiting = waitpid(pid, &status, 0);
810 if (waiting < 0) {
811 if (errno == EINTR)
812 continue;
813 io->error = errno;
814 return FALSE;
815 }
817 return waiting == pid &&
818 !WIFSIGNALED(status) &&
819 WIFEXITED(status) &&
820 !WEXITSTATUS(status);
821 }
823 return TRUE;
824 }
826 static bool
827 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
828 {
829 int pipefds[2] = { -1, -1 };
830 va_list args;
832 io_init(io);
834 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
835 io->error = errno;
836 return FALSE;
837 } else if (type == IO_AP) {
838 va_start(args, argv);
839 pipefds[1] = va_arg(args, int);
840 va_end(args);
841 }
843 if ((io->pid = fork())) {
844 if (io->pid == -1)
845 io->error = errno;
846 if (pipefds[!(type == IO_WR)] != -1)
847 close(pipefds[!(type == IO_WR)]);
848 if (io->pid != -1) {
849 io->pipe = pipefds[!!(type == IO_WR)];
850 return TRUE;
851 }
853 } else {
854 if (type != IO_FG) {
855 int devnull = open("/dev/null", O_RDWR);
856 int readfd = type == IO_WR ? pipefds[0] : devnull;
857 int writefd = (type == IO_RD || type == IO_AP)
858 ? pipefds[1] : devnull;
860 dup2(readfd, STDIN_FILENO);
861 dup2(writefd, STDOUT_FILENO);
862 dup2(devnull, STDERR_FILENO);
864 close(devnull);
865 if (pipefds[0] != -1)
866 close(pipefds[0]);
867 if (pipefds[1] != -1)
868 close(pipefds[1]);
869 }
871 if (dir && *dir && chdir(dir) == -1)
872 exit(errno);
874 execvp(argv[0], (char *const*) argv);
875 exit(errno);
876 }
878 if (pipefds[!!(type == IO_WR)] != -1)
879 close(pipefds[!!(type == IO_WR)]);
880 return FALSE;
881 }
883 static bool
884 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
885 {
886 struct io io;
888 return io_run(&io, type, dir, argv, fd) && io_done(&io);
889 }
891 static bool
892 io_run_bg(const char **argv)
893 {
894 return io_complete(IO_BG, argv, NULL, -1);
895 }
897 static bool
898 io_run_fg(const char **argv, const char *dir)
899 {
900 return io_complete(IO_FG, argv, dir, -1);
901 }
903 static bool
904 io_run_append(const char **argv, int fd)
905 {
906 return io_complete(IO_AP, argv, NULL, fd);
907 }
909 static bool
910 io_eof(struct io *io)
911 {
912 return io->eof;
913 }
915 static int
916 io_error(struct io *io)
917 {
918 return io->error;
919 }
921 static char *
922 io_strerror(struct io *io)
923 {
924 return strerror(io->error);
925 }
927 static bool
928 io_can_read(struct io *io)
929 {
930 struct timeval tv = { 0, 500 };
931 fd_set fds;
933 FD_ZERO(&fds);
934 FD_SET(io->pipe, &fds);
936 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
937 }
939 static ssize_t
940 io_read(struct io *io, void *buf, size_t bufsize)
941 {
942 do {
943 ssize_t readsize = read(io->pipe, buf, bufsize);
945 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
946 continue;
947 else if (readsize == -1)
948 io->error = errno;
949 else if (readsize == 0)
950 io->eof = 1;
951 return readsize;
952 } while (1);
953 }
955 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
957 static char *
958 io_get(struct io *io, int c, bool can_read)
959 {
960 char *eol;
961 ssize_t readsize;
963 while (TRUE) {
964 if (io->bufsize > 0) {
965 eol = memchr(io->bufpos, c, io->bufsize);
966 if (eol) {
967 char *line = io->bufpos;
969 *eol = 0;
970 io->bufpos = eol + 1;
971 io->bufsize -= io->bufpos - line;
972 return line;
973 }
974 }
976 if (io_eof(io)) {
977 if (io->bufsize) {
978 io->bufpos[io->bufsize] = 0;
979 io->bufsize = 0;
980 return io->bufpos;
981 }
982 return NULL;
983 }
985 if (!can_read)
986 return NULL;
988 if (io->bufsize > 0 && io->bufpos > io->buf)
989 memmove(io->buf, io->bufpos, io->bufsize);
991 if (io->bufalloc == io->bufsize) {
992 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
993 return NULL;
994 io->bufalloc += BUFSIZ;
995 }
997 io->bufpos = io->buf;
998 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
999 if (io_error(io))
1000 return NULL;
1001 io->bufsize += readsize;
1002 }
1003 }
1005 static bool
1006 io_write(struct io *io, const void *buf, size_t bufsize)
1007 {
1008 size_t written = 0;
1010 while (!io_error(io) && written < bufsize) {
1011 ssize_t size;
1013 size = write(io->pipe, buf + written, bufsize - written);
1014 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1015 continue;
1016 else if (size == -1)
1017 io->error = errno;
1018 else
1019 written += size;
1020 }
1022 return written == bufsize;
1023 }
1025 static bool
1026 io_read_buf(struct io *io, char buf[], size_t bufsize)
1027 {
1028 char *result = io_get(io, '\n', TRUE);
1030 if (result) {
1031 result = chomp_string(result);
1032 string_ncopy_do(buf, bufsize, result, strlen(result));
1033 }
1035 return io_done(io) && result;
1036 }
1038 static bool
1039 io_run_buf(const char **argv, char buf[], size_t bufsize)
1040 {
1041 struct io io;
1043 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1044 }
1046 static int
1047 io_load(struct io *io, const char *separators,
1048 int (*read_property)(char *, size_t, char *, size_t))
1049 {
1050 char *name;
1051 int state = OK;
1053 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054 char *value;
1055 size_t namelen;
1056 size_t valuelen;
1058 name = chomp_string(name);
1059 namelen = strcspn(name, separators);
1061 if (name[namelen]) {
1062 name[namelen] = 0;
1063 value = chomp_string(name + namelen + 1);
1064 valuelen = strlen(value);
1066 } else {
1067 value = "";
1068 valuelen = 0;
1069 }
1071 state = read_property(name, namelen, value, valuelen);
1072 }
1074 if (state != ERR && io_error(io))
1075 state = ERR;
1076 io_done(io);
1078 return state;
1079 }
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083 int (*read_property)(char *, size_t, char *, size_t))
1084 {
1085 struct io io;
1087 if (!io_run(&io, IO_RD, NULL, argv))
1088 return ERR;
1089 return io_load(&io, separators, read_property);
1090 }
1093 /*
1094 * User requests
1095 */
1097 #define REQ_INFO \
1098 /* XXX: Keep the view request first and in sync with views[]. */ \
1099 REQ_GROUP("View switching") \
1100 REQ_(VIEW_MAIN, "Show main view"), \
1101 REQ_(VIEW_DIFF, "Show diff view"), \
1102 REQ_(VIEW_LOG, "Show log view"), \
1103 REQ_(VIEW_TREE, "Show tree view"), \
1104 REQ_(VIEW_BLOB, "Show blob view"), \
1105 REQ_(VIEW_BLAME, "Show blame view"), \
1106 REQ_(VIEW_BRANCH, "Show branch view"), \
1107 REQ_(VIEW_HELP, "Show help page"), \
1108 REQ_(VIEW_PAGER, "Show pager view"), \
1109 REQ_(VIEW_STATUS, "Show status view"), \
1110 REQ_(VIEW_STAGE, "Show stage view"), \
1111 \
1112 REQ_GROUP("View manipulation") \
1113 REQ_(ENTER, "Enter current line and scroll"), \
1114 REQ_(NEXT, "Move to next"), \
1115 REQ_(PREVIOUS, "Move to previous"), \
1116 REQ_(PARENT, "Move to parent"), \
1117 REQ_(VIEW_NEXT, "Move focus to next view"), \
1118 REQ_(REFRESH, "Reload and refresh"), \
1119 REQ_(MAXIMIZE, "Maximize the current view"), \
1120 REQ_(VIEW_CLOSE, "Close the current view"), \
1121 REQ_(QUIT, "Close all views and quit"), \
1122 \
1123 REQ_GROUP("View specific requests") \
1124 REQ_(STATUS_UPDATE, "Update file status"), \
1125 REQ_(STATUS_REVERT, "Revert file changes"), \
1126 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1127 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1128 \
1129 REQ_GROUP("Cursor navigation") \
1130 REQ_(MOVE_UP, "Move cursor one line up"), \
1131 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1132 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1133 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1134 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1135 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1136 \
1137 REQ_GROUP("Scrolling") \
1138 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1139 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1140 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1141 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1142 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1143 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1144 \
1145 REQ_GROUP("Searching") \
1146 REQ_(SEARCH, "Search the view"), \
1147 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1148 REQ_(FIND_NEXT, "Find next search match"), \
1149 REQ_(FIND_PREV, "Find previous search match"), \
1150 \
1151 REQ_GROUP("Option manipulation") \
1152 REQ_(OPTIONS, "Open option menu"), \
1153 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1154 REQ_(TOGGLE_DATE, "Toggle date display"), \
1155 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1156 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1157 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1158 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1159 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1160 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1161 \
1162 REQ_GROUP("Misc") \
1163 REQ_(PROMPT, "Bring up the prompt"), \
1164 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1165 REQ_(SHOW_VERSION, "Show version information"), \
1166 REQ_(STOP_LOADING, "Stop all loading views"), \
1167 REQ_(EDIT, "Open in editor"), \
1168 REQ_(NONE, "Do nothing")
1171 /* User action requests. */
1172 enum request {
1173 #define REQ_GROUP(help)
1174 #define REQ_(req, help) REQ_##req
1176 /* Offset all requests to avoid conflicts with ncurses getch values. */
1177 REQ_UNKNOWN = KEY_MAX + 1,
1178 REQ_OFFSET,
1179 REQ_INFO
1181 #undef REQ_GROUP
1182 #undef REQ_
1183 };
1185 struct request_info {
1186 enum request request;
1187 const char *name;
1188 int namelen;
1189 const char *help;
1190 };
1192 static const struct request_info req_info[] = {
1193 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1194 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1195 REQ_INFO
1196 #undef REQ_GROUP
1197 #undef REQ_
1198 };
1200 static enum request
1201 get_request(const char *name)
1202 {
1203 int namelen = strlen(name);
1204 int i;
1206 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1207 if (enum_equals(req_info[i], name, namelen))
1208 return req_info[i].request;
1210 return REQ_UNKNOWN;
1211 }
1214 /*
1215 * Options
1216 */
1218 /* Option and state variables. */
1219 static enum date opt_date = DATE_DEFAULT;
1220 static enum author opt_author = AUTHOR_DEFAULT;
1221 static bool opt_line_number = FALSE;
1222 static bool opt_line_graphics = TRUE;
1223 static bool opt_rev_graph = FALSE;
1224 static bool opt_show_refs = TRUE;
1225 static int opt_num_interval = 5;
1226 static double opt_hscroll = 0.50;
1227 static double opt_scale_split_view = 2.0 / 3.0;
1228 static int opt_tab_size = 8;
1229 static int opt_author_cols = AUTHOR_COLS;
1230 static char opt_path[SIZEOF_STR] = "";
1231 static char opt_file[SIZEOF_STR] = "";
1232 static char opt_ref[SIZEOF_REF] = "";
1233 static char opt_head[SIZEOF_REF] = "";
1234 static char opt_remote[SIZEOF_REF] = "";
1235 static char opt_encoding[20] = "UTF-8";
1236 static iconv_t opt_iconv_in = ICONV_NONE;
1237 static iconv_t opt_iconv_out = ICONV_NONE;
1238 static char opt_search[SIZEOF_STR] = "";
1239 static char opt_cdup[SIZEOF_STR] = "";
1240 static char opt_prefix[SIZEOF_STR] = "";
1241 static char opt_git_dir[SIZEOF_STR] = "";
1242 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1243 static char opt_editor[SIZEOF_STR] = "";
1244 static FILE *opt_tty = NULL;
1245 static const char **opt_diff_args = NULL;
1246 static const char **opt_rev_args = NULL;
1247 static const char **opt_file_args = NULL;
1249 #define is_initial_commit() (!get_ref_head())
1250 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1253 /*
1254 * Line-oriented content detection.
1255 */
1257 #define LINE_INFO \
1258 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1273 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1274 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1275 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1279 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1280 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1281 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1282 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1283 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1284 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1285 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1289 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1290 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1291 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1293 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1296 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1297 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1301 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1302 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1306 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1307 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1308 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1309 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1310 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1311 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1312 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1313 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1315 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1316 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1318 enum line_type {
1319 #define LINE(type, line, fg, bg, attr) \
1320 LINE_##type
1321 LINE_INFO,
1322 LINE_NONE
1323 #undef LINE
1324 };
1326 struct line_info {
1327 const char *name; /* Option name. */
1328 int namelen; /* Size of option name. */
1329 const char *line; /* The start of line to match. */
1330 int linelen; /* Size of string to match. */
1331 int fg, bg, attr; /* Color and text attributes for the lines. */
1332 };
1334 static struct line_info line_info[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1337 LINE_INFO
1338 #undef LINE
1339 };
1341 static enum line_type
1342 get_line_type(const char *line)
1343 {
1344 int linelen = strlen(line);
1345 enum line_type type;
1347 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1348 /* Case insensitive search matches Signed-off-by lines better. */
1349 if (linelen >= line_info[type].linelen &&
1350 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1351 return type;
1353 return LINE_DEFAULT;
1354 }
1356 static inline int
1357 get_line_attr(enum line_type type)
1358 {
1359 assert(type < ARRAY_SIZE(line_info));
1360 return COLOR_PAIR(type) | line_info[type].attr;
1361 }
1363 static struct line_info *
1364 get_line_info(const char *name)
1365 {
1366 size_t namelen = strlen(name);
1367 enum line_type type;
1369 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1370 if (enum_equals(line_info[type], name, namelen))
1371 return &line_info[type];
1373 return NULL;
1374 }
1376 static void
1377 init_colors(void)
1378 {
1379 int default_bg = line_info[LINE_DEFAULT].bg;
1380 int default_fg = line_info[LINE_DEFAULT].fg;
1381 enum line_type type;
1383 start_color();
1385 if (assume_default_colors(default_fg, default_bg) == ERR) {
1386 default_bg = COLOR_BLACK;
1387 default_fg = COLOR_WHITE;
1388 }
1390 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1391 struct line_info *info = &line_info[type];
1392 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1393 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1395 init_pair(type, fg, bg);
1396 }
1397 }
1399 struct line {
1400 enum line_type type;
1402 /* State flags */
1403 unsigned int selected:1;
1404 unsigned int dirty:1;
1405 unsigned int cleareol:1;
1406 unsigned int other:16;
1408 void *data; /* User data */
1409 };
1412 /*
1413 * Keys
1414 */
1416 struct keybinding {
1417 int alias;
1418 enum request request;
1419 };
1421 static struct keybinding default_keybindings[] = {
1422 /* View switching */
1423 { 'm', REQ_VIEW_MAIN },
1424 { 'd', REQ_VIEW_DIFF },
1425 { 'l', REQ_VIEW_LOG },
1426 { 't', REQ_VIEW_TREE },
1427 { 'f', REQ_VIEW_BLOB },
1428 { 'B', REQ_VIEW_BLAME },
1429 { 'H', REQ_VIEW_BRANCH },
1430 { 'p', REQ_VIEW_PAGER },
1431 { 'h', REQ_VIEW_HELP },
1432 { 'S', REQ_VIEW_STATUS },
1433 { 'c', REQ_VIEW_STAGE },
1435 /* View manipulation */
1436 { 'q', REQ_VIEW_CLOSE },
1437 { KEY_TAB, REQ_VIEW_NEXT },
1438 { KEY_RETURN, REQ_ENTER },
1439 { KEY_UP, REQ_PREVIOUS },
1440 { KEY_DOWN, REQ_NEXT },
1441 { 'R', REQ_REFRESH },
1442 { KEY_F(5), REQ_REFRESH },
1443 { 'O', REQ_MAXIMIZE },
1445 /* Cursor navigation */
1446 { 'k', REQ_MOVE_UP },
1447 { 'j', REQ_MOVE_DOWN },
1448 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1449 { KEY_END, REQ_MOVE_LAST_LINE },
1450 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1451 { ' ', REQ_MOVE_PAGE_DOWN },
1452 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1453 { 'b', REQ_MOVE_PAGE_UP },
1454 { '-', REQ_MOVE_PAGE_UP },
1456 /* Scrolling */
1457 { KEY_LEFT, REQ_SCROLL_LEFT },
1458 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1459 { KEY_IC, REQ_SCROLL_LINE_UP },
1460 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1461 { 'w', REQ_SCROLL_PAGE_UP },
1462 { 's', REQ_SCROLL_PAGE_DOWN },
1464 /* Searching */
1465 { '/', REQ_SEARCH },
1466 { '?', REQ_SEARCH_BACK },
1467 { 'n', REQ_FIND_NEXT },
1468 { 'N', REQ_FIND_PREV },
1470 /* Misc */
1471 { 'Q', REQ_QUIT },
1472 { 'z', REQ_STOP_LOADING },
1473 { 'v', REQ_SHOW_VERSION },
1474 { 'r', REQ_SCREEN_REDRAW },
1475 { 'o', REQ_OPTIONS },
1476 { '.', REQ_TOGGLE_LINENO },
1477 { 'D', REQ_TOGGLE_DATE },
1478 { 'A', REQ_TOGGLE_AUTHOR },
1479 { 'g', REQ_TOGGLE_REV_GRAPH },
1480 { 'F', REQ_TOGGLE_REFS },
1481 { 'I', REQ_TOGGLE_SORT_ORDER },
1482 { 'i', REQ_TOGGLE_SORT_FIELD },
1483 { ':', REQ_PROMPT },
1484 { 'u', REQ_STATUS_UPDATE },
1485 { '!', REQ_STATUS_REVERT },
1486 { 'M', REQ_STATUS_MERGE },
1487 { '@', REQ_STAGE_NEXT },
1488 { ',', REQ_PARENT },
1489 { 'e', REQ_EDIT },
1490 };
1492 #define KEYMAP_INFO \
1493 KEYMAP_(GENERIC), \
1494 KEYMAP_(MAIN), \
1495 KEYMAP_(DIFF), \
1496 KEYMAP_(LOG), \
1497 KEYMAP_(TREE), \
1498 KEYMAP_(BLOB), \
1499 KEYMAP_(BLAME), \
1500 KEYMAP_(BRANCH), \
1501 KEYMAP_(PAGER), \
1502 KEYMAP_(HELP), \
1503 KEYMAP_(STATUS), \
1504 KEYMAP_(STAGE)
1506 enum keymap {
1507 #define KEYMAP_(name) KEYMAP_##name
1508 KEYMAP_INFO
1509 #undef KEYMAP_
1510 };
1512 static const struct enum_map keymap_table[] = {
1513 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1514 KEYMAP_INFO
1515 #undef KEYMAP_
1516 };
1518 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1520 struct keybinding_table {
1521 struct keybinding *data;
1522 size_t size;
1523 };
1525 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1527 static void
1528 add_keybinding(enum keymap keymap, enum request request, int key)
1529 {
1530 struct keybinding_table *table = &keybindings[keymap];
1531 size_t i;
1533 for (i = 0; i < keybindings[keymap].size; i++) {
1534 if (keybindings[keymap].data[i].alias == key) {
1535 keybindings[keymap].data[i].request = request;
1536 return;
1537 }
1538 }
1540 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1541 if (!table->data)
1542 die("Failed to allocate keybinding");
1543 table->data[table->size].alias = key;
1544 table->data[table->size++].request = request;
1546 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1547 int i;
1549 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1550 if (default_keybindings[i].alias == key)
1551 default_keybindings[i].request = REQ_NONE;
1552 }
1553 }
1555 /* Looks for a key binding first in the given map, then in the generic map, and
1556 * lastly in the default keybindings. */
1557 static enum request
1558 get_keybinding(enum keymap keymap, int key)
1559 {
1560 size_t i;
1562 for (i = 0; i < keybindings[keymap].size; i++)
1563 if (keybindings[keymap].data[i].alias == key)
1564 return keybindings[keymap].data[i].request;
1566 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1567 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1568 return keybindings[KEYMAP_GENERIC].data[i].request;
1570 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1571 if (default_keybindings[i].alias == key)
1572 return default_keybindings[i].request;
1574 return (enum request) key;
1575 }
1578 struct key {
1579 const char *name;
1580 int value;
1581 };
1583 static const struct key key_table[] = {
1584 { "Enter", KEY_RETURN },
1585 { "Space", ' ' },
1586 { "Backspace", KEY_BACKSPACE },
1587 { "Tab", KEY_TAB },
1588 { "Escape", KEY_ESC },
1589 { "Left", KEY_LEFT },
1590 { "Right", KEY_RIGHT },
1591 { "Up", KEY_UP },
1592 { "Down", KEY_DOWN },
1593 { "Insert", KEY_IC },
1594 { "Delete", KEY_DC },
1595 { "Hash", '#' },
1596 { "Home", KEY_HOME },
1597 { "End", KEY_END },
1598 { "PageUp", KEY_PPAGE },
1599 { "PageDown", KEY_NPAGE },
1600 { "F1", KEY_F(1) },
1601 { "F2", KEY_F(2) },
1602 { "F3", KEY_F(3) },
1603 { "F4", KEY_F(4) },
1604 { "F5", KEY_F(5) },
1605 { "F6", KEY_F(6) },
1606 { "F7", KEY_F(7) },
1607 { "F8", KEY_F(8) },
1608 { "F9", KEY_F(9) },
1609 { "F10", KEY_F(10) },
1610 { "F11", KEY_F(11) },
1611 { "F12", KEY_F(12) },
1612 };
1614 static int
1615 get_key_value(const char *name)
1616 {
1617 int i;
1619 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1620 if (!strcasecmp(key_table[i].name, name))
1621 return key_table[i].value;
1623 if (strlen(name) == 1 && isprint(*name))
1624 return (int) *name;
1626 return ERR;
1627 }
1629 static const char *
1630 get_key_name(int key_value)
1631 {
1632 static char key_char[] = "'X'";
1633 const char *seq = NULL;
1634 int key;
1636 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1637 if (key_table[key].value == key_value)
1638 seq = key_table[key].name;
1640 if (seq == NULL &&
1641 key_value < 127 &&
1642 isprint(key_value)) {
1643 key_char[1] = (char) key_value;
1644 seq = key_char;
1645 }
1647 return seq ? seq : "(no key)";
1648 }
1650 static bool
1651 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1652 {
1653 const char *sep = *pos > 0 ? ", " : "";
1654 const char *keyname = get_key_name(keybinding->alias);
1656 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1657 }
1659 static bool
1660 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1661 enum keymap keymap, bool all)
1662 {
1663 int i;
1665 for (i = 0; i < keybindings[keymap].size; i++) {
1666 if (keybindings[keymap].data[i].request == request) {
1667 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1668 return FALSE;
1669 if (!all)
1670 break;
1671 }
1672 }
1674 return TRUE;
1675 }
1677 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1679 static const char *
1680 get_keys(enum keymap keymap, enum request request, bool all)
1681 {
1682 static char buf[BUFSIZ];
1683 size_t pos = 0;
1684 int i;
1686 buf[pos] = 0;
1688 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1689 return "Too many keybindings!";
1690 if (pos > 0 && !all)
1691 return buf;
1693 if (keymap != KEYMAP_GENERIC) {
1694 /* Only the generic keymap includes the default keybindings when
1695 * listing all keys. */
1696 if (all)
1697 return buf;
1699 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1700 return "Too many keybindings!";
1701 if (pos)
1702 return buf;
1703 }
1705 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1706 if (default_keybindings[i].request == request) {
1707 if (!append_key(buf, &pos, &default_keybindings[i]))
1708 return "Too many keybindings!";
1709 if (!all)
1710 return buf;
1711 }
1712 }
1714 return buf;
1715 }
1717 struct run_request {
1718 enum keymap keymap;
1719 int key;
1720 const char **argv;
1721 };
1723 static struct run_request *run_request;
1724 static size_t run_requests;
1726 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1728 static enum request
1729 add_run_request(enum keymap keymap, int key, const char **argv)
1730 {
1731 struct run_request *req;
1733 if (!realloc_run_requests(&run_request, run_requests, 1))
1734 return REQ_NONE;
1736 req = &run_request[run_requests];
1737 req->keymap = keymap;
1738 req->key = key;
1739 req->argv = NULL;
1741 if (!argv_copy(&req->argv, argv))
1742 return REQ_NONE;
1744 return REQ_NONE + ++run_requests;
1745 }
1747 static struct run_request *
1748 get_run_request(enum request request)
1749 {
1750 if (request <= REQ_NONE)
1751 return NULL;
1752 return &run_request[request - REQ_NONE - 1];
1753 }
1755 static void
1756 add_builtin_run_requests(void)
1757 {
1758 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1759 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1760 const char *commit[] = { "git", "commit", NULL };
1761 const char *gc[] = { "git", "gc", NULL };
1762 struct run_request reqs[] = {
1763 { KEYMAP_MAIN, 'C', cherry_pick },
1764 { KEYMAP_STATUS, 'C', commit },
1765 { KEYMAP_BRANCH, 'C', checkout },
1766 { KEYMAP_GENERIC, 'G', gc },
1767 };
1768 int i;
1770 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1771 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1773 if (req != reqs[i].key)
1774 continue;
1775 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1776 if (req != REQ_NONE)
1777 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1778 }
1779 }
1781 /*
1782 * User config file handling.
1783 */
1785 static int config_lineno;
1786 static bool config_errors;
1787 static const char *config_msg;
1789 static const struct enum_map color_map[] = {
1790 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1791 COLOR_MAP(DEFAULT),
1792 COLOR_MAP(BLACK),
1793 COLOR_MAP(BLUE),
1794 COLOR_MAP(CYAN),
1795 COLOR_MAP(GREEN),
1796 COLOR_MAP(MAGENTA),
1797 COLOR_MAP(RED),
1798 COLOR_MAP(WHITE),
1799 COLOR_MAP(YELLOW),
1800 };
1802 static const struct enum_map attr_map[] = {
1803 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1804 ATTR_MAP(NORMAL),
1805 ATTR_MAP(BLINK),
1806 ATTR_MAP(BOLD),
1807 ATTR_MAP(DIM),
1808 ATTR_MAP(REVERSE),
1809 ATTR_MAP(STANDOUT),
1810 ATTR_MAP(UNDERLINE),
1811 };
1813 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1815 static int parse_step(double *opt, const char *arg)
1816 {
1817 *opt = atoi(arg);
1818 if (!strchr(arg, '%'))
1819 return OK;
1821 /* "Shift down" so 100% and 1 does not conflict. */
1822 *opt = (*opt - 1) / 100;
1823 if (*opt >= 1.0) {
1824 *opt = 0.99;
1825 config_msg = "Step value larger than 100%";
1826 return ERR;
1827 }
1828 if (*opt < 0.0) {
1829 *opt = 1;
1830 config_msg = "Invalid step value";
1831 return ERR;
1832 }
1833 return OK;
1834 }
1836 static int
1837 parse_int(int *opt, const char *arg, int min, int max)
1838 {
1839 int value = atoi(arg);
1841 if (min <= value && value <= max) {
1842 *opt = value;
1843 return OK;
1844 }
1846 config_msg = "Integer value out of bound";
1847 return ERR;
1848 }
1850 static bool
1851 set_color(int *color, const char *name)
1852 {
1853 if (map_enum(color, color_map, name))
1854 return TRUE;
1855 if (!prefixcmp(name, "color"))
1856 return parse_int(color, name + 5, 0, 255) == OK;
1857 return FALSE;
1858 }
1860 /* Wants: object fgcolor bgcolor [attribute] */
1861 static int
1862 option_color_command(int argc, const char *argv[])
1863 {
1864 struct line_info *info;
1866 if (argc < 3) {
1867 config_msg = "Wrong number of arguments given to color command";
1868 return ERR;
1869 }
1871 info = get_line_info(argv[0]);
1872 if (!info) {
1873 static const struct enum_map obsolete[] = {
1874 ENUM_MAP("main-delim", LINE_DELIMITER),
1875 ENUM_MAP("main-date", LINE_DATE),
1876 ENUM_MAP("main-author", LINE_AUTHOR),
1877 };
1878 int index;
1880 if (!map_enum(&index, obsolete, argv[0])) {
1881 config_msg = "Unknown color name";
1882 return ERR;
1883 }
1884 info = &line_info[index];
1885 }
1887 if (!set_color(&info->fg, argv[1]) ||
1888 !set_color(&info->bg, argv[2])) {
1889 config_msg = "Unknown color";
1890 return ERR;
1891 }
1893 info->attr = 0;
1894 while (argc-- > 3) {
1895 int attr;
1897 if (!set_attribute(&attr, argv[argc])) {
1898 config_msg = "Unknown attribute";
1899 return ERR;
1900 }
1901 info->attr |= attr;
1902 }
1904 return OK;
1905 }
1907 static int parse_bool(bool *opt, const char *arg)
1908 {
1909 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1910 ? TRUE : FALSE;
1911 return OK;
1912 }
1914 static int parse_enum_do(unsigned int *opt, const char *arg,
1915 const struct enum_map *map, size_t map_size)
1916 {
1917 bool is_true;
1919 assert(map_size > 1);
1921 if (map_enum_do(map, map_size, (int *) opt, arg))
1922 return OK;
1924 if (parse_bool(&is_true, arg) != OK)
1925 return ERR;
1927 *opt = is_true ? map[1].value : map[0].value;
1928 return OK;
1929 }
1931 #define parse_enum(opt, arg, map) \
1932 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1934 static int
1935 parse_string(char *opt, const char *arg, size_t optsize)
1936 {
1937 int arglen = strlen(arg);
1939 switch (arg[0]) {
1940 case '\"':
1941 case '\'':
1942 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1943 config_msg = "Unmatched quotation";
1944 return ERR;
1945 }
1946 arg += 1; arglen -= 2;
1947 default:
1948 string_ncopy_do(opt, optsize, arg, arglen);
1949 return OK;
1950 }
1951 }
1953 /* Wants: name = value */
1954 static int
1955 option_set_command(int argc, const char *argv[])
1956 {
1957 if (argc != 3) {
1958 config_msg = "Wrong number of arguments given to set command";
1959 return ERR;
1960 }
1962 if (strcmp(argv[1], "=")) {
1963 config_msg = "No value assigned";
1964 return ERR;
1965 }
1967 if (!strcmp(argv[0], "show-author"))
1968 return parse_enum(&opt_author, argv[2], author_map);
1970 if (!strcmp(argv[0], "show-date"))
1971 return parse_enum(&opt_date, argv[2], date_map);
1973 if (!strcmp(argv[0], "show-rev-graph"))
1974 return parse_bool(&opt_rev_graph, argv[2]);
1976 if (!strcmp(argv[0], "show-refs"))
1977 return parse_bool(&opt_show_refs, argv[2]);
1979 if (!strcmp(argv[0], "show-line-numbers"))
1980 return parse_bool(&opt_line_number, argv[2]);
1982 if (!strcmp(argv[0], "line-graphics"))
1983 return parse_bool(&opt_line_graphics, argv[2]);
1985 if (!strcmp(argv[0], "line-number-interval"))
1986 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1988 if (!strcmp(argv[0], "author-width"))
1989 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1991 if (!strcmp(argv[0], "horizontal-scroll"))
1992 return parse_step(&opt_hscroll, argv[2]);
1994 if (!strcmp(argv[0], "split-view-height"))
1995 return parse_step(&opt_scale_split_view, argv[2]);
1997 if (!strcmp(argv[0], "tab-size"))
1998 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2000 if (!strcmp(argv[0], "commit-encoding"))
2001 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2003 config_msg = "Unknown variable name";
2004 return ERR;
2005 }
2007 /* Wants: mode request key */
2008 static int
2009 option_bind_command(int argc, const char *argv[])
2010 {
2011 enum request request;
2012 int keymap = -1;
2013 int key;
2015 if (argc < 3) {
2016 config_msg = "Wrong number of arguments given to bind command";
2017 return ERR;
2018 }
2020 if (!set_keymap(&keymap, argv[0])) {
2021 config_msg = "Unknown key map";
2022 return ERR;
2023 }
2025 key = get_key_value(argv[1]);
2026 if (key == ERR) {
2027 config_msg = "Unknown key";
2028 return ERR;
2029 }
2031 request = get_request(argv[2]);
2032 if (request == REQ_UNKNOWN) {
2033 static const struct enum_map obsolete[] = {
2034 ENUM_MAP("cherry-pick", REQ_NONE),
2035 ENUM_MAP("screen-resize", REQ_NONE),
2036 ENUM_MAP("tree-parent", REQ_PARENT),
2037 };
2038 int alias;
2040 if (map_enum(&alias, obsolete, argv[2])) {
2041 if (alias != REQ_NONE)
2042 add_keybinding(keymap, alias, key);
2043 config_msg = "Obsolete request name";
2044 return ERR;
2045 }
2046 }
2047 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2048 request = add_run_request(keymap, key, argv + 2);
2049 if (request == REQ_UNKNOWN) {
2050 config_msg = "Unknown request name";
2051 return ERR;
2052 }
2054 add_keybinding(keymap, request, key);
2056 return OK;
2057 }
2059 static int
2060 set_option(const char *opt, char *value)
2061 {
2062 const char *argv[SIZEOF_ARG];
2063 int argc = 0;
2065 if (!argv_from_string(argv, &argc, value)) {
2066 config_msg = "Too many option arguments";
2067 return ERR;
2068 }
2070 if (!strcmp(opt, "color"))
2071 return option_color_command(argc, argv);
2073 if (!strcmp(opt, "set"))
2074 return option_set_command(argc, argv);
2076 if (!strcmp(opt, "bind"))
2077 return option_bind_command(argc, argv);
2079 config_msg = "Unknown option command";
2080 return ERR;
2081 }
2083 static int
2084 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2085 {
2086 int status = OK;
2088 config_lineno++;
2089 config_msg = "Internal error";
2091 /* Check for comment markers, since read_properties() will
2092 * only ensure opt and value are split at first " \t". */
2093 optlen = strcspn(opt, "#");
2094 if (optlen == 0)
2095 return OK;
2097 if (opt[optlen] != 0) {
2098 config_msg = "No option value";
2099 status = ERR;
2101 } else {
2102 /* Look for comment endings in the value. */
2103 size_t len = strcspn(value, "#");
2105 if (len < valuelen) {
2106 valuelen = len;
2107 value[valuelen] = 0;
2108 }
2110 status = set_option(opt, value);
2111 }
2113 if (status == ERR) {
2114 warn("Error on line %d, near '%.*s': %s",
2115 config_lineno, (int) optlen, opt, config_msg);
2116 config_errors = TRUE;
2117 }
2119 /* Always keep going if errors are encountered. */
2120 return OK;
2121 }
2123 static void
2124 load_option_file(const char *path)
2125 {
2126 struct io io;
2128 /* It's OK that the file doesn't exist. */
2129 if (!io_open(&io, "%s", path))
2130 return;
2132 config_lineno = 0;
2133 config_errors = FALSE;
2135 if (io_load(&io, " \t", read_option) == ERR ||
2136 config_errors == TRUE)
2137 warn("Errors while loading %s.", path);
2138 }
2140 static int
2141 load_options(void)
2142 {
2143 const char *home = getenv("HOME");
2144 const char *tigrc_user = getenv("TIGRC_USER");
2145 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2146 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2147 char buf[SIZEOF_STR];
2149 if (!tigrc_system)
2150 tigrc_system = SYSCONFDIR "/tigrc";
2151 load_option_file(tigrc_system);
2153 if (!tigrc_user) {
2154 if (!home || !string_format(buf, "%s/.tigrc", home))
2155 return ERR;
2156 tigrc_user = buf;
2157 }
2158 load_option_file(tigrc_user);
2160 /* Add _after_ loading config files to avoid adding run requests
2161 * that conflict with keybindings. */
2162 add_builtin_run_requests();
2164 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2165 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2166 int argc = 0;
2168 if (!string_format(buf, "%s", tig_diff_opts) ||
2169 !argv_from_string(diff_opts, &argc, buf))
2170 die("TIG_DIFF_OPTS contains too many arguments");
2171 else if (!argv_copy(&opt_diff_args, diff_opts))
2172 die("Failed to format TIG_DIFF_OPTS arguments");
2173 }
2175 return OK;
2176 }
2179 /*
2180 * The viewer
2181 */
2183 struct view;
2184 struct view_ops;
2186 /* The display array of active views and the index of the current view. */
2187 static struct view *display[2];
2188 static unsigned int current_view;
2190 #define foreach_displayed_view(view, i) \
2191 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views() (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob[SIZEOF_REF] = "";
2197 static char ref_commit[SIZEOF_REF] = "HEAD";
2198 static char ref_head[SIZEOF_REF] = "HEAD";
2199 static char ref_branch[SIZEOF_REF] = "";
2201 enum view_type {
2202 VIEW_MAIN,
2203 VIEW_DIFF,
2204 VIEW_LOG,
2205 VIEW_TREE,
2206 VIEW_BLOB,
2207 VIEW_BLAME,
2208 VIEW_BRANCH,
2209 VIEW_HELP,
2210 VIEW_PAGER,
2211 VIEW_STATUS,
2212 VIEW_STAGE,
2213 };
2215 struct view {
2216 enum view_type type; /* View type */
2217 const char *name; /* View name */
2218 const char *cmd_env; /* Command line set via environment */
2219 const char *id; /* Points to either of ref_{head,commit,blob} */
2221 struct view_ops *ops; /* View operations */
2223 enum keymap keymap; /* What keymap does this view have */
2224 bool git_dir; /* Whether the view requires a git directory. */
2226 char ref[SIZEOF_REF]; /* Hovered commit reference */
2227 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2229 int height, width; /* The width and height of the main window */
2230 WINDOW *win; /* The main window */
2231 WINDOW *title; /* The title window living below the main window */
2233 /* Navigation */
2234 unsigned long offset; /* Offset of the window top */
2235 unsigned long yoffset; /* Offset from the window side. */
2236 unsigned long lineno; /* Current line number */
2237 unsigned long p_offset; /* Previous offset of the window top */
2238 unsigned long p_yoffset;/* Previous offset from the window side */
2239 unsigned long p_lineno; /* Previous current line number */
2240 bool p_restore; /* Should the previous position be restored. */
2242 /* Searching */
2243 char grep[SIZEOF_STR]; /* Search string */
2244 regex_t *regex; /* Pre-compiled regexp */
2246 /* If non-NULL, points to the view that opened this view. If this view
2247 * is closed tig will switch back to the parent view. */
2248 struct view *parent;
2249 struct view *prev;
2251 /* Buffering */
2252 size_t lines; /* Total number of lines */
2253 struct line *line; /* Line index */
2254 unsigned int digits; /* Number of digits in the lines member. */
2256 /* Drawing */
2257 struct line *curline; /* Line currently being drawn. */
2258 enum line_type curtype; /* Attribute currently used for drawing. */
2259 unsigned long col; /* Column when drawing. */
2260 bool has_scrolled; /* View was scrolled. */
2262 /* Loading */
2263 const char **argv; /* Shell command arguments. */
2264 const char *dir; /* Directory from which to execute. */
2265 struct io io;
2266 struct io *pipe;
2267 time_t start_time;
2268 time_t update_secs;
2269 };
2271 struct view_ops {
2272 /* What type of content being displayed. Used in the title bar. */
2273 const char *type;
2274 /* Default command arguments. */
2275 const char **argv;
2276 /* Open and reads in all view content. */
2277 bool (*open)(struct view *view);
2278 /* Read one line; updates view->line. */
2279 bool (*read)(struct view *view, char *data);
2280 /* Draw one line; @lineno must be < view->height. */
2281 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2282 /* Depending on view handle a special requests. */
2283 enum request (*request)(struct view *view, enum request request, struct line *line);
2284 /* Search for regexp in a line. */
2285 bool (*grep)(struct view *view, struct line *line);
2286 /* Select line */
2287 void (*select)(struct view *view, struct line *line);
2288 /* Prepare view for loading */
2289 bool (*prepare)(struct view *view);
2290 };
2292 static struct view_ops blame_ops;
2293 static struct view_ops blob_ops;
2294 static struct view_ops diff_ops;
2295 static struct view_ops help_ops;
2296 static struct view_ops log_ops;
2297 static struct view_ops main_ops;
2298 static struct view_ops pager_ops;
2299 static struct view_ops stage_ops;
2300 static struct view_ops status_ops;
2301 static struct view_ops tree_ops;
2302 static struct view_ops branch_ops;
2304 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2305 { type, name, #env, ref, ops, map, git }
2307 #define VIEW_(id, name, ops, git, ref) \
2308 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2310 static struct view views[] = {
2311 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2312 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2313 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2314 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2315 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2316 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2317 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2318 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2319 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2320 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2321 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2322 };
2324 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2326 #define foreach_view(view, i) \
2327 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2329 #define view_is_displayed(view) \
2330 (view == display[0] || view == display[1])
2332 static enum request
2333 view_request(struct view *view, enum request request)
2334 {
2335 if (!view || !view->lines)
2336 return request;
2337 return view->ops->request(view, request, &view->line[view->lineno]);
2338 }
2341 /*
2342 * View drawing.
2343 */
2345 static inline void
2346 set_view_attr(struct view *view, enum line_type type)
2347 {
2348 if (!view->curline->selected && view->curtype != type) {
2349 (void) wattrset(view->win, get_line_attr(type));
2350 wchgat(view->win, -1, 0, type, NULL);
2351 view->curtype = type;
2352 }
2353 }
2355 static int
2356 draw_chars(struct view *view, enum line_type type, const char *string,
2357 int max_len, bool use_tilde)
2358 {
2359 static char out_buffer[BUFSIZ * 2];
2360 int len = 0;
2361 int col = 0;
2362 int trimmed = FALSE;
2363 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2365 if (max_len <= 0)
2366 return 0;
2368 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2370 set_view_attr(view, type);
2371 if (len > 0) {
2372 if (opt_iconv_out != ICONV_NONE) {
2373 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2374 size_t inlen = len + 1;
2376 char *outbuf = out_buffer;
2377 size_t outlen = sizeof(out_buffer);
2379 size_t ret;
2381 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2382 if (ret != (size_t) -1) {
2383 string = out_buffer;
2384 len = sizeof(out_buffer) - outlen;
2385 }
2386 }
2388 waddnstr(view->win, string, len);
2389 }
2390 if (trimmed && use_tilde) {
2391 set_view_attr(view, LINE_DELIMITER);
2392 waddch(view->win, '~');
2393 col++;
2394 }
2396 return col;
2397 }
2399 static int
2400 draw_space(struct view *view, enum line_type type, int max, int spaces)
2401 {
2402 static char space[] = " ";
2403 int col = 0;
2405 spaces = MIN(max, spaces);
2407 while (spaces > 0) {
2408 int len = MIN(spaces, sizeof(space) - 1);
2410 col += draw_chars(view, type, space, len, FALSE);
2411 spaces -= len;
2412 }
2414 return col;
2415 }
2417 static bool
2418 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2419 {
2420 char text[SIZEOF_STR];
2422 do {
2423 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2425 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2426 string += pos;
2427 } while (*string && view->width + view->yoffset > view->col);
2429 return view->width + view->yoffset <= view->col;
2430 }
2432 static bool
2433 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2434 {
2435 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2436 int max = view->width + view->yoffset - view->col;
2437 int i;
2439 if (max < size)
2440 size = max;
2442 set_view_attr(view, type);
2443 /* Using waddch() instead of waddnstr() ensures that
2444 * they'll be rendered correctly for the cursor line. */
2445 for (i = skip; i < size; i++)
2446 waddch(view->win, graphic[i]);
2448 view->col += size;
2449 if (size < max && skip <= size)
2450 waddch(view->win, ' ');
2451 view->col++;
2453 return view->width + view->yoffset <= view->col;
2454 }
2456 static bool
2457 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2458 {
2459 int max = MIN(view->width + view->yoffset - view->col, len);
2460 int col;
2462 if (text)
2463 col = draw_chars(view, type, text, max - 1, trim);
2464 else
2465 col = draw_space(view, type, max - 1, max - 1);
2467 view->col += col;
2468 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2469 return view->width + view->yoffset <= view->col;
2470 }
2472 static bool
2473 draw_date(struct view *view, struct time *time)
2474 {
2475 const char *date = mkdate(time, opt_date);
2476 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2478 return draw_field(view, LINE_DATE, date, cols, FALSE);
2479 }
2481 static bool
2482 draw_author(struct view *view, const char *author)
2483 {
2484 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2485 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2487 if (abbreviate && author)
2488 author = get_author_initials(author);
2490 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2491 }
2493 static bool
2494 draw_mode(struct view *view, mode_t mode)
2495 {
2496 const char *str;
2498 if (S_ISDIR(mode))
2499 str = "drwxr-xr-x";
2500 else if (S_ISLNK(mode))
2501 str = "lrwxrwxrwx";
2502 else if (S_ISGITLINK(mode))
2503 str = "m---------";
2504 else if (S_ISREG(mode) && mode & S_IXUSR)
2505 str = "-rwxr-xr-x";
2506 else if (S_ISREG(mode))
2507 str = "-rw-r--r--";
2508 else
2509 str = "----------";
2511 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2512 }
2514 static bool
2515 draw_lineno(struct view *view, unsigned int lineno)
2516 {
2517 char number[10];
2518 int digits3 = view->digits < 3 ? 3 : view->digits;
2519 int max = MIN(view->width + view->yoffset - view->col, digits3);
2520 char *text = NULL;
2521 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2523 lineno += view->offset + 1;
2524 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2525 static char fmt[] = "%1ld";
2527 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2528 if (string_format(number, fmt, lineno))
2529 text = number;
2530 }
2531 if (text)
2532 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2533 else
2534 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2535 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2536 }
2538 static bool
2539 draw_view_line(struct view *view, unsigned int lineno)
2540 {
2541 struct line *line;
2542 bool selected = (view->offset + lineno == view->lineno);
2544 assert(view_is_displayed(view));
2546 if (view->offset + lineno >= view->lines)
2547 return FALSE;
2549 line = &view->line[view->offset + lineno];
2551 wmove(view->win, lineno, 0);
2552 if (line->cleareol)
2553 wclrtoeol(view->win);
2554 view->col = 0;
2555 view->curline = line;
2556 view->curtype = LINE_NONE;
2557 line->selected = FALSE;
2558 line->dirty = line->cleareol = 0;
2560 if (selected) {
2561 set_view_attr(view, LINE_CURSOR);
2562 line->selected = TRUE;
2563 view->ops->select(view, line);
2564 }
2566 return view->ops->draw(view, line, lineno);
2567 }
2569 static void
2570 redraw_view_dirty(struct view *view)
2571 {
2572 bool dirty = FALSE;
2573 int lineno;
2575 for (lineno = 0; lineno < view->height; lineno++) {
2576 if (view->offset + lineno >= view->lines)
2577 break;
2578 if (!view->line[view->offset + lineno].dirty)
2579 continue;
2580 dirty = TRUE;
2581 if (!draw_view_line(view, lineno))
2582 break;
2583 }
2585 if (!dirty)
2586 return;
2587 wnoutrefresh(view->win);
2588 }
2590 static void
2591 redraw_view_from(struct view *view, int lineno)
2592 {
2593 assert(0 <= lineno && lineno < view->height);
2595 for (; lineno < view->height; lineno++) {
2596 if (!draw_view_line(view, lineno))
2597 break;
2598 }
2600 wnoutrefresh(view->win);
2601 }
2603 static void
2604 redraw_view(struct view *view)
2605 {
2606 werase(view->win);
2607 redraw_view_from(view, 0);
2608 }
2611 static void
2612 update_view_title(struct view *view)
2613 {
2614 char buf[SIZEOF_STR];
2615 char state[SIZEOF_STR];
2616 size_t bufpos = 0, statelen = 0;
2618 assert(view_is_displayed(view));
2620 if (view->type != VIEW_STATUS && view->lines) {
2621 unsigned int view_lines = view->offset + view->height;
2622 unsigned int lines = view->lines
2623 ? MIN(view_lines, view->lines) * 100 / view->lines
2624 : 0;
2626 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2627 view->ops->type,
2628 view->lineno + 1,
2629 view->lines,
2630 lines);
2632 }
2634 if (view->pipe) {
2635 time_t secs = time(NULL) - view->start_time;
2637 /* Three git seconds are a long time ... */
2638 if (secs > 2)
2639 string_format_from(state, &statelen, " loading %lds", secs);
2640 }
2642 string_format_from(buf, &bufpos, "[%s]", view->name);
2643 if (*view->ref && bufpos < view->width) {
2644 size_t refsize = strlen(view->ref);
2645 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2647 if (minsize < view->width)
2648 refsize = view->width - minsize + 7;
2649 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2650 }
2652 if (statelen && bufpos < view->width) {
2653 string_format_from(buf, &bufpos, "%s", state);
2654 }
2656 if (view == display[current_view])
2657 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2658 else
2659 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2661 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2662 wclrtoeol(view->title);
2663 wnoutrefresh(view->title);
2664 }
2666 static int
2667 apply_step(double step, int value)
2668 {
2669 if (step >= 1)
2670 return (int) step;
2671 value *= step + 0.01;
2672 return value ? value : 1;
2673 }
2675 static void
2676 resize_display(void)
2677 {
2678 int offset, i;
2679 struct view *base = display[0];
2680 struct view *view = display[1] ? display[1] : display[0];
2682 /* Setup window dimensions */
2684 getmaxyx(stdscr, base->height, base->width);
2686 /* Make room for the status window. */
2687 base->height -= 1;
2689 if (view != base) {
2690 /* Horizontal split. */
2691 view->width = base->width;
2692 view->height = apply_step(opt_scale_split_view, base->height);
2693 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2694 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2695 base->height -= view->height;
2697 /* Make room for the title bar. */
2698 view->height -= 1;
2699 }
2701 /* Make room for the title bar. */
2702 base->height -= 1;
2704 offset = 0;
2706 foreach_displayed_view (view, i) {
2707 if (!view->win) {
2708 view->win = newwin(view->height, 0, offset, 0);
2709 if (!view->win)
2710 die("Failed to create %s view", view->name);
2712 scrollok(view->win, FALSE);
2714 view->title = newwin(1, 0, offset + view->height, 0);
2715 if (!view->title)
2716 die("Failed to create title window");
2718 } else {
2719 wresize(view->win, view->height, view->width);
2720 mvwin(view->win, offset, 0);
2721 mvwin(view->title, offset + view->height, 0);
2722 }
2724 offset += view->height + 1;
2725 }
2726 }
2728 static void
2729 redraw_display(bool clear)
2730 {
2731 struct view *view;
2732 int i;
2734 foreach_displayed_view (view, i) {
2735 if (clear)
2736 wclear(view->win);
2737 redraw_view(view);
2738 update_view_title(view);
2739 }
2740 }
2743 /*
2744 * Option management
2745 */
2747 static void
2748 toggle_enum_option_do(unsigned int *opt, const char *help,
2749 const struct enum_map *map, size_t size)
2750 {
2751 *opt = (*opt + 1) % size;
2752 redraw_display(FALSE);
2753 report("Displaying %s %s", enum_name(map[*opt]), help);
2754 }
2756 #define toggle_enum_option(opt, help, map) \
2757 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2759 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2760 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2762 static void
2763 toggle_view_option(bool *option, const char *help)
2764 {
2765 *option = !*option;
2766 redraw_display(FALSE);
2767 report("%sabling %s", *option ? "En" : "Dis", help);
2768 }
2770 static void
2771 open_option_menu(void)
2772 {
2773 const struct menu_item menu[] = {
2774 { '.', "line numbers", &opt_line_number },
2775 { 'D', "date display", &opt_date },
2776 { 'A', "author display", &opt_author },
2777 { 'g', "revision graph display", &opt_rev_graph },
2778 { 'F', "reference display", &opt_show_refs },
2779 { 0 }
2780 };
2781 int selected = 0;
2783 if (prompt_menu("Toggle option", menu, &selected)) {
2784 if (menu[selected].data == &opt_date)
2785 toggle_date();
2786 else if (menu[selected].data == &opt_author)
2787 toggle_author();
2788 else
2789 toggle_view_option(menu[selected].data, menu[selected].text);
2790 }
2791 }
2793 static void
2794 maximize_view(struct view *view)
2795 {
2796 memset(display, 0, sizeof(display));
2797 current_view = 0;
2798 display[current_view] = view;
2799 resize_display();
2800 redraw_display(FALSE);
2801 report("");
2802 }
2805 /*
2806 * Navigation
2807 */
2809 static bool
2810 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2811 {
2812 if (lineno >= view->lines)
2813 lineno = view->lines > 0 ? view->lines - 1 : 0;
2815 if (offset > lineno || offset + view->height <= lineno) {
2816 unsigned long half = view->height / 2;
2818 if (lineno > half)
2819 offset = lineno - half;
2820 else
2821 offset = 0;
2822 }
2824 if (offset != view->offset || lineno != view->lineno) {
2825 view->offset = offset;
2826 view->lineno = lineno;
2827 return TRUE;
2828 }
2830 return FALSE;
2831 }
2833 /* Scrolling backend */
2834 static void
2835 do_scroll_view(struct view *view, int lines)
2836 {
2837 bool redraw_current_line = FALSE;
2839 /* The rendering expects the new offset. */
2840 view->offset += lines;
2842 assert(0 <= view->offset && view->offset < view->lines);
2843 assert(lines);
2845 /* Move current line into the view. */
2846 if (view->lineno < view->offset) {
2847 view->lineno = view->offset;
2848 redraw_current_line = TRUE;
2849 } else if (view->lineno >= view->offset + view->height) {
2850 view->lineno = view->offset + view->height - 1;
2851 redraw_current_line = TRUE;
2852 }
2854 assert(view->offset <= view->lineno && view->lineno < view->lines);
2856 /* Redraw the whole screen if scrolling is pointless. */
2857 if (view->height < ABS(lines)) {
2858 redraw_view(view);
2860 } else {
2861 int line = lines > 0 ? view->height - lines : 0;
2862 int end = line + ABS(lines);
2864 scrollok(view->win, TRUE);
2865 wscrl(view->win, lines);
2866 scrollok(view->win, FALSE);
2868 while (line < end && draw_view_line(view, line))
2869 line++;
2871 if (redraw_current_line)
2872 draw_view_line(view, view->lineno - view->offset);
2873 wnoutrefresh(view->win);
2874 }
2876 view->has_scrolled = TRUE;
2877 report("");
2878 }
2880 /* Scroll frontend */
2881 static void
2882 scroll_view(struct view *view, enum request request)
2883 {
2884 int lines = 1;
2886 assert(view_is_displayed(view));
2888 switch (request) {
2889 case REQ_SCROLL_LEFT:
2890 if (view->yoffset == 0) {
2891 report("Cannot scroll beyond the first column");
2892 return;
2893 }
2894 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2895 view->yoffset = 0;
2896 else
2897 view->yoffset -= apply_step(opt_hscroll, view->width);
2898 redraw_view_from(view, 0);
2899 report("");
2900 return;
2901 case REQ_SCROLL_RIGHT:
2902 view->yoffset += apply_step(opt_hscroll, view->width);
2903 redraw_view(view);
2904 report("");
2905 return;
2906 case REQ_SCROLL_PAGE_DOWN:
2907 lines = view->height;
2908 case REQ_SCROLL_LINE_DOWN:
2909 if (view->offset + lines > view->lines)
2910 lines = view->lines - view->offset;
2912 if (lines == 0 || view->offset + view->height >= view->lines) {
2913 report("Cannot scroll beyond the last line");
2914 return;
2915 }
2916 break;
2918 case REQ_SCROLL_PAGE_UP:
2919 lines = view->height;
2920 case REQ_SCROLL_LINE_UP:
2921 if (lines > view->offset)
2922 lines = view->offset;
2924 if (lines == 0) {
2925 report("Cannot scroll beyond the first line");
2926 return;
2927 }
2929 lines = -lines;
2930 break;
2932 default:
2933 die("request %d not handled in switch", request);
2934 }
2936 do_scroll_view(view, lines);
2937 }
2939 /* Cursor moving */
2940 static void
2941 move_view(struct view *view, enum request request)
2942 {
2943 int scroll_steps = 0;
2944 int steps;
2946 switch (request) {
2947 case REQ_MOVE_FIRST_LINE:
2948 steps = -view->lineno;
2949 break;
2951 case REQ_MOVE_LAST_LINE:
2952 steps = view->lines - view->lineno - 1;
2953 break;
2955 case REQ_MOVE_PAGE_UP:
2956 steps = view->height > view->lineno
2957 ? -view->lineno : -view->height;
2958 break;
2960 case REQ_MOVE_PAGE_DOWN:
2961 steps = view->lineno + view->height >= view->lines
2962 ? view->lines - view->lineno - 1 : view->height;
2963 break;
2965 case REQ_MOVE_UP:
2966 steps = -1;
2967 break;
2969 case REQ_MOVE_DOWN:
2970 steps = 1;
2971 break;
2973 default:
2974 die("request %d not handled in switch", request);
2975 }
2977 if (steps <= 0 && view->lineno == 0) {
2978 report("Cannot move beyond the first line");
2979 return;
2981 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2982 report("Cannot move beyond the last line");
2983 return;
2984 }
2986 /* Move the current line */
2987 view->lineno += steps;
2988 assert(0 <= view->lineno && view->lineno < view->lines);
2990 /* Check whether the view needs to be scrolled */
2991 if (view->lineno < view->offset ||
2992 view->lineno >= view->offset + view->height) {
2993 scroll_steps = steps;
2994 if (steps < 0 && -steps > view->offset) {
2995 scroll_steps = -view->offset;
2997 } else if (steps > 0) {
2998 if (view->lineno == view->lines - 1 &&
2999 view->lines > view->height) {
3000 scroll_steps = view->lines - view->offset - 1;
3001 if (scroll_steps >= view->height)
3002 scroll_steps -= view->height - 1;
3003 }
3004 }
3005 }
3007 if (!view_is_displayed(view)) {
3008 view->offset += scroll_steps;
3009 assert(0 <= view->offset && view->offset < view->lines);
3010 view->ops->select(view, &view->line[view->lineno]);
3011 return;
3012 }
3014 /* Repaint the old "current" line if we be scrolling */
3015 if (ABS(steps) < view->height)
3016 draw_view_line(view, view->lineno - steps - view->offset);
3018 if (scroll_steps) {
3019 do_scroll_view(view, scroll_steps);
3020 return;
3021 }
3023 /* Draw the current line */
3024 draw_view_line(view, view->lineno - view->offset);
3026 wnoutrefresh(view->win);
3027 report("");
3028 }
3031 /*
3032 * Searching
3033 */
3035 static void search_view(struct view *view, enum request request);
3037 static bool
3038 grep_text(struct view *view, const char *text[])
3039 {
3040 regmatch_t pmatch;
3041 size_t i;
3043 for (i = 0; text[i]; i++)
3044 if (*text[i] &&
3045 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3046 return TRUE;
3047 return FALSE;
3048 }
3050 static void
3051 select_view_line(struct view *view, unsigned long lineno)
3052 {
3053 unsigned long old_lineno = view->lineno;
3054 unsigned long old_offset = view->offset;
3056 if (goto_view_line(view, view->offset, lineno)) {
3057 if (view_is_displayed(view)) {
3058 if (old_offset != view->offset) {
3059 redraw_view(view);
3060 } else {
3061 draw_view_line(view, old_lineno - view->offset);
3062 draw_view_line(view, view->lineno - view->offset);
3063 wnoutrefresh(view->win);
3064 }
3065 } else {
3066 view->ops->select(view, &view->line[view->lineno]);
3067 }
3068 }
3069 }
3071 static void
3072 find_next(struct view *view, enum request request)
3073 {
3074 unsigned long lineno = view->lineno;
3075 int direction;
3077 if (!*view->grep) {
3078 if (!*opt_search)
3079 report("No previous search");
3080 else
3081 search_view(view, request);
3082 return;
3083 }
3085 switch (request) {
3086 case REQ_SEARCH:
3087 case REQ_FIND_NEXT:
3088 direction = 1;
3089 break;
3091 case REQ_SEARCH_BACK:
3092 case REQ_FIND_PREV:
3093 direction = -1;
3094 break;
3096 default:
3097 return;
3098 }
3100 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3101 lineno += direction;
3103 /* Note, lineno is unsigned long so will wrap around in which case it
3104 * will become bigger than view->lines. */
3105 for (; lineno < view->lines; lineno += direction) {
3106 if (view->ops->grep(view, &view->line[lineno])) {
3107 select_view_line(view, lineno);
3108 report("Line %ld matches '%s'", lineno + 1, view->grep);
3109 return;
3110 }
3111 }
3113 report("No match found for '%s'", view->grep);
3114 }
3116 static void
3117 search_view(struct view *view, enum request request)
3118 {
3119 int regex_err;
3121 if (view->regex) {
3122 regfree(view->regex);
3123 *view->grep = 0;
3124 } else {
3125 view->regex = calloc(1, sizeof(*view->regex));
3126 if (!view->regex)
3127 return;
3128 }
3130 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3131 if (regex_err != 0) {
3132 char buf[SIZEOF_STR] = "unknown error";
3134 regerror(regex_err, view->regex, buf, sizeof(buf));
3135 report("Search failed: %s", buf);
3136 return;
3137 }
3139 string_copy(view->grep, opt_search);
3141 find_next(view, request);
3142 }
3144 /*
3145 * Incremental updating
3146 */
3148 static void
3149 reset_view(struct view *view)
3150 {
3151 int i;
3153 for (i = 0; i < view->lines; i++)
3154 free(view->line[i].data);
3155 free(view->line);
3157 view->p_offset = view->offset;
3158 view->p_yoffset = view->yoffset;
3159 view->p_lineno = view->lineno;
3161 view->line = NULL;
3162 view->offset = 0;
3163 view->yoffset = 0;
3164 view->lines = 0;
3165 view->lineno = 0;
3166 view->vid[0] = 0;
3167 view->update_secs = 0;
3168 }
3170 static const char *
3171 format_arg(const char *name)
3172 {
3173 static struct {
3174 const char *name;
3175 size_t namelen;
3176 const char *value;
3177 const char *value_if_empty;
3178 } vars[] = {
3179 #define FORMAT_VAR(name, value, value_if_empty) \
3180 { name, STRING_SIZE(name), value, value_if_empty }
3181 FORMAT_VAR("%(directory)", opt_path, ""),
3182 FORMAT_VAR("%(file)", opt_file, ""),
3183 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3184 FORMAT_VAR("%(head)", ref_head, ""),
3185 FORMAT_VAR("%(commit)", ref_commit, ""),
3186 FORMAT_VAR("%(blob)", ref_blob, ""),
3187 FORMAT_VAR("%(branch)", ref_branch, ""),
3188 };
3189 int i;
3191 for (i = 0; i < ARRAY_SIZE(vars); i++)
3192 if (!strncmp(name, vars[i].name, vars[i].namelen))
3193 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3195 report("Unknown replacement: `%s`", name);
3196 return NULL;
3197 }
3199 static bool
3200 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3201 {
3202 char buf[SIZEOF_STR];
3203 int argc;
3205 argv_free(*dst_argv);
3207 for (argc = 0; src_argv[argc]; argc++) {
3208 const char *arg = src_argv[argc];
3209 size_t bufpos = 0;
3211 if (!strcmp(arg, "%(fileargs)")) {
3212 if (!argv_append_array(dst_argv, opt_file_args))
3213 break;
3214 continue;
3216 } else if (!strcmp(arg, "%(diffargs)")) {
3217 if (!argv_append_array(dst_argv, opt_diff_args))
3218 break;
3219 continue;
3221 } else if (!strcmp(arg, "%(revargs)")) {
3222 if (!argv_append_array(dst_argv, opt_rev_args))
3223 break;
3224 continue;
3225 }
3227 while (arg) {
3228 char *next = strstr(arg, "%(");
3229 int len = next - arg;
3230 const char *value;
3232 if (!next || !replace) {
3233 len = strlen(arg);
3234 value = "";
3236 } else {
3237 value = format_arg(next);
3239 if (!value) {
3240 return FALSE;
3241 }
3242 }
3244 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3245 return FALSE;
3247 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3248 }
3250 if (!argv_append(dst_argv, buf))
3251 break;
3252 }
3254 return src_argv[argc] == NULL;
3255 }
3257 static bool
3258 restore_view_position(struct view *view)
3259 {
3260 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3261 return FALSE;
3263 /* Changing the view position cancels the restoring. */
3264 /* FIXME: Changing back to the first line is not detected. */
3265 if (view->offset != 0 || view->lineno != 0) {
3266 view->p_restore = FALSE;
3267 return FALSE;
3268 }
3270 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3271 view_is_displayed(view))
3272 werase(view->win);
3274 view->yoffset = view->p_yoffset;
3275 view->p_restore = FALSE;
3277 return TRUE;
3278 }
3280 static void
3281 end_update(struct view *view, bool force)
3282 {
3283 if (!view->pipe)
3284 return;
3285 while (!view->ops->read(view, NULL))
3286 if (!force)
3287 return;
3288 if (force)
3289 io_kill(view->pipe);
3290 io_done(view->pipe);
3291 view->pipe = NULL;
3292 }
3294 static void
3295 setup_update(struct view *view, const char *vid)
3296 {
3297 reset_view(view);
3298 string_copy_rev(view->vid, vid);
3299 view->pipe = &view->io;
3300 view->start_time = time(NULL);
3301 }
3303 static bool
3304 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3305 {
3306 view->dir = dir;
3307 return format_argv(&view->argv, argv, replace);
3308 }
3310 static bool
3311 prepare_update(struct view *view, const char *argv[], const char *dir)
3312 {
3313 if (view->pipe)
3314 end_update(view, TRUE);
3315 return prepare_io(view, dir, argv, FALSE);
3316 }
3318 static bool
3319 start_update(struct view *view, const char **argv, const char *dir)
3320 {
3321 if (view->pipe)
3322 io_done(view->pipe);
3323 return prepare_io(view, dir, argv, FALSE) &&
3324 io_run(&view->io, IO_RD, dir, view->argv);
3325 }
3327 static bool
3328 prepare_update_file(struct view *view, const char *name)
3329 {
3330 if (view->pipe)
3331 end_update(view, TRUE);
3332 argv_free(view->argv);
3333 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3334 }
3336 static bool
3337 begin_update(struct view *view, bool refresh)
3338 {
3339 if (view->pipe)
3340 end_update(view, TRUE);
3342 if (!refresh) {
3343 if (view->ops->prepare) {
3344 if (!view->ops->prepare(view))
3345 return FALSE;
3346 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3347 return FALSE;
3348 }
3350 /* Put the current ref_* value to the view title ref
3351 * member. This is needed by the blob view. Most other
3352 * views sets it automatically after loading because the
3353 * first line is a commit line. */
3354 string_copy_rev(view->ref, view->id);
3355 }
3357 if (view->argv && view->argv[0] &&
3358 !io_run(&view->io, IO_RD, view->dir, view->argv))
3359 return FALSE;
3361 setup_update(view, view->id);
3363 return TRUE;
3364 }
3366 static bool
3367 update_view(struct view *view)
3368 {
3369 char out_buffer[BUFSIZ * 2];
3370 char *line;
3371 /* Clear the view and redraw everything since the tree sorting
3372 * might have rearranged things. */
3373 bool redraw = view->lines == 0;
3374 bool can_read = TRUE;
3376 if (!view->pipe)
3377 return TRUE;
3379 if (!io_can_read(view->pipe)) {
3380 if (view->lines == 0 && view_is_displayed(view)) {
3381 time_t secs = time(NULL) - view->start_time;
3383 if (secs > 1 && secs > view->update_secs) {
3384 if (view->update_secs == 0)
3385 redraw_view(view);
3386 update_view_title(view);
3387 view->update_secs = secs;
3388 }
3389 }
3390 return TRUE;
3391 }
3393 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3394 if (opt_iconv_in != ICONV_NONE) {
3395 ICONV_CONST char *inbuf = line;
3396 size_t inlen = strlen(line) + 1;
3398 char *outbuf = out_buffer;
3399 size_t outlen = sizeof(out_buffer);
3401 size_t ret;
3403 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3404 if (ret != (size_t) -1)
3405 line = out_buffer;
3406 }
3408 if (!view->ops->read(view, line)) {
3409 report("Allocation failure");
3410 end_update(view, TRUE);
3411 return FALSE;
3412 }
3413 }
3415 {
3416 unsigned long lines = view->lines;
3417 int digits;
3419 for (digits = 0; lines; digits++)
3420 lines /= 10;
3422 /* Keep the displayed view in sync with line number scaling. */
3423 if (digits != view->digits) {
3424 view->digits = digits;
3425 if (opt_line_number || view->type == VIEW_BLAME)
3426 redraw = TRUE;
3427 }
3428 }
3430 if (io_error(view->pipe)) {
3431 report("Failed to read: %s", io_strerror(view->pipe));
3432 end_update(view, TRUE);
3434 } else if (io_eof(view->pipe)) {
3435 if (view_is_displayed(view))
3436 report("");
3437 end_update(view, FALSE);
3438 }
3440 if (restore_view_position(view))
3441 redraw = TRUE;
3443 if (!view_is_displayed(view))
3444 return TRUE;
3446 if (redraw)
3447 redraw_view_from(view, 0);
3448 else
3449 redraw_view_dirty(view);
3451 /* Update the title _after_ the redraw so that if the redraw picks up a
3452 * commit reference in view->ref it'll be available here. */
3453 update_view_title(view);
3454 return TRUE;
3455 }
3457 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3459 static struct line *
3460 add_line_data(struct view *view, void *data, enum line_type type)
3461 {
3462 struct line *line;
3464 if (!realloc_lines(&view->line, view->lines, 1))
3465 return NULL;
3467 line = &view->line[view->lines++];
3468 memset(line, 0, sizeof(*line));
3469 line->type = type;
3470 line->data = data;
3471 line->dirty = 1;
3473 return line;
3474 }
3476 static struct line *
3477 add_line_text(struct view *view, const char *text, enum line_type type)
3478 {
3479 char *data = text ? strdup(text) : NULL;
3481 return data ? add_line_data(view, data, type) : NULL;
3482 }
3484 static struct line *
3485 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3486 {
3487 char buf[SIZEOF_STR];
3488 va_list args;
3490 va_start(args, fmt);
3491 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3492 buf[0] = 0;
3493 va_end(args);
3495 return buf[0] ? add_line_text(view, buf, type) : NULL;
3496 }
3498 /*
3499 * View opening
3500 */
3502 enum open_flags {
3503 OPEN_DEFAULT = 0, /* Use default view switching. */
3504 OPEN_SPLIT = 1, /* Split current view. */
3505 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3506 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3507 OPEN_PREPARED = 32, /* Open already prepared command. */
3508 };
3510 static void
3511 open_view(struct view *prev, enum request request, enum open_flags flags)
3512 {
3513 bool split = !!(flags & OPEN_SPLIT);
3514 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3515 bool nomaximize = !!(flags & OPEN_REFRESH);
3516 struct view *view = VIEW(request);
3517 int nviews = displayed_views();
3518 struct view *base_view = display[0];
3520 if (view == prev && nviews == 1 && !reload) {
3521 report("Already in %s view", view->name);
3522 return;
3523 }
3525 if (view->git_dir && !opt_git_dir[0]) {
3526 report("The %s view is disabled in pager view", view->name);
3527 return;
3528 }
3530 if (split) {
3531 display[1] = view;
3532 current_view = 1;
3533 view->parent = prev;
3534 } else if (!nomaximize) {
3535 /* Maximize the current view. */
3536 memset(display, 0, sizeof(display));
3537 current_view = 0;
3538 display[current_view] = view;
3539 }
3541 /* No prev signals that this is the first loaded view. */
3542 if (prev && view != prev) {
3543 view->prev = prev;
3544 }
3546 /* Resize the view when switching between split- and full-screen,
3547 * or when switching between two different full-screen views. */
3548 if (nviews != displayed_views() ||
3549 (nviews == 1 && base_view != display[0]))
3550 resize_display();
3552 if (view->ops->open) {
3553 if (view->pipe)
3554 end_update(view, TRUE);
3555 if (!view->ops->open(view)) {
3556 report("Failed to load %s view", view->name);
3557 return;
3558 }
3559 restore_view_position(view);
3561 } else if ((reload || strcmp(view->vid, view->id)) &&
3562 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3563 report("Failed to load %s view", view->name);
3564 return;
3565 }
3567 if (split && prev->lineno - prev->offset >= prev->height) {
3568 /* Take the title line into account. */
3569 int lines = prev->lineno - prev->offset - prev->height + 1;
3571 /* Scroll the view that was split if the current line is
3572 * outside the new limited view. */
3573 do_scroll_view(prev, lines);
3574 }
3576 if (prev && view != prev && split && view_is_displayed(prev)) {
3577 /* "Blur" the previous view. */
3578 update_view_title(prev);
3579 }
3581 if (view->pipe && view->lines == 0) {
3582 /* Clear the old view and let the incremental updating refill
3583 * the screen. */
3584 werase(view->win);
3585 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3586 report("");
3587 } else if (view_is_displayed(view)) {
3588 redraw_view(view);
3589 report("");
3590 }
3591 }
3593 static void
3594 open_external_viewer(const char *argv[], const char *dir)
3595 {
3596 def_prog_mode(); /* save current tty modes */
3597 endwin(); /* restore original tty modes */
3598 io_run_fg(argv, dir);
3599 fprintf(stderr, "Press Enter to continue");
3600 getc(opt_tty);
3601 reset_prog_mode();
3602 redraw_display(TRUE);
3603 }
3605 static void
3606 open_mergetool(const char *file)
3607 {
3608 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3610 open_external_viewer(mergetool_argv, opt_cdup);
3611 }
3613 static void
3614 open_editor(const char *file)
3615 {
3616 const char *editor_argv[] = { "vi", file, NULL };
3617 const char *editor;
3619 editor = getenv("GIT_EDITOR");
3620 if (!editor && *opt_editor)
3621 editor = opt_editor;
3622 if (!editor)
3623 editor = getenv("VISUAL");
3624 if (!editor)
3625 editor = getenv("EDITOR");
3626 if (!editor)
3627 editor = "vi";
3629 editor_argv[0] = editor;
3630 open_external_viewer(editor_argv, opt_cdup);
3631 }
3633 static void
3634 open_run_request(enum request request)
3635 {
3636 struct run_request *req = get_run_request(request);
3637 const char **argv = NULL;
3639 if (!req) {
3640 report("Unknown run request");
3641 return;
3642 }
3644 if (format_argv(&argv, req->argv, TRUE))
3645 open_external_viewer(argv, NULL);
3646 if (argv)
3647 argv_free(argv);
3648 free(argv);
3649 }
3651 /*
3652 * User request switch noodle
3653 */
3655 static int
3656 view_driver(struct view *view, enum request request)
3657 {
3658 int i;
3660 if (request == REQ_NONE)
3661 return TRUE;
3663 if (request > REQ_NONE) {
3664 open_run_request(request);
3665 view_request(view, REQ_REFRESH);
3666 return TRUE;
3667 }
3669 request = view_request(view, request);
3670 if (request == REQ_NONE)
3671 return TRUE;
3673 switch (request) {
3674 case REQ_MOVE_UP:
3675 case REQ_MOVE_DOWN:
3676 case REQ_MOVE_PAGE_UP:
3677 case REQ_MOVE_PAGE_DOWN:
3678 case REQ_MOVE_FIRST_LINE:
3679 case REQ_MOVE_LAST_LINE:
3680 move_view(view, request);
3681 break;
3683 case REQ_SCROLL_LEFT:
3684 case REQ_SCROLL_RIGHT:
3685 case REQ_SCROLL_LINE_DOWN:
3686 case REQ_SCROLL_LINE_UP:
3687 case REQ_SCROLL_PAGE_DOWN:
3688 case REQ_SCROLL_PAGE_UP:
3689 scroll_view(view, request);
3690 break;
3692 case REQ_VIEW_BLAME:
3693 if (!opt_file[0]) {
3694 report("No file chosen, press %s to open tree view",
3695 get_key(view->keymap, REQ_VIEW_TREE));
3696 break;
3697 }
3698 open_view(view, request, OPEN_DEFAULT);
3699 break;
3701 case REQ_VIEW_BLOB:
3702 if (!ref_blob[0]) {
3703 report("No file chosen, press %s to open tree view",
3704 get_key(view->keymap, REQ_VIEW_TREE));
3705 break;
3706 }
3707 open_view(view, request, OPEN_DEFAULT);
3708 break;
3710 case REQ_VIEW_PAGER:
3711 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3712 report("No pager content, press %s to run command from prompt",
3713 get_key(view->keymap, REQ_PROMPT));
3714 break;
3715 }
3716 open_view(view, request, OPEN_DEFAULT);
3717 break;
3719 case REQ_VIEW_STAGE:
3720 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3721 report("No stage content, press %s to open the status view and choose file",
3722 get_key(view->keymap, REQ_VIEW_STATUS));
3723 break;
3724 }
3725 open_view(view, request, OPEN_DEFAULT);
3726 break;
3728 case REQ_VIEW_STATUS:
3729 if (opt_is_inside_work_tree == FALSE) {
3730 report("The status view requires a working tree");
3731 break;
3732 }
3733 open_view(view, request, OPEN_DEFAULT);
3734 break;
3736 case REQ_VIEW_MAIN:
3737 case REQ_VIEW_DIFF:
3738 case REQ_VIEW_LOG:
3739 case REQ_VIEW_TREE:
3740 case REQ_VIEW_HELP:
3741 case REQ_VIEW_BRANCH:
3742 open_view(view, request, OPEN_DEFAULT);
3743 break;
3745 case REQ_NEXT:
3746 case REQ_PREVIOUS:
3747 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3749 if (view->parent) {
3750 int line;
3752 view = view->parent;
3753 line = view->lineno;
3754 move_view(view, request);
3755 if (view_is_displayed(view))
3756 update_view_title(view);
3757 if (line != view->lineno)
3758 view_request(view, REQ_ENTER);
3759 } else {
3760 move_view(view, request);
3761 }
3762 break;
3764 case REQ_VIEW_NEXT:
3765 {
3766 int nviews = displayed_views();
3767 int next_view = (current_view + 1) % nviews;
3769 if (next_view == current_view) {
3770 report("Only one view is displayed");
3771 break;
3772 }
3774 current_view = next_view;
3775 /* Blur out the title of the previous view. */
3776 update_view_title(view);
3777 report("");
3778 break;
3779 }
3780 case REQ_REFRESH:
3781 report("Refreshing is not yet supported for the %s view", view->name);
3782 break;
3784 case REQ_MAXIMIZE:
3785 if (displayed_views() == 2)
3786 maximize_view(view);
3787 break;
3789 case REQ_OPTIONS:
3790 open_option_menu();
3791 break;
3793 case REQ_TOGGLE_LINENO:
3794 toggle_view_option(&opt_line_number, "line numbers");
3795 break;
3797 case REQ_TOGGLE_DATE:
3798 toggle_date();
3799 break;
3801 case REQ_TOGGLE_AUTHOR:
3802 toggle_author();
3803 break;
3805 case REQ_TOGGLE_REV_GRAPH:
3806 toggle_view_option(&opt_rev_graph, "revision graph display");
3807 break;
3809 case REQ_TOGGLE_REFS:
3810 toggle_view_option(&opt_show_refs, "reference display");
3811 break;
3813 case REQ_TOGGLE_SORT_FIELD:
3814 case REQ_TOGGLE_SORT_ORDER:
3815 report("Sorting is not yet supported for the %s view", view->name);
3816 break;
3818 case REQ_SEARCH:
3819 case REQ_SEARCH_BACK:
3820 search_view(view, request);
3821 break;
3823 case REQ_FIND_NEXT:
3824 case REQ_FIND_PREV:
3825 find_next(view, request);
3826 break;
3828 case REQ_STOP_LOADING:
3829 foreach_view(view, i) {
3830 if (view->pipe)
3831 report("Stopped loading the %s view", view->name),
3832 end_update(view, TRUE);
3833 }
3834 break;
3836 case REQ_SHOW_VERSION:
3837 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3838 return TRUE;
3840 case REQ_SCREEN_REDRAW:
3841 redraw_display(TRUE);
3842 break;
3844 case REQ_EDIT:
3845 report("Nothing to edit");
3846 break;
3848 case REQ_ENTER:
3849 report("Nothing to enter");
3850 break;
3852 case REQ_VIEW_CLOSE:
3853 /* XXX: Mark closed views by letting view->prev point to the
3854 * view itself. Parents to closed view should never be
3855 * followed. */
3856 if (view->prev && view->prev != view) {
3857 maximize_view(view->prev);
3858 view->prev = view;
3859 break;
3860 }
3861 /* Fall-through */
3862 case REQ_QUIT:
3863 return FALSE;
3865 default:
3866 report("Unknown key, press %s for help",
3867 get_key(view->keymap, REQ_VIEW_HELP));
3868 return TRUE;
3869 }
3871 return TRUE;
3872 }
3875 /*
3876 * View backend utilities
3877 */
3879 enum sort_field {
3880 ORDERBY_NAME,
3881 ORDERBY_DATE,
3882 ORDERBY_AUTHOR,
3883 };
3885 struct sort_state {
3886 const enum sort_field *fields;
3887 size_t size, current;
3888 bool reverse;
3889 };
3891 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3892 #define get_sort_field(state) ((state).fields[(state).current])
3893 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3895 static void
3896 sort_view(struct view *view, enum request request, struct sort_state *state,
3897 int (*compare)(const void *, const void *))
3898 {
3899 switch (request) {
3900 case REQ_TOGGLE_SORT_FIELD:
3901 state->current = (state->current + 1) % state->size;
3902 break;
3904 case REQ_TOGGLE_SORT_ORDER:
3905 state->reverse = !state->reverse;
3906 break;
3907 default:
3908 die("Not a sort request");
3909 }
3911 qsort(view->line, view->lines, sizeof(*view->line), compare);
3912 redraw_view(view);
3913 }
3915 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3917 /* Small author cache to reduce memory consumption. It uses binary
3918 * search to lookup or find place to position new entries. No entries
3919 * are ever freed. */
3920 static const char *
3921 get_author(const char *name)
3922 {
3923 static const char **authors;
3924 static size_t authors_size;
3925 int from = 0, to = authors_size - 1;
3927 while (from <= to) {
3928 size_t pos = (to + from) / 2;
3929 int cmp = strcmp(name, authors[pos]);
3931 if (!cmp)
3932 return authors[pos];
3934 if (cmp < 0)
3935 to = pos - 1;
3936 else
3937 from = pos + 1;
3938 }
3940 if (!realloc_authors(&authors, authors_size, 1))
3941 return NULL;
3942 name = strdup(name);
3943 if (!name)
3944 return NULL;
3946 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3947 authors[from] = name;
3948 authors_size++;
3950 return name;
3951 }
3953 static void
3954 parse_timesec(struct time *time, const char *sec)
3955 {
3956 time->sec = (time_t) atol(sec);
3957 }
3959 static void
3960 parse_timezone(struct time *time, const char *zone)
3961 {
3962 long tz;
3964 tz = ('0' - zone[1]) * 60 * 60 * 10;
3965 tz += ('0' - zone[2]) * 60 * 60;
3966 tz += ('0' - zone[3]) * 60 * 10;
3967 tz += ('0' - zone[4]) * 60;
3969 if (zone[0] == '-')
3970 tz = -tz;
3972 time->tz = tz;
3973 time->sec -= tz;
3974 }
3976 /* Parse author lines where the name may be empty:
3977 * author <email@address.tld> 1138474660 +0100
3978 */
3979 static void
3980 parse_author_line(char *ident, const char **author, struct time *time)
3981 {
3982 char *nameend = strchr(ident, '<');
3983 char *emailend = strchr(ident, '>');
3985 if (nameend && emailend)
3986 *nameend = *emailend = 0;
3987 ident = chomp_string(ident);
3988 if (!*ident) {
3989 if (nameend)
3990 ident = chomp_string(nameend + 1);
3991 if (!*ident)
3992 ident = "Unknown";
3993 }
3995 *author = get_author(ident);
3997 /* Parse epoch and timezone */
3998 if (emailend && emailend[1] == ' ') {
3999 char *secs = emailend + 2;
4000 char *zone = strchr(secs, ' ');
4002 parse_timesec(time, secs);
4004 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4005 parse_timezone(time, zone + 1);
4006 }
4007 }
4009 /*
4010 * Pager backend
4011 */
4013 static bool
4014 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4015 {
4016 if (opt_line_number && draw_lineno(view, lineno))
4017 return TRUE;
4019 draw_text(view, line->type, line->data, TRUE);
4020 return TRUE;
4021 }
4023 static bool
4024 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4025 {
4026 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4027 char ref[SIZEOF_STR];
4029 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4030 return TRUE;
4032 /* This is the only fatal call, since it can "corrupt" the buffer. */
4033 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4034 return FALSE;
4036 return TRUE;
4037 }
4039 static void
4040 add_pager_refs(struct view *view, struct line *line)
4041 {
4042 char buf[SIZEOF_STR];
4043 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4044 struct ref_list *list;
4045 size_t bufpos = 0, i;
4046 const char *sep = "Refs: ";
4047 bool is_tag = FALSE;
4049 assert(line->type == LINE_COMMIT);
4051 list = get_ref_list(commit_id);
4052 if (!list) {
4053 if (view->type == VIEW_DIFF)
4054 goto try_add_describe_ref;
4055 return;
4056 }
4058 for (i = 0; i < list->size; i++) {
4059 struct ref *ref = list->refs[i];
4060 const char *fmt = ref->tag ? "%s[%s]" :
4061 ref->remote ? "%s<%s>" : "%s%s";
4063 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4064 return;
4065 sep = ", ";
4066 if (ref->tag)
4067 is_tag = TRUE;
4068 }
4070 if (!is_tag && view->type == VIEW_DIFF) {
4071 try_add_describe_ref:
4072 /* Add <tag>-g<commit_id> "fake" reference. */
4073 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4074 return;
4075 }
4077 if (bufpos == 0)
4078 return;
4080 add_line_text(view, buf, LINE_PP_REFS);
4081 }
4083 static bool
4084 pager_read(struct view *view, char *data)
4085 {
4086 struct line *line;
4088 if (!data)
4089 return TRUE;
4091 line = add_line_text(view, data, get_line_type(data));
4092 if (!line)
4093 return FALSE;
4095 if (line->type == LINE_COMMIT &&
4096 (view->type == VIEW_DIFF ||
4097 view->type == VIEW_LOG))
4098 add_pager_refs(view, line);
4100 return TRUE;
4101 }
4103 static enum request
4104 pager_request(struct view *view, enum request request, struct line *line)
4105 {
4106 int split = 0;
4108 if (request != REQ_ENTER)
4109 return request;
4111 if (line->type == LINE_COMMIT &&
4112 (view->type == VIEW_LOG ||
4113 view->type == VIEW_PAGER)) {
4114 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4115 split = 1;
4116 }
4118 /* Always scroll the view even if it was split. That way
4119 * you can use Enter to scroll through the log view and
4120 * split open each commit diff. */
4121 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4123 /* FIXME: A minor workaround. Scrolling the view will call report("")
4124 * but if we are scrolling a non-current view this won't properly
4125 * update the view title. */
4126 if (split)
4127 update_view_title(view);
4129 return REQ_NONE;
4130 }
4132 static bool
4133 pager_grep(struct view *view, struct line *line)
4134 {
4135 const char *text[] = { line->data, NULL };
4137 return grep_text(view, text);
4138 }
4140 static void
4141 pager_select(struct view *view, struct line *line)
4142 {
4143 if (line->type == LINE_COMMIT) {
4144 char *text = (char *)line->data + STRING_SIZE("commit ");
4146 if (view->type != VIEW_PAGER)
4147 string_copy_rev(view->ref, text);
4148 string_copy_rev(ref_commit, text);
4149 }
4150 }
4152 static struct view_ops pager_ops = {
4153 "line",
4154 NULL,
4155 NULL,
4156 pager_read,
4157 pager_draw,
4158 pager_request,
4159 pager_grep,
4160 pager_select,
4161 };
4163 static const char *log_argv[SIZEOF_ARG] = {
4164 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4165 };
4167 static enum request
4168 log_request(struct view *view, enum request request, struct line *line)
4169 {
4170 switch (request) {
4171 case REQ_REFRESH:
4172 load_refs();
4173 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4174 return REQ_NONE;
4175 default:
4176 return pager_request(view, request, line);
4177 }
4178 }
4180 static struct view_ops log_ops = {
4181 "line",
4182 log_argv,
4183 NULL,
4184 pager_read,
4185 pager_draw,
4186 log_request,
4187 pager_grep,
4188 pager_select,
4189 };
4191 static const char *diff_argv[SIZEOF_ARG] = {
4192 "git", "show", "--pretty=fuller", "--no-color", "--root",
4193 "--patch-with-stat", "--find-copies-harder", "-C",
4194 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4195 };
4197 static bool
4198 diff_read(struct view *view, char *data)
4199 {
4200 if (!data) {
4201 /* Fall back to retry if no diff will be shown. */
4202 if (view->lines == 0 && opt_file_args) {
4203 int pos = argv_size(view->argv)
4204 - argv_size(opt_file_args) - 1;
4206 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4207 for (; view->argv[pos]; pos++) {
4208 free((void *) view->argv[pos]);
4209 view->argv[pos] = NULL;
4210 }
4212 if (view->pipe)
4213 io_done(view->pipe);
4214 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4215 return FALSE;
4216 }
4217 }
4218 return TRUE;
4219 }
4221 return pager_read(view, data);
4222 }
4224 static struct view_ops diff_ops = {
4225 "line",
4226 diff_argv,
4227 NULL,
4228 diff_read,
4229 pager_draw,
4230 pager_request,
4231 pager_grep,
4232 pager_select,
4233 };
4235 /*
4236 * Help backend
4237 */
4239 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4241 static bool
4242 help_open_keymap_title(struct view *view, enum keymap keymap)
4243 {
4244 struct line *line;
4246 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4247 help_keymap_hidden[keymap] ? '+' : '-',
4248 enum_name(keymap_table[keymap]));
4249 if (line)
4250 line->other = keymap;
4252 return help_keymap_hidden[keymap];
4253 }
4255 static void
4256 help_open_keymap(struct view *view, enum keymap keymap)
4257 {
4258 const char *group = NULL;
4259 char buf[SIZEOF_STR];
4260 size_t bufpos;
4261 bool add_title = TRUE;
4262 int i;
4264 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4265 const char *key = NULL;
4267 if (req_info[i].request == REQ_NONE)
4268 continue;
4270 if (!req_info[i].request) {
4271 group = req_info[i].help;
4272 continue;
4273 }
4275 key = get_keys(keymap, req_info[i].request, TRUE);
4276 if (!key || !*key)
4277 continue;
4279 if (add_title && help_open_keymap_title(view, keymap))
4280 return;
4281 add_title = FALSE;
4283 if (group) {
4284 add_line_text(view, group, LINE_HELP_GROUP);
4285 group = NULL;
4286 }
4288 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4289 enum_name(req_info[i]), req_info[i].help);
4290 }
4292 group = "External commands:";
4294 for (i = 0; i < run_requests; i++) {
4295 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4296 const char *key;
4297 int argc;
4299 if (!req || req->keymap != keymap)
4300 continue;
4302 key = get_key_name(req->key);
4303 if (!*key)
4304 key = "(no key defined)";
4306 if (add_title && help_open_keymap_title(view, keymap))
4307 return;
4308 if (group) {
4309 add_line_text(view, group, LINE_HELP_GROUP);
4310 group = NULL;
4311 }
4313 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4314 if (!string_format_from(buf, &bufpos, "%s%s",
4315 argc ? " " : "", req->argv[argc]))
4316 return;
4318 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4319 }
4320 }
4322 static bool
4323 help_open(struct view *view)
4324 {
4325 enum keymap keymap;
4327 reset_view(view);
4328 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4329 add_line_text(view, "", LINE_DEFAULT);
4331 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4332 help_open_keymap(view, keymap);
4334 return TRUE;
4335 }
4337 static enum request
4338 help_request(struct view *view, enum request request, struct line *line)
4339 {
4340 switch (request) {
4341 case REQ_ENTER:
4342 if (line->type == LINE_HELP_KEYMAP) {
4343 help_keymap_hidden[line->other] =
4344 !help_keymap_hidden[line->other];
4345 view->p_restore = TRUE;
4346 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4347 }
4349 return REQ_NONE;
4350 default:
4351 return pager_request(view, request, line);
4352 }
4353 }
4355 static struct view_ops help_ops = {
4356 "line",
4357 NULL,
4358 help_open,
4359 NULL,
4360 pager_draw,
4361 help_request,
4362 pager_grep,
4363 pager_select,
4364 };
4367 /*
4368 * Tree backend
4369 */
4371 struct tree_stack_entry {
4372 struct tree_stack_entry *prev; /* Entry below this in the stack */
4373 unsigned long lineno; /* Line number to restore */
4374 char *name; /* Position of name in opt_path */
4375 };
4377 /* The top of the path stack. */
4378 static struct tree_stack_entry *tree_stack = NULL;
4379 unsigned long tree_lineno = 0;
4381 static void
4382 pop_tree_stack_entry(void)
4383 {
4384 struct tree_stack_entry *entry = tree_stack;
4386 tree_lineno = entry->lineno;
4387 entry->name[0] = 0;
4388 tree_stack = entry->prev;
4389 free(entry);
4390 }
4392 static void
4393 push_tree_stack_entry(const char *name, unsigned long lineno)
4394 {
4395 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4396 size_t pathlen = strlen(opt_path);
4398 if (!entry)
4399 return;
4401 entry->prev = tree_stack;
4402 entry->name = opt_path + pathlen;
4403 tree_stack = entry;
4405 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4406 pop_tree_stack_entry();
4407 return;
4408 }
4410 /* Move the current line to the first tree entry. */
4411 tree_lineno = 1;
4412 entry->lineno = lineno;
4413 }
4415 /* Parse output from git-ls-tree(1):
4416 *
4417 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4418 */
4420 #define SIZEOF_TREE_ATTR \
4421 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4423 #define SIZEOF_TREE_MODE \
4424 STRING_SIZE("100644 ")
4426 #define TREE_ID_OFFSET \
4427 STRING_SIZE("100644 blob ")
4429 struct tree_entry {
4430 char id[SIZEOF_REV];
4431 mode_t mode;
4432 struct time time; /* Date from the author ident. */
4433 const char *author; /* Author of the commit. */
4434 char name[1];
4435 };
4437 static const char *
4438 tree_path(const struct line *line)
4439 {
4440 return ((struct tree_entry *) line->data)->name;
4441 }
4443 static int
4444 tree_compare_entry(const struct line *line1, const struct line *line2)
4445 {
4446 if (line1->type != line2->type)
4447 return line1->type == LINE_TREE_DIR ? -1 : 1;
4448 return strcmp(tree_path(line1), tree_path(line2));
4449 }
4451 static const enum sort_field tree_sort_fields[] = {
4452 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4453 };
4454 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4456 static int
4457 tree_compare(const void *l1, const void *l2)
4458 {
4459 const struct line *line1 = (const struct line *) l1;
4460 const struct line *line2 = (const struct line *) l2;
4461 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4462 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4464 if (line1->type == LINE_TREE_HEAD)
4465 return -1;
4466 if (line2->type == LINE_TREE_HEAD)
4467 return 1;
4469 switch (get_sort_field(tree_sort_state)) {
4470 case ORDERBY_DATE:
4471 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4473 case ORDERBY_AUTHOR:
4474 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4476 case ORDERBY_NAME:
4477 default:
4478 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4479 }
4480 }
4483 static struct line *
4484 tree_entry(struct view *view, enum line_type type, const char *path,
4485 const char *mode, const char *id)
4486 {
4487 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4488 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4490 if (!entry || !line) {
4491 free(entry);
4492 return NULL;
4493 }
4495 strncpy(entry->name, path, strlen(path));
4496 if (mode)
4497 entry->mode = strtoul(mode, NULL, 8);
4498 if (id)
4499 string_copy_rev(entry->id, id);
4501 return line;
4502 }
4504 static bool
4505 tree_read_date(struct view *view, char *text, bool *read_date)
4506 {
4507 static const char *author_name;
4508 static struct time author_time;
4510 if (!text && *read_date) {
4511 *read_date = FALSE;
4512 return TRUE;
4514 } else if (!text) {
4515 char *path = *opt_path ? opt_path : ".";
4516 /* Find next entry to process */
4517 const char *log_file[] = {
4518 "git", "log", "--no-color", "--pretty=raw",
4519 "--cc", "--raw", view->id, "--", path, NULL
4520 };
4522 if (!view->lines) {
4523 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4524 report("Tree is empty");
4525 return TRUE;
4526 }
4528 if (!start_update(view, log_file, opt_cdup)) {
4529 report("Failed to load tree data");
4530 return TRUE;
4531 }
4533 *read_date = TRUE;
4534 return FALSE;
4536 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4537 parse_author_line(text + STRING_SIZE("author "),
4538 &author_name, &author_time);
4540 } else if (*text == ':') {
4541 char *pos;
4542 size_t annotated = 1;
4543 size_t i;
4545 pos = strchr(text, '\t');
4546 if (!pos)
4547 return TRUE;
4548 text = pos + 1;
4549 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4550 text += strlen(opt_path);
4551 pos = strchr(text, '/');
4552 if (pos)
4553 *pos = 0;
4555 for (i = 1; i < view->lines; i++) {
4556 struct line *line = &view->line[i];
4557 struct tree_entry *entry = line->data;
4559 annotated += !!entry->author;
4560 if (entry->author || strcmp(entry->name, text))
4561 continue;
4563 entry->author = author_name;
4564 entry->time = author_time;
4565 line->dirty = 1;
4566 break;
4567 }
4569 if (annotated == view->lines)
4570 io_kill(view->pipe);
4571 }
4572 return TRUE;
4573 }
4575 static bool
4576 tree_read(struct view *view, char *text)
4577 {
4578 static bool read_date = FALSE;
4579 struct tree_entry *data;
4580 struct line *entry, *line;
4581 enum line_type type;
4582 size_t textlen = text ? strlen(text) : 0;
4583 char *path = text + SIZEOF_TREE_ATTR;
4585 if (read_date || !text)
4586 return tree_read_date(view, text, &read_date);
4588 if (textlen <= SIZEOF_TREE_ATTR)
4589 return FALSE;
4590 if (view->lines == 0 &&
4591 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4592 return FALSE;
4594 /* Strip the path part ... */
4595 if (*opt_path) {
4596 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4597 size_t striplen = strlen(opt_path);
4599 if (pathlen > striplen)
4600 memmove(path, path + striplen,
4601 pathlen - striplen + 1);
4603 /* Insert "link" to parent directory. */
4604 if (view->lines == 1 &&
4605 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4606 return FALSE;
4607 }
4609 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4610 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4611 if (!entry)
4612 return FALSE;
4613 data = entry->data;
4615 /* Skip "Directory ..." and ".." line. */
4616 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4617 if (tree_compare_entry(line, entry) <= 0)
4618 continue;
4620 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4622 line->data = data;
4623 line->type = type;
4624 for (; line <= entry; line++)
4625 line->dirty = line->cleareol = 1;
4626 return TRUE;
4627 }
4629 if (tree_lineno > view->lineno) {
4630 view->lineno = tree_lineno;
4631 tree_lineno = 0;
4632 }
4634 return TRUE;
4635 }
4637 static bool
4638 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4639 {
4640 struct tree_entry *entry = line->data;
4642 if (line->type == LINE_TREE_HEAD) {
4643 if (draw_text(view, line->type, "Directory path /", TRUE))
4644 return TRUE;
4645 } else {
4646 if (draw_mode(view, entry->mode))
4647 return TRUE;
4649 if (opt_author && draw_author(view, entry->author))
4650 return TRUE;
4652 if (opt_date && draw_date(view, &entry->time))
4653 return TRUE;
4654 }
4655 if (draw_text(view, line->type, entry->name, TRUE))
4656 return TRUE;
4657 return TRUE;
4658 }
4660 static void
4661 open_blob_editor(const char *id)
4662 {
4663 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4664 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4665 int fd = mkstemp(file);
4667 if (fd == -1)
4668 report("Failed to create temporary file");
4669 else if (!io_run_append(blob_argv, fd))
4670 report("Failed to save blob data to file");
4671 else
4672 open_editor(file);
4673 if (fd != -1)
4674 unlink(file);
4675 }
4677 static enum request
4678 tree_request(struct view *view, enum request request, struct line *line)
4679 {
4680 enum open_flags flags;
4681 struct tree_entry *entry = line->data;
4683 switch (request) {
4684 case REQ_VIEW_BLAME:
4685 if (line->type != LINE_TREE_FILE) {
4686 report("Blame only supported for files");
4687 return REQ_NONE;
4688 }
4690 string_copy(opt_ref, view->vid);
4691 return request;
4693 case REQ_EDIT:
4694 if (line->type != LINE_TREE_FILE) {
4695 report("Edit only supported for files");
4696 } else if (!is_head_commit(view->vid)) {
4697 open_blob_editor(entry->id);
4698 } else {
4699 open_editor(opt_file);
4700 }
4701 return REQ_NONE;
4703 case REQ_TOGGLE_SORT_FIELD:
4704 case REQ_TOGGLE_SORT_ORDER:
4705 sort_view(view, request, &tree_sort_state, tree_compare);
4706 return REQ_NONE;
4708 case REQ_PARENT:
4709 if (!*opt_path) {
4710 /* quit view if at top of tree */
4711 return REQ_VIEW_CLOSE;
4712 }
4713 /* fake 'cd ..' */
4714 line = &view->line[1];
4715 break;
4717 case REQ_ENTER:
4718 break;
4720 default:
4721 return request;
4722 }
4724 /* Cleanup the stack if the tree view is at a different tree. */
4725 while (!*opt_path && tree_stack)
4726 pop_tree_stack_entry();
4728 switch (line->type) {
4729 case LINE_TREE_DIR:
4730 /* Depending on whether it is a subdirectory or parent link
4731 * mangle the path buffer. */
4732 if (line == &view->line[1] && *opt_path) {
4733 pop_tree_stack_entry();
4735 } else {
4736 const char *basename = tree_path(line);
4738 push_tree_stack_entry(basename, view->lineno);
4739 }
4741 /* Trees and subtrees share the same ID, so they are not not
4742 * unique like blobs. */
4743 flags = OPEN_RELOAD;
4744 request = REQ_VIEW_TREE;
4745 break;
4747 case LINE_TREE_FILE:
4748 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4749 request = REQ_VIEW_BLOB;
4750 break;
4752 default:
4753 return REQ_NONE;
4754 }
4756 open_view(view, request, flags);
4757 if (request == REQ_VIEW_TREE)
4758 view->lineno = tree_lineno;
4760 return REQ_NONE;
4761 }
4763 static bool
4764 tree_grep(struct view *view, struct line *line)
4765 {
4766 struct tree_entry *entry = line->data;
4767 const char *text[] = {
4768 entry->name,
4769 opt_author ? entry->author : "",
4770 mkdate(&entry->time, opt_date),
4771 NULL
4772 };
4774 return grep_text(view, text);
4775 }
4777 static void
4778 tree_select(struct view *view, struct line *line)
4779 {
4780 struct tree_entry *entry = line->data;
4782 if (line->type == LINE_TREE_FILE) {
4783 string_copy_rev(ref_blob, entry->id);
4784 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4786 } else if (line->type != LINE_TREE_DIR) {
4787 return;
4788 }
4790 string_copy_rev(view->ref, entry->id);
4791 }
4793 static bool
4794 tree_prepare(struct view *view)
4795 {
4796 if (view->lines == 0 && opt_prefix[0]) {
4797 char *pos = opt_prefix;
4799 while (pos && *pos) {
4800 char *end = strchr(pos, '/');
4802 if (end)
4803 *end = 0;
4804 push_tree_stack_entry(pos, 0);
4805 pos = end;
4806 if (end) {
4807 *end = '/';
4808 pos++;
4809 }
4810 }
4812 } else if (strcmp(view->vid, view->id)) {
4813 opt_path[0] = 0;
4814 }
4816 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4817 }
4819 static const char *tree_argv[SIZEOF_ARG] = {
4820 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4821 };
4823 static struct view_ops tree_ops = {
4824 "file",
4825 tree_argv,
4826 NULL,
4827 tree_read,
4828 tree_draw,
4829 tree_request,
4830 tree_grep,
4831 tree_select,
4832 tree_prepare,
4833 };
4835 static bool
4836 blob_read(struct view *view, char *line)
4837 {
4838 if (!line)
4839 return TRUE;
4840 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4841 }
4843 static enum request
4844 blob_request(struct view *view, enum request request, struct line *line)
4845 {
4846 switch (request) {
4847 case REQ_EDIT:
4848 open_blob_editor(view->vid);
4849 return REQ_NONE;
4850 default:
4851 return pager_request(view, request, line);
4852 }
4853 }
4855 static const char *blob_argv[SIZEOF_ARG] = {
4856 "git", "cat-file", "blob", "%(blob)", NULL
4857 };
4859 static struct view_ops blob_ops = {
4860 "line",
4861 blob_argv,
4862 NULL,
4863 blob_read,
4864 pager_draw,
4865 blob_request,
4866 pager_grep,
4867 pager_select,
4868 };
4870 /*
4871 * Blame backend
4872 *
4873 * Loading the blame view is a two phase job:
4874 *
4875 * 1. File content is read either using opt_file from the
4876 * filesystem or using git-cat-file.
4877 * 2. Then blame information is incrementally added by
4878 * reading output from git-blame.
4879 */
4881 struct blame_commit {
4882 char id[SIZEOF_REV]; /* SHA1 ID. */
4883 char title[128]; /* First line of the commit message. */
4884 const char *author; /* Author of the commit. */
4885 struct time time; /* Date from the author ident. */
4886 char filename[128]; /* Name of file. */
4887 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4888 char parent_filename[128]; /* Parent/previous name of file. */
4889 };
4891 struct blame {
4892 struct blame_commit *commit;
4893 unsigned long lineno;
4894 char text[1];
4895 };
4897 static bool
4898 blame_open(struct view *view)
4899 {
4900 char path[SIZEOF_STR];
4901 size_t i;
4903 if (!view->prev && *opt_prefix) {
4904 string_copy(path, opt_file);
4905 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4906 return FALSE;
4907 }
4909 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4910 const char *blame_cat_file_argv[] = {
4911 "git", "cat-file", "blob", path, NULL
4912 };
4914 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4915 !start_update(view, blame_cat_file_argv, opt_cdup))
4916 return FALSE;
4917 }
4919 /* First pass: remove multiple references to the same commit. */
4920 for (i = 0; i < view->lines; i++) {
4921 struct blame *blame = view->line[i].data;
4923 if (blame->commit && blame->commit->id[0])
4924 blame->commit->id[0] = 0;
4925 else
4926 blame->commit = NULL;
4927 }
4929 /* Second pass: free existing references. */
4930 for (i = 0; i < view->lines; i++) {
4931 struct blame *blame = view->line[i].data;
4933 if (blame->commit)
4934 free(blame->commit);
4935 }
4937 setup_update(view, opt_file);
4938 string_format(view->ref, "%s ...", opt_file);
4940 return TRUE;
4941 }
4943 static struct blame_commit *
4944 get_blame_commit(struct view *view, const char *id)
4945 {
4946 size_t i;
4948 for (i = 0; i < view->lines; i++) {
4949 struct blame *blame = view->line[i].data;
4951 if (!blame->commit)
4952 continue;
4954 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4955 return blame->commit;
4956 }
4958 {
4959 struct blame_commit *commit = calloc(1, sizeof(*commit));
4961 if (commit)
4962 string_ncopy(commit->id, id, SIZEOF_REV);
4963 return commit;
4964 }
4965 }
4967 static bool
4968 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4969 {
4970 const char *pos = *posref;
4972 *posref = NULL;
4973 pos = strchr(pos + 1, ' ');
4974 if (!pos || !isdigit(pos[1]))
4975 return FALSE;
4976 *number = atoi(pos + 1);
4977 if (*number < min || *number > max)
4978 return FALSE;
4980 *posref = pos;
4981 return TRUE;
4982 }
4984 static struct blame_commit *
4985 parse_blame_commit(struct view *view, const char *text, int *blamed)
4986 {
4987 struct blame_commit *commit;
4988 struct blame *blame;
4989 const char *pos = text + SIZEOF_REV - 2;
4990 size_t orig_lineno = 0;
4991 size_t lineno;
4992 size_t group;
4994 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4995 return NULL;
4997 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4998 !parse_number(&pos, &lineno, 1, view->lines) ||
4999 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5000 return NULL;
5002 commit = get_blame_commit(view, text);
5003 if (!commit)
5004 return NULL;
5006 *blamed += group;
5007 while (group--) {
5008 struct line *line = &view->line[lineno + group - 1];
5010 blame = line->data;
5011 blame->commit = commit;
5012 blame->lineno = orig_lineno + group - 1;
5013 line->dirty = 1;
5014 }
5016 return commit;
5017 }
5019 static bool
5020 blame_read_file(struct view *view, const char *line, bool *read_file)
5021 {
5022 if (!line) {
5023 const char *blame_argv[] = {
5024 "git", "blame", "--incremental",
5025 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5026 };
5028 if (view->lines == 0 && !view->prev)
5029 die("No blame exist for %s", view->vid);
5031 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5032 report("Failed to load blame data");
5033 return TRUE;
5034 }
5036 *read_file = FALSE;
5037 return FALSE;
5039 } else {
5040 size_t linelen = strlen(line);
5041 struct blame *blame = malloc(sizeof(*blame) + linelen);
5043 if (!blame)
5044 return FALSE;
5046 blame->commit = NULL;
5047 strncpy(blame->text, line, linelen);
5048 blame->text[linelen] = 0;
5049 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5050 }
5051 }
5053 static bool
5054 match_blame_header(const char *name, char **line)
5055 {
5056 size_t namelen = strlen(name);
5057 bool matched = !strncmp(name, *line, namelen);
5059 if (matched)
5060 *line += namelen;
5062 return matched;
5063 }
5065 static bool
5066 blame_read(struct view *view, char *line)
5067 {
5068 static struct blame_commit *commit = NULL;
5069 static int blamed = 0;
5070 static bool read_file = TRUE;
5072 if (read_file)
5073 return blame_read_file(view, line, &read_file);
5075 if (!line) {
5076 /* Reset all! */
5077 commit = NULL;
5078 blamed = 0;
5079 read_file = TRUE;
5080 string_format(view->ref, "%s", view->vid);
5081 if (view_is_displayed(view)) {
5082 update_view_title(view);
5083 redraw_view_from(view, 0);
5084 }
5085 return TRUE;
5086 }
5088 if (!commit) {
5089 commit = parse_blame_commit(view, line, &blamed);
5090 string_format(view->ref, "%s %2d%%", view->vid,
5091 view->lines ? blamed * 100 / view->lines : 0);
5093 } else if (match_blame_header("author ", &line)) {
5094 commit->author = get_author(line);
5096 } else if (match_blame_header("author-time ", &line)) {
5097 parse_timesec(&commit->time, line);
5099 } else if (match_blame_header("author-tz ", &line)) {
5100 parse_timezone(&commit->time, line);
5102 } else if (match_blame_header("summary ", &line)) {
5103 string_ncopy(commit->title, line, strlen(line));
5105 } else if (match_blame_header("previous ", &line)) {
5106 if (strlen(line) <= SIZEOF_REV)
5107 return FALSE;
5108 string_copy_rev(commit->parent_id, line);
5109 line += SIZEOF_REV;
5110 string_ncopy(commit->parent_filename, line, strlen(line));
5112 } else if (match_blame_header("filename ", &line)) {
5113 string_ncopy(commit->filename, line, strlen(line));
5114 commit = NULL;
5115 }
5117 return TRUE;
5118 }
5120 static bool
5121 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5122 {
5123 struct blame *blame = line->data;
5124 struct time *time = NULL;
5125 const char *id = NULL, *author = NULL;
5127 if (blame->commit && *blame->commit->filename) {
5128 id = blame->commit->id;
5129 author = blame->commit->author;
5130 time = &blame->commit->time;
5131 }
5133 if (opt_date && draw_date(view, time))
5134 return TRUE;
5136 if (opt_author && draw_author(view, author))
5137 return TRUE;
5139 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5140 return TRUE;
5142 if (draw_lineno(view, lineno))
5143 return TRUE;
5145 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5146 return TRUE;
5147 }
5149 static bool
5150 check_blame_commit(struct blame *blame, bool check_null_id)
5151 {
5152 if (!blame->commit)
5153 report("Commit data not loaded yet");
5154 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5155 report("No commit exist for the selected line");
5156 else
5157 return TRUE;
5158 return FALSE;
5159 }
5161 static void
5162 setup_blame_parent_line(struct view *view, struct blame *blame)
5163 {
5164 char from[SIZEOF_REF + SIZEOF_STR];
5165 char to[SIZEOF_REF + SIZEOF_STR];
5166 const char *diff_tree_argv[] = {
5167 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5168 "-U0", from, to, "--", NULL
5169 };
5170 struct io io;
5171 int parent_lineno = -1;
5172 int blamed_lineno = -1;
5173 char *line;
5175 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5176 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5177 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5178 return;
5180 while ((line = io_get(&io, '\n', TRUE))) {
5181 if (*line == '@') {
5182 char *pos = strchr(line, '+');
5184 parent_lineno = atoi(line + 4);
5185 if (pos)
5186 blamed_lineno = atoi(pos + 1);
5188 } else if (*line == '+' && parent_lineno != -1) {
5189 if (blame->lineno == blamed_lineno - 1 &&
5190 !strcmp(blame->text, line + 1)) {
5191 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5192 break;
5193 }
5194 blamed_lineno++;
5195 }
5196 }
5198 io_done(&io);
5199 }
5201 static enum request
5202 blame_request(struct view *view, enum request request, struct line *line)
5203 {
5204 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5205 struct blame *blame = line->data;
5207 switch (request) {
5208 case REQ_VIEW_BLAME:
5209 if (check_blame_commit(blame, TRUE)) {
5210 string_copy(opt_ref, blame->commit->id);
5211 string_copy(opt_file, blame->commit->filename);
5212 if (blame->lineno)
5213 view->lineno = blame->lineno;
5214 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5215 }
5216 break;
5218 case REQ_PARENT:
5219 if (!check_blame_commit(blame, TRUE))
5220 break;
5221 if (!*blame->commit->parent_id) {
5222 report("The selected commit has no parents");
5223 } else {
5224 string_copy_rev(opt_ref, blame->commit->parent_id);
5225 string_copy(opt_file, blame->commit->parent_filename);
5226 setup_blame_parent_line(view, blame);
5227 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5228 }
5229 break;
5231 case REQ_ENTER:
5232 if (!check_blame_commit(blame, FALSE))
5233 break;
5235 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5236 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5237 break;
5239 if (!strcmp(blame->commit->id, NULL_ID)) {
5240 struct view *diff = VIEW(REQ_VIEW_DIFF);
5241 const char *diff_index_argv[] = {
5242 "git", "diff-index", "--root", "--patch-with-stat",
5243 "-C", "-M", "HEAD", "--", view->vid, NULL
5244 };
5246 if (!*blame->commit->parent_id) {
5247 diff_index_argv[1] = "diff";
5248 diff_index_argv[2] = "--no-color";
5249 diff_index_argv[6] = "--";
5250 diff_index_argv[7] = "/dev/null";
5251 }
5253 if (!prepare_update(diff, diff_index_argv, NULL)) {
5254 report("Failed to allocate diff command");
5255 break;
5256 }
5257 flags |= OPEN_PREPARED;
5258 }
5260 open_view(view, REQ_VIEW_DIFF, flags);
5261 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5262 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5263 break;
5265 default:
5266 return request;
5267 }
5269 return REQ_NONE;
5270 }
5272 static bool
5273 blame_grep(struct view *view, struct line *line)
5274 {
5275 struct blame *blame = line->data;
5276 struct blame_commit *commit = blame->commit;
5277 const char *text[] = {
5278 blame->text,
5279 commit ? commit->title : "",
5280 commit ? commit->id : "",
5281 commit && opt_author ? commit->author : "",
5282 commit ? mkdate(&commit->time, opt_date) : "",
5283 NULL
5284 };
5286 return grep_text(view, text);
5287 }
5289 static void
5290 blame_select(struct view *view, struct line *line)
5291 {
5292 struct blame *blame = line->data;
5293 struct blame_commit *commit = blame->commit;
5295 if (!commit)
5296 return;
5298 if (!strcmp(commit->id, NULL_ID))
5299 string_ncopy(ref_commit, "HEAD", 4);
5300 else
5301 string_copy_rev(ref_commit, commit->id);
5302 }
5304 static struct view_ops blame_ops = {
5305 "line",
5306 NULL,
5307 blame_open,
5308 blame_read,
5309 blame_draw,
5310 blame_request,
5311 blame_grep,
5312 blame_select,
5313 };
5315 /*
5316 * Branch backend
5317 */
5319 struct branch {
5320 const char *author; /* Author of the last commit. */
5321 struct time time; /* Date of the last activity. */
5322 const struct ref *ref; /* Name and commit ID information. */
5323 };
5325 static const struct ref branch_all;
5327 static const enum sort_field branch_sort_fields[] = {
5328 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5329 };
5330 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5332 static int
5333 branch_compare(const void *l1, const void *l2)
5334 {
5335 const struct branch *branch1 = ((const struct line *) l1)->data;
5336 const struct branch *branch2 = ((const struct line *) l2)->data;
5338 switch (get_sort_field(branch_sort_state)) {
5339 case ORDERBY_DATE:
5340 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5342 case ORDERBY_AUTHOR:
5343 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5345 case ORDERBY_NAME:
5346 default:
5347 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5348 }
5349 }
5351 static bool
5352 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5353 {
5354 struct branch *branch = line->data;
5355 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5357 if (opt_date && draw_date(view, &branch->time))
5358 return TRUE;
5360 if (opt_author && draw_author(view, branch->author))
5361 return TRUE;
5363 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5364 return TRUE;
5365 }
5367 static enum request
5368 branch_request(struct view *view, enum request request, struct line *line)
5369 {
5370 struct branch *branch = line->data;
5372 switch (request) {
5373 case REQ_REFRESH:
5374 load_refs();
5375 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5376 return REQ_NONE;
5378 case REQ_TOGGLE_SORT_FIELD:
5379 case REQ_TOGGLE_SORT_ORDER:
5380 sort_view(view, request, &branch_sort_state, branch_compare);
5381 return REQ_NONE;
5383 case REQ_ENTER:
5384 {
5385 const struct ref *ref = branch->ref;
5386 const char *all_branches_argv[] = {
5387 "git", "log", "--no-color", "--pretty=raw", "--parents",
5388 "--topo-order",
5389 ref == &branch_all ? "--all" : ref->name, NULL
5390 };
5391 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5393 if (!prepare_update(main_view, all_branches_argv, NULL))
5394 report("Failed to load view of all branches");
5395 else
5396 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5397 return REQ_NONE;
5398 }
5399 default:
5400 return request;
5401 }
5402 }
5404 static bool
5405 branch_read(struct view *view, char *line)
5406 {
5407 static char id[SIZEOF_REV];
5408 struct branch *reference;
5409 size_t i;
5411 if (!line)
5412 return TRUE;
5414 switch (get_line_type(line)) {
5415 case LINE_COMMIT:
5416 string_copy_rev(id, line + STRING_SIZE("commit "));
5417 return TRUE;
5419 case LINE_AUTHOR:
5420 for (i = 0, reference = NULL; i < view->lines; i++) {
5421 struct branch *branch = view->line[i].data;
5423 if (strcmp(branch->ref->id, id))
5424 continue;
5426 view->line[i].dirty = TRUE;
5427 if (reference) {
5428 branch->author = reference->author;
5429 branch->time = reference->time;
5430 continue;
5431 }
5433 parse_author_line(line + STRING_SIZE("author "),
5434 &branch->author, &branch->time);
5435 reference = branch;
5436 }
5437 return TRUE;
5439 default:
5440 return TRUE;
5441 }
5443 }
5445 static bool
5446 branch_open_visitor(void *data, const struct ref *ref)
5447 {
5448 struct view *view = data;
5449 struct branch *branch;
5451 if (ref->tag || ref->ltag || ref->remote)
5452 return TRUE;
5454 branch = calloc(1, sizeof(*branch));
5455 if (!branch)
5456 return FALSE;
5458 branch->ref = ref;
5459 return !!add_line_data(view, branch, LINE_DEFAULT);
5460 }
5462 static bool
5463 branch_open(struct view *view)
5464 {
5465 const char *branch_log[] = {
5466 "git", "log", "--no-color", "--pretty=raw",
5467 "--simplify-by-decoration", "--all", NULL
5468 };
5470 if (!start_update(view, branch_log, NULL)) {
5471 report("Failed to load branch data");
5472 return TRUE;
5473 }
5475 setup_update(view, view->id);
5476 branch_open_visitor(view, &branch_all);
5477 foreach_ref(branch_open_visitor, view);
5478 view->p_restore = TRUE;
5480 return TRUE;
5481 }
5483 static bool
5484 branch_grep(struct view *view, struct line *line)
5485 {
5486 struct branch *branch = line->data;
5487 const char *text[] = {
5488 branch->ref->name,
5489 branch->author,
5490 NULL
5491 };
5493 return grep_text(view, text);
5494 }
5496 static void
5497 branch_select(struct view *view, struct line *line)
5498 {
5499 struct branch *branch = line->data;
5501 string_copy_rev(view->ref, branch->ref->id);
5502 string_copy_rev(ref_commit, branch->ref->id);
5503 string_copy_rev(ref_head, branch->ref->id);
5504 string_copy_rev(ref_branch, branch->ref->name);
5505 }
5507 static struct view_ops branch_ops = {
5508 "branch",
5509 NULL,
5510 branch_open,
5511 branch_read,
5512 branch_draw,
5513 branch_request,
5514 branch_grep,
5515 branch_select,
5516 };
5518 /*
5519 * Status backend
5520 */
5522 struct status {
5523 char status;
5524 struct {
5525 mode_t mode;
5526 char rev[SIZEOF_REV];
5527 char name[SIZEOF_STR];
5528 } old;
5529 struct {
5530 mode_t mode;
5531 char rev[SIZEOF_REV];
5532 char name[SIZEOF_STR];
5533 } new;
5534 };
5536 static char status_onbranch[SIZEOF_STR];
5537 static struct status stage_status;
5538 static enum line_type stage_line_type;
5539 static size_t stage_chunks;
5540 static int *stage_chunk;
5542 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5544 /* This should work even for the "On branch" line. */
5545 static inline bool
5546 status_has_none(struct view *view, struct line *line)
5547 {
5548 return line < view->line + view->lines && !line[1].data;
5549 }
5551 /* Get fields from the diff line:
5552 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5553 */
5554 static inline bool
5555 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5556 {
5557 const char *old_mode = buf + 1;
5558 const char *new_mode = buf + 8;
5559 const char *old_rev = buf + 15;
5560 const char *new_rev = buf + 56;
5561 const char *status = buf + 97;
5563 if (bufsize < 98 ||
5564 old_mode[-1] != ':' ||
5565 new_mode[-1] != ' ' ||
5566 old_rev[-1] != ' ' ||
5567 new_rev[-1] != ' ' ||
5568 status[-1] != ' ')
5569 return FALSE;
5571 file->status = *status;
5573 string_copy_rev(file->old.rev, old_rev);
5574 string_copy_rev(file->new.rev, new_rev);
5576 file->old.mode = strtoul(old_mode, NULL, 8);
5577 file->new.mode = strtoul(new_mode, NULL, 8);
5579 file->old.name[0] = file->new.name[0] = 0;
5581 return TRUE;
5582 }
5584 static bool
5585 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5586 {
5587 struct status *unmerged = NULL;
5588 char *buf;
5589 struct io io;
5591 if (!io_run(&io, IO_RD, opt_cdup, argv))
5592 return FALSE;
5594 add_line_data(view, NULL, type);
5596 while ((buf = io_get(&io, 0, TRUE))) {
5597 struct status *file = unmerged;
5599 if (!file) {
5600 file = calloc(1, sizeof(*file));
5601 if (!file || !add_line_data(view, file, type))
5602 goto error_out;
5603 }
5605 /* Parse diff info part. */
5606 if (status) {
5607 file->status = status;
5608 if (status == 'A')
5609 string_copy(file->old.rev, NULL_ID);
5611 } else if (!file->status || file == unmerged) {
5612 if (!status_get_diff(file, buf, strlen(buf)))
5613 goto error_out;
5615 buf = io_get(&io, 0, TRUE);
5616 if (!buf)
5617 break;
5619 /* Collapse all modified entries that follow an
5620 * associated unmerged entry. */
5621 if (unmerged == file) {
5622 unmerged->status = 'U';
5623 unmerged = NULL;
5624 } else if (file->status == 'U') {
5625 unmerged = file;
5626 }
5627 }
5629 /* Grab the old name for rename/copy. */
5630 if (!*file->old.name &&
5631 (file->status == 'R' || file->status == 'C')) {
5632 string_ncopy(file->old.name, buf, strlen(buf));
5634 buf = io_get(&io, 0, TRUE);
5635 if (!buf)
5636 break;
5637 }
5639 /* git-ls-files just delivers a NUL separated list of
5640 * file names similar to the second half of the
5641 * git-diff-* output. */
5642 string_ncopy(file->new.name, buf, strlen(buf));
5643 if (!*file->old.name)
5644 string_copy(file->old.name, file->new.name);
5645 file = NULL;
5646 }
5648 if (io_error(&io)) {
5649 error_out:
5650 io_done(&io);
5651 return FALSE;
5652 }
5654 if (!view->line[view->lines - 1].data)
5655 add_line_data(view, NULL, LINE_STAT_NONE);
5657 io_done(&io);
5658 return TRUE;
5659 }
5661 /* Don't show unmerged entries in the staged section. */
5662 static const char *status_diff_index_argv[] = {
5663 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5664 "--cached", "-M", "HEAD", NULL
5665 };
5667 static const char *status_diff_files_argv[] = {
5668 "git", "diff-files", "-z", NULL
5669 };
5671 static const char *status_list_other_argv[] = {
5672 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5673 };
5675 static const char *status_list_no_head_argv[] = {
5676 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5677 };
5679 static const char *update_index_argv[] = {
5680 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5681 };
5683 /* Restore the previous line number to stay in the context or select a
5684 * line with something that can be updated. */
5685 static void
5686 status_restore(struct view *view)
5687 {
5688 if (view->p_lineno >= view->lines)
5689 view->p_lineno = view->lines - 1;
5690 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5691 view->p_lineno++;
5692 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5693 view->p_lineno--;
5695 /* If the above fails, always skip the "On branch" line. */
5696 if (view->p_lineno < view->lines)
5697 view->lineno = view->p_lineno;
5698 else
5699 view->lineno = 1;
5701 if (view->lineno < view->offset)
5702 view->offset = view->lineno;
5703 else if (view->offset + view->height <= view->lineno)
5704 view->offset = view->lineno - view->height + 1;
5706 view->p_restore = FALSE;
5707 }
5709 static void
5710 status_update_onbranch(void)
5711 {
5712 static const char *paths[][2] = {
5713 { "rebase-apply/rebasing", "Rebasing" },
5714 { "rebase-apply/applying", "Applying mailbox" },
5715 { "rebase-apply/", "Rebasing mailbox" },
5716 { "rebase-merge/interactive", "Interactive rebase" },
5717 { "rebase-merge/", "Rebase merge" },
5718 { "MERGE_HEAD", "Merging" },
5719 { "BISECT_LOG", "Bisecting" },
5720 { "HEAD", "On branch" },
5721 };
5722 char buf[SIZEOF_STR];
5723 struct stat stat;
5724 int i;
5726 if (is_initial_commit()) {
5727 string_copy(status_onbranch, "Initial commit");
5728 return;
5729 }
5731 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5732 char *head = opt_head;
5734 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5735 lstat(buf, &stat) < 0)
5736 continue;
5738 if (!*opt_head) {
5739 struct io io;
5741 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5742 io_read_buf(&io, buf, sizeof(buf))) {
5743 head = buf;
5744 if (!prefixcmp(head, "refs/heads/"))
5745 head += STRING_SIZE("refs/heads/");
5746 }
5747 }
5749 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5750 string_copy(status_onbranch, opt_head);
5751 return;
5752 }
5754 string_copy(status_onbranch, "Not currently on any branch");
5755 }
5757 /* First parse staged info using git-diff-index(1), then parse unstaged
5758 * info using git-diff-files(1), and finally untracked files using
5759 * git-ls-files(1). */
5760 static bool
5761 status_open(struct view *view)
5762 {
5763 reset_view(view);
5765 add_line_data(view, NULL, LINE_STAT_HEAD);
5766 status_update_onbranch();
5768 io_run_bg(update_index_argv);
5770 if (is_initial_commit()) {
5771 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5772 return FALSE;
5773 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5774 return FALSE;
5775 }
5777 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5778 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5779 return FALSE;
5781 /* Restore the exact position or use the specialized restore
5782 * mode? */
5783 if (!view->p_restore)
5784 status_restore(view);
5785 return TRUE;
5786 }
5788 static bool
5789 status_draw(struct view *view, struct line *line, unsigned int lineno)
5790 {
5791 struct status *status = line->data;
5792 enum line_type type;
5793 const char *text;
5795 if (!status) {
5796 switch (line->type) {
5797 case LINE_STAT_STAGED:
5798 type = LINE_STAT_SECTION;
5799 text = "Changes to be committed:";
5800 break;
5802 case LINE_STAT_UNSTAGED:
5803 type = LINE_STAT_SECTION;
5804 text = "Changed but not updated:";
5805 break;
5807 case LINE_STAT_UNTRACKED:
5808 type = LINE_STAT_SECTION;
5809 text = "Untracked files:";
5810 break;
5812 case LINE_STAT_NONE:
5813 type = LINE_DEFAULT;
5814 text = " (no files)";
5815 break;
5817 case LINE_STAT_HEAD:
5818 type = LINE_STAT_HEAD;
5819 text = status_onbranch;
5820 break;
5822 default:
5823 return FALSE;
5824 }
5825 } else {
5826 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5828 buf[0] = status->status;
5829 if (draw_text(view, line->type, buf, TRUE))
5830 return TRUE;
5831 type = LINE_DEFAULT;
5832 text = status->new.name;
5833 }
5835 draw_text(view, type, text, TRUE);
5836 return TRUE;
5837 }
5839 static enum request
5840 status_load_error(struct view *view, struct view *stage, const char *path)
5841 {
5842 if (displayed_views() == 2 || display[current_view] != view)
5843 maximize_view(view);
5844 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5845 return REQ_NONE;
5846 }
5848 static enum request
5849 status_enter(struct view *view, struct line *line)
5850 {
5851 struct status *status = line->data;
5852 const char *oldpath = status ? status->old.name : NULL;
5853 /* Diffs for unmerged entries are empty when passing the new
5854 * path, so leave it empty. */
5855 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5856 const char *info;
5857 enum open_flags split;
5858 struct view *stage = VIEW(REQ_VIEW_STAGE);
5860 if (line->type == LINE_STAT_NONE ||
5861 (!status && line[1].type == LINE_STAT_NONE)) {
5862 report("No file to diff");
5863 return REQ_NONE;
5864 }
5866 switch (line->type) {
5867 case LINE_STAT_STAGED:
5868 if (is_initial_commit()) {
5869 const char *no_head_diff_argv[] = {
5870 "git", "diff", "--no-color", "--patch-with-stat",
5871 "--", "/dev/null", newpath, NULL
5872 };
5874 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5875 return status_load_error(view, stage, newpath);
5876 } else {
5877 const char *index_show_argv[] = {
5878 "git", "diff-index", "--root", "--patch-with-stat",
5879 "-C", "-M", "--cached", "HEAD", "--",
5880 oldpath, newpath, NULL
5881 };
5883 if (!prepare_update(stage, index_show_argv, opt_cdup))
5884 return status_load_error(view, stage, newpath);
5885 }
5887 if (status)
5888 info = "Staged changes to %s";
5889 else
5890 info = "Staged changes";
5891 break;
5893 case LINE_STAT_UNSTAGED:
5894 {
5895 const char *files_show_argv[] = {
5896 "git", "diff-files", "--root", "--patch-with-stat",
5897 "-C", "-M", "--", oldpath, newpath, NULL
5898 };
5900 if (!prepare_update(stage, files_show_argv, opt_cdup))
5901 return status_load_error(view, stage, newpath);
5902 if (status)
5903 info = "Unstaged changes to %s";
5904 else
5905 info = "Unstaged changes";
5906 break;
5907 }
5908 case LINE_STAT_UNTRACKED:
5909 if (!newpath) {
5910 report("No file to show");
5911 return REQ_NONE;
5912 }
5914 if (!suffixcmp(status->new.name, -1, "/")) {
5915 report("Cannot display a directory");
5916 return REQ_NONE;
5917 }
5919 if (!prepare_update_file(stage, newpath))
5920 return status_load_error(view, stage, newpath);
5921 info = "Untracked file %s";
5922 break;
5924 case LINE_STAT_HEAD:
5925 return REQ_NONE;
5927 default:
5928 die("line type %d not handled in switch", line->type);
5929 }
5931 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5932 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5933 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5934 if (status) {
5935 stage_status = *status;
5936 } else {
5937 memset(&stage_status, 0, sizeof(stage_status));
5938 }
5940 stage_line_type = line->type;
5941 stage_chunks = 0;
5942 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5943 }
5945 return REQ_NONE;
5946 }
5948 static bool
5949 status_exists(struct status *status, enum line_type type)
5950 {
5951 struct view *view = VIEW(REQ_VIEW_STATUS);
5952 unsigned long lineno;
5954 for (lineno = 0; lineno < view->lines; lineno++) {
5955 struct line *line = &view->line[lineno];
5956 struct status *pos = line->data;
5958 if (line->type != type)
5959 continue;
5960 if (!pos && (!status || !status->status) && line[1].data) {
5961 select_view_line(view, lineno);
5962 return TRUE;
5963 }
5964 if (pos && !strcmp(status->new.name, pos->new.name)) {
5965 select_view_line(view, lineno);
5966 return TRUE;
5967 }
5968 }
5970 return FALSE;
5971 }
5974 static bool
5975 status_update_prepare(struct io *io, enum line_type type)
5976 {
5977 const char *staged_argv[] = {
5978 "git", "update-index", "-z", "--index-info", NULL
5979 };
5980 const char *others_argv[] = {
5981 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5982 };
5984 switch (type) {
5985 case LINE_STAT_STAGED:
5986 return io_run(io, IO_WR, opt_cdup, staged_argv);
5988 case LINE_STAT_UNSTAGED:
5989 case LINE_STAT_UNTRACKED:
5990 return io_run(io, IO_WR, opt_cdup, others_argv);
5992 default:
5993 die("line type %d not handled in switch", type);
5994 return FALSE;
5995 }
5996 }
5998 static bool
5999 status_update_write(struct io *io, struct status *status, enum line_type type)
6000 {
6001 char buf[SIZEOF_STR];
6002 size_t bufsize = 0;
6004 switch (type) {
6005 case LINE_STAT_STAGED:
6006 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6007 status->old.mode,
6008 status->old.rev,
6009 status->old.name, 0))
6010 return FALSE;
6011 break;
6013 case LINE_STAT_UNSTAGED:
6014 case LINE_STAT_UNTRACKED:
6015 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6016 return FALSE;
6017 break;
6019 default:
6020 die("line type %d not handled in switch", type);
6021 }
6023 return io_write(io, buf, bufsize);
6024 }
6026 static bool
6027 status_update_file(struct status *status, enum line_type type)
6028 {
6029 struct io io;
6030 bool result;
6032 if (!status_update_prepare(&io, type))
6033 return FALSE;
6035 result = status_update_write(&io, status, type);
6036 return io_done(&io) && result;
6037 }
6039 static bool
6040 status_update_files(struct view *view, struct line *line)
6041 {
6042 char buf[sizeof(view->ref)];
6043 struct io io;
6044 bool result = TRUE;
6045 struct line *pos = view->line + view->lines;
6046 int files = 0;
6047 int file, done;
6048 int cursor_y = -1, cursor_x = -1;
6050 if (!status_update_prepare(&io, line->type))
6051 return FALSE;
6053 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6054 files++;
6056 string_copy(buf, view->ref);
6057 getsyx(cursor_y, cursor_x);
6058 for (file = 0, done = 5; result && file < files; line++, file++) {
6059 int almost_done = file * 100 / files;
6061 if (almost_done > done) {
6062 done = almost_done;
6063 string_format(view->ref, "updating file %u of %u (%d%% done)",
6064 file, files, done);
6065 update_view_title(view);
6066 setsyx(cursor_y, cursor_x);
6067 doupdate();
6068 }
6069 result = status_update_write(&io, line->data, line->type);
6070 }
6071 string_copy(view->ref, buf);
6073 return io_done(&io) && result;
6074 }
6076 static bool
6077 status_update(struct view *view)
6078 {
6079 struct line *line = &view->line[view->lineno];
6081 assert(view->lines);
6083 if (!line->data) {
6084 /* This should work even for the "On branch" line. */
6085 if (line < view->line + view->lines && !line[1].data) {
6086 report("Nothing to update");
6087 return FALSE;
6088 }
6090 if (!status_update_files(view, line + 1)) {
6091 report("Failed to update file status");
6092 return FALSE;
6093 }
6095 } else if (!status_update_file(line->data, line->type)) {
6096 report("Failed to update file status");
6097 return FALSE;
6098 }
6100 return TRUE;
6101 }
6103 static bool
6104 status_revert(struct status *status, enum line_type type, bool has_none)
6105 {
6106 if (!status || type != LINE_STAT_UNSTAGED) {
6107 if (type == LINE_STAT_STAGED) {
6108 report("Cannot revert changes to staged files");
6109 } else if (type == LINE_STAT_UNTRACKED) {
6110 report("Cannot revert changes to untracked files");
6111 } else if (has_none) {
6112 report("Nothing to revert");
6113 } else {
6114 report("Cannot revert changes to multiple files");
6115 }
6117 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6118 char mode[10] = "100644";
6119 const char *reset_argv[] = {
6120 "git", "update-index", "--cacheinfo", mode,
6121 status->old.rev, status->old.name, NULL
6122 };
6123 const char *checkout_argv[] = {
6124 "git", "checkout", "--", status->old.name, NULL
6125 };
6127 if (status->status == 'U') {
6128 string_format(mode, "%5o", status->old.mode);
6130 if (status->old.mode == 0 && status->new.mode == 0) {
6131 reset_argv[2] = "--force-remove";
6132 reset_argv[3] = status->old.name;
6133 reset_argv[4] = NULL;
6134 }
6136 if (!io_run_fg(reset_argv, opt_cdup))
6137 return FALSE;
6138 if (status->old.mode == 0 && status->new.mode == 0)
6139 return TRUE;
6140 }
6142 return io_run_fg(checkout_argv, opt_cdup);
6143 }
6145 return FALSE;
6146 }
6148 static enum request
6149 status_request(struct view *view, enum request request, struct line *line)
6150 {
6151 struct status *status = line->data;
6153 switch (request) {
6154 case REQ_STATUS_UPDATE:
6155 if (!status_update(view))
6156 return REQ_NONE;
6157 break;
6159 case REQ_STATUS_REVERT:
6160 if (!status_revert(status, line->type, status_has_none(view, line)))
6161 return REQ_NONE;
6162 break;
6164 case REQ_STATUS_MERGE:
6165 if (!status || status->status != 'U') {
6166 report("Merging only possible for files with unmerged status ('U').");
6167 return REQ_NONE;
6168 }
6169 open_mergetool(status->new.name);
6170 break;
6172 case REQ_EDIT:
6173 if (!status)
6174 return request;
6175 if (status->status == 'D') {
6176 report("File has been deleted.");
6177 return REQ_NONE;
6178 }
6180 open_editor(status->new.name);
6181 break;
6183 case REQ_VIEW_BLAME:
6184 if (status)
6185 opt_ref[0] = 0;
6186 return request;
6188 case REQ_ENTER:
6189 /* After returning the status view has been split to
6190 * show the stage view. No further reloading is
6191 * necessary. */
6192 return status_enter(view, line);
6194 case REQ_REFRESH:
6195 /* Simply reload the view. */
6196 break;
6198 default:
6199 return request;
6200 }
6202 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6204 return REQ_NONE;
6205 }
6207 static void
6208 status_select(struct view *view, struct line *line)
6209 {
6210 struct status *status = line->data;
6211 char file[SIZEOF_STR] = "all files";
6212 const char *text;
6213 const char *key;
6215 if (status && !string_format(file, "'%s'", status->new.name))
6216 return;
6218 if (!status && line[1].type == LINE_STAT_NONE)
6219 line++;
6221 switch (line->type) {
6222 case LINE_STAT_STAGED:
6223 text = "Press %s to unstage %s for commit";
6224 break;
6226 case LINE_STAT_UNSTAGED:
6227 text = "Press %s to stage %s for commit";
6228 break;
6230 case LINE_STAT_UNTRACKED:
6231 text = "Press %s to stage %s for addition";
6232 break;
6234 case LINE_STAT_HEAD:
6235 case LINE_STAT_NONE:
6236 text = "Nothing to update";
6237 break;
6239 default:
6240 die("line type %d not handled in switch", line->type);
6241 }
6243 if (status && status->status == 'U') {
6244 text = "Press %s to resolve conflict in %s";
6245 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6247 } else {
6248 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6249 }
6251 string_format(view->ref, text, key, file);
6252 if (status)
6253 string_copy(opt_file, status->new.name);
6254 }
6256 static bool
6257 status_grep(struct view *view, struct line *line)
6258 {
6259 struct status *status = line->data;
6261 if (status) {
6262 const char buf[2] = { status->status, 0 };
6263 const char *text[] = { status->new.name, buf, NULL };
6265 return grep_text(view, text);
6266 }
6268 return FALSE;
6269 }
6271 static struct view_ops status_ops = {
6272 "file",
6273 NULL,
6274 status_open,
6275 NULL,
6276 status_draw,
6277 status_request,
6278 status_grep,
6279 status_select,
6280 };
6283 static bool
6284 stage_diff_write(struct io *io, struct line *line, struct line *end)
6285 {
6286 while (line < end) {
6287 if (!io_write(io, line->data, strlen(line->data)) ||
6288 !io_write(io, "\n", 1))
6289 return FALSE;
6290 line++;
6291 if (line->type == LINE_DIFF_CHUNK ||
6292 line->type == LINE_DIFF_HEADER)
6293 break;
6294 }
6296 return TRUE;
6297 }
6299 static struct line *
6300 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6301 {
6302 for (; view->line < line; line--)
6303 if (line->type == type)
6304 return line;
6306 return NULL;
6307 }
6309 static bool
6310 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6311 {
6312 const char *apply_argv[SIZEOF_ARG] = {
6313 "git", "apply", "--whitespace=nowarn", NULL
6314 };
6315 struct line *diff_hdr;
6316 struct io io;
6317 int argc = 3;
6319 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6320 if (!diff_hdr)
6321 return FALSE;
6323 if (!revert)
6324 apply_argv[argc++] = "--cached";
6325 if (revert || stage_line_type == LINE_STAT_STAGED)
6326 apply_argv[argc++] = "-R";
6327 apply_argv[argc++] = "-";
6328 apply_argv[argc++] = NULL;
6329 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6330 return FALSE;
6332 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6333 !stage_diff_write(&io, chunk, view->line + view->lines))
6334 chunk = NULL;
6336 io_done(&io);
6337 io_run_bg(update_index_argv);
6339 return chunk ? TRUE : FALSE;
6340 }
6342 static bool
6343 stage_update(struct view *view, struct line *line)
6344 {
6345 struct line *chunk = NULL;
6347 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6348 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6350 if (chunk) {
6351 if (!stage_apply_chunk(view, chunk, FALSE)) {
6352 report("Failed to apply chunk");
6353 return FALSE;
6354 }
6356 } else if (!stage_status.status) {
6357 view = VIEW(REQ_VIEW_STATUS);
6359 for (line = view->line; line < view->line + view->lines; line++)
6360 if (line->type == stage_line_type)
6361 break;
6363 if (!status_update_files(view, line + 1)) {
6364 report("Failed to update files");
6365 return FALSE;
6366 }
6368 } else if (!status_update_file(&stage_status, stage_line_type)) {
6369 report("Failed to update file");
6370 return FALSE;
6371 }
6373 return TRUE;
6374 }
6376 static bool
6377 stage_revert(struct view *view, struct line *line)
6378 {
6379 struct line *chunk = NULL;
6381 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6382 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6384 if (chunk) {
6385 if (!prompt_yesno("Are you sure you want to revert changes?"))
6386 return FALSE;
6388 if (!stage_apply_chunk(view, chunk, TRUE)) {
6389 report("Failed to revert chunk");
6390 return FALSE;
6391 }
6392 return TRUE;
6394 } else {
6395 return status_revert(stage_status.status ? &stage_status : NULL,
6396 stage_line_type, FALSE);
6397 }
6398 }
6401 static void
6402 stage_next(struct view *view, struct line *line)
6403 {
6404 int i;
6406 if (!stage_chunks) {
6407 for (line = view->line; line < view->line + view->lines; line++) {
6408 if (line->type != LINE_DIFF_CHUNK)
6409 continue;
6411 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6412 report("Allocation failure");
6413 return;
6414 }
6416 stage_chunk[stage_chunks++] = line - view->line;
6417 }
6418 }
6420 for (i = 0; i < stage_chunks; i++) {
6421 if (stage_chunk[i] > view->lineno) {
6422 do_scroll_view(view, stage_chunk[i] - view->lineno);
6423 report("Chunk %d of %d", i + 1, stage_chunks);
6424 return;
6425 }
6426 }
6428 report("No next chunk found");
6429 }
6431 static enum request
6432 stage_request(struct view *view, enum request request, struct line *line)
6433 {
6434 switch (request) {
6435 case REQ_STATUS_UPDATE:
6436 if (!stage_update(view, line))
6437 return REQ_NONE;
6438 break;
6440 case REQ_STATUS_REVERT:
6441 if (!stage_revert(view, line))
6442 return REQ_NONE;
6443 break;
6445 case REQ_STAGE_NEXT:
6446 if (stage_line_type == LINE_STAT_UNTRACKED) {
6447 report("File is untracked; press %s to add",
6448 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6449 return REQ_NONE;
6450 }
6451 stage_next(view, line);
6452 return REQ_NONE;
6454 case REQ_EDIT:
6455 if (!stage_status.new.name[0])
6456 return request;
6457 if (stage_status.status == 'D') {
6458 report("File has been deleted.");
6459 return REQ_NONE;
6460 }
6462 open_editor(stage_status.new.name);
6463 break;
6465 case REQ_REFRESH:
6466 /* Reload everything ... */
6467 break;
6469 case REQ_VIEW_BLAME:
6470 if (stage_status.new.name[0]) {
6471 string_copy(opt_file, stage_status.new.name);
6472 opt_ref[0] = 0;
6473 }
6474 return request;
6476 case REQ_ENTER:
6477 return pager_request(view, request, line);
6479 default:
6480 return request;
6481 }
6483 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6484 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6486 /* Check whether the staged entry still exists, and close the
6487 * stage view if it doesn't. */
6488 if (!status_exists(&stage_status, stage_line_type)) {
6489 status_restore(VIEW(REQ_VIEW_STATUS));
6490 return REQ_VIEW_CLOSE;
6491 }
6493 if (stage_line_type == LINE_STAT_UNTRACKED) {
6494 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6495 report("Cannot display a directory");
6496 return REQ_NONE;
6497 }
6499 if (!prepare_update_file(view, stage_status.new.name)) {
6500 report("Failed to open file: %s", strerror(errno));
6501 return REQ_NONE;
6502 }
6503 }
6504 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6506 return REQ_NONE;
6507 }
6509 static struct view_ops stage_ops = {
6510 "line",
6511 NULL,
6512 NULL,
6513 pager_read,
6514 pager_draw,
6515 stage_request,
6516 pager_grep,
6517 pager_select,
6518 };
6521 /*
6522 * Revision graph
6523 */
6525 struct commit {
6526 char id[SIZEOF_REV]; /* SHA1 ID. */
6527 char title[128]; /* First line of the commit message. */
6528 const char *author; /* Author of the commit. */
6529 struct time time; /* Date from the author ident. */
6530 struct ref_list *refs; /* Repository references. */
6531 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6532 size_t graph_size; /* The width of the graph array. */
6533 bool has_parents; /* Rewritten --parents seen. */
6534 };
6536 /* Size of rev graph with no "padding" columns */
6537 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6539 struct rev_graph {
6540 struct rev_graph *prev, *next, *parents;
6541 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6542 size_t size;
6543 struct commit *commit;
6544 size_t pos;
6545 unsigned int boundary:1;
6546 };
6548 /* Parents of the commit being visualized. */
6549 static struct rev_graph graph_parents[4];
6551 /* The current stack of revisions on the graph. */
6552 static struct rev_graph graph_stacks[4] = {
6553 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6554 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6555 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6556 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6557 };
6559 static inline bool
6560 graph_parent_is_merge(struct rev_graph *graph)
6561 {
6562 return graph->parents->size > 1;
6563 }
6565 static inline void
6566 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6567 {
6568 struct commit *commit = graph->commit;
6570 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6571 commit->graph[commit->graph_size++] = symbol;
6572 }
6574 static void
6575 clear_rev_graph(struct rev_graph *graph)
6576 {
6577 graph->boundary = 0;
6578 graph->size = graph->pos = 0;
6579 graph->commit = NULL;
6580 memset(graph->parents, 0, sizeof(*graph->parents));
6581 }
6583 static void
6584 done_rev_graph(struct rev_graph *graph)
6585 {
6586 if (graph_parent_is_merge(graph) &&
6587 graph->pos < graph->size - 1 &&
6588 graph->next->size == graph->size + graph->parents->size - 1) {
6589 size_t i = graph->pos + graph->parents->size - 1;
6591 graph->commit->graph_size = i * 2;
6592 while (i < graph->next->size - 1) {
6593 append_to_rev_graph(graph, ' ');
6594 append_to_rev_graph(graph, '\\');
6595 i++;
6596 }
6597 }
6599 clear_rev_graph(graph);
6600 }
6602 static void
6603 push_rev_graph(struct rev_graph *graph, const char *parent)
6604 {
6605 int i;
6607 /* "Collapse" duplicate parents lines.
6608 *
6609 * FIXME: This needs to also update update the drawn graph but
6610 * for now it just serves as a method for pruning graph lines. */
6611 for (i = 0; i < graph->size; i++)
6612 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6613 return;
6615 if (graph->size < SIZEOF_REVITEMS) {
6616 string_copy_rev(graph->rev[graph->size++], parent);
6617 }
6618 }
6620 static chtype
6621 get_rev_graph_symbol(struct rev_graph *graph)
6622 {
6623 chtype symbol;
6625 if (graph->boundary)
6626 symbol = REVGRAPH_BOUND;
6627 else if (graph->parents->size == 0)
6628 symbol = REVGRAPH_INIT;
6629 else if (graph_parent_is_merge(graph))
6630 symbol = REVGRAPH_MERGE;
6631 else if (graph->pos >= graph->size)
6632 symbol = REVGRAPH_BRANCH;
6633 else
6634 symbol = REVGRAPH_COMMIT;
6636 return symbol;
6637 }
6639 static void
6640 draw_rev_graph(struct rev_graph *graph)
6641 {
6642 struct rev_filler {
6643 chtype separator, line;
6644 };
6645 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6646 static struct rev_filler fillers[] = {
6647 { ' ', '|' },
6648 { '`', '.' },
6649 { '\'', ' ' },
6650 { '/', ' ' },
6651 };
6652 chtype symbol = get_rev_graph_symbol(graph);
6653 struct rev_filler *filler;
6654 size_t i;
6656 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6657 filler = &fillers[DEFAULT];
6659 for (i = 0; i < graph->pos; i++) {
6660 append_to_rev_graph(graph, filler->line);
6661 if (graph_parent_is_merge(graph->prev) &&
6662 graph->prev->pos == i)
6663 filler = &fillers[RSHARP];
6665 append_to_rev_graph(graph, filler->separator);
6666 }
6668 /* Place the symbol for this revision. */
6669 append_to_rev_graph(graph, symbol);
6671 if (graph->prev->size > graph->size)
6672 filler = &fillers[RDIAG];
6673 else
6674 filler = &fillers[DEFAULT];
6676 i++;
6678 for (; i < graph->size; i++) {
6679 append_to_rev_graph(graph, filler->separator);
6680 append_to_rev_graph(graph, filler->line);
6681 if (graph_parent_is_merge(graph->prev) &&
6682 i < graph->prev->pos + graph->parents->size)
6683 filler = &fillers[RSHARP];
6684 if (graph->prev->size > graph->size)
6685 filler = &fillers[LDIAG];
6686 }
6688 if (graph->prev->size > graph->size) {
6689 append_to_rev_graph(graph, filler->separator);
6690 if (filler->line != ' ')
6691 append_to_rev_graph(graph, filler->line);
6692 }
6693 }
6695 /* Prepare the next rev graph */
6696 static void
6697 prepare_rev_graph(struct rev_graph *graph)
6698 {
6699 size_t i;
6701 /* First, traverse all lines of revisions up to the active one. */
6702 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6703 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6704 break;
6706 push_rev_graph(graph->next, graph->rev[graph->pos]);
6707 }
6709 /* Interleave the new revision parent(s). */
6710 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6711 push_rev_graph(graph->next, graph->parents->rev[i]);
6713 /* Lastly, put any remaining revisions. */
6714 for (i = graph->pos + 1; i < graph->size; i++)
6715 push_rev_graph(graph->next, graph->rev[i]);
6716 }
6718 static void
6719 update_rev_graph(struct view *view, struct rev_graph *graph)
6720 {
6721 /* If this is the finalizing update ... */
6722 if (graph->commit)
6723 prepare_rev_graph(graph);
6725 /* Graph visualization needs a one rev look-ahead,
6726 * so the first update doesn't visualize anything. */
6727 if (!graph->prev->commit)
6728 return;
6730 if (view->lines > 2)
6731 view->line[view->lines - 3].dirty = 1;
6732 if (view->lines > 1)
6733 view->line[view->lines - 2].dirty = 1;
6734 draw_rev_graph(graph->prev);
6735 done_rev_graph(graph->prev->prev);
6736 }
6739 /*
6740 * Main view backend
6741 */
6743 static const char *main_argv[SIZEOF_ARG] = {
6744 "git", "log", "--no-color", "--pretty=raw", "--parents",
6745 "--topo-order", "%(diffargs)", "%(revargs)",
6746 "--", "%(fileargs)", NULL
6747 };
6749 static bool
6750 main_draw(struct view *view, struct line *line, unsigned int lineno)
6751 {
6752 struct commit *commit = line->data;
6754 if (!commit->author)
6755 return FALSE;
6757 if (opt_date && draw_date(view, &commit->time))
6758 return TRUE;
6760 if (opt_author && draw_author(view, commit->author))
6761 return TRUE;
6763 if (opt_rev_graph && commit->graph_size &&
6764 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6765 return TRUE;
6767 if (opt_show_refs && commit->refs) {
6768 size_t i;
6770 for (i = 0; i < commit->refs->size; i++) {
6771 struct ref *ref = commit->refs->refs[i];
6772 enum line_type type;
6774 if (ref->head)
6775 type = LINE_MAIN_HEAD;
6776 else if (ref->ltag)
6777 type = LINE_MAIN_LOCAL_TAG;
6778 else if (ref->tag)
6779 type = LINE_MAIN_TAG;
6780 else if (ref->tracked)
6781 type = LINE_MAIN_TRACKED;
6782 else if (ref->remote)
6783 type = LINE_MAIN_REMOTE;
6784 else
6785 type = LINE_MAIN_REF;
6787 if (draw_text(view, type, "[", TRUE) ||
6788 draw_text(view, type, ref->name, TRUE) ||
6789 draw_text(view, type, "]", TRUE))
6790 return TRUE;
6792 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6793 return TRUE;
6794 }
6795 }
6797 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6798 return TRUE;
6799 }
6801 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6802 static bool
6803 main_read(struct view *view, char *line)
6804 {
6805 static struct rev_graph *graph = graph_stacks;
6806 enum line_type type;
6807 struct commit *commit;
6809 if (!line) {
6810 int i;
6812 if (!view->lines && !view->prev)
6813 die("No revisions match the given arguments.");
6814 if (view->lines > 0) {
6815 commit = view->line[view->lines - 1].data;
6816 view->line[view->lines - 1].dirty = 1;
6817 if (!commit->author) {
6818 view->lines--;
6819 free(commit);
6820 graph->commit = NULL;
6821 }
6822 }
6823 update_rev_graph(view, graph);
6825 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6826 clear_rev_graph(&graph_stacks[i]);
6827 return TRUE;
6828 }
6830 type = get_line_type(line);
6831 if (type == LINE_COMMIT) {
6832 commit = calloc(1, sizeof(struct commit));
6833 if (!commit)
6834 return FALSE;
6836 line += STRING_SIZE("commit ");
6837 if (*line == '-') {
6838 graph->boundary = 1;
6839 line++;
6840 }
6842 string_copy_rev(commit->id, line);
6843 commit->refs = get_ref_list(commit->id);
6844 graph->commit = commit;
6845 add_line_data(view, commit, LINE_MAIN_COMMIT);
6847 while ((line = strchr(line, ' '))) {
6848 line++;
6849 push_rev_graph(graph->parents, line);
6850 commit->has_parents = TRUE;
6851 }
6852 return TRUE;
6853 }
6855 if (!view->lines)
6856 return TRUE;
6857 commit = view->line[view->lines - 1].data;
6859 switch (type) {
6860 case LINE_PARENT:
6861 if (commit->has_parents)
6862 break;
6863 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6864 break;
6866 case LINE_AUTHOR:
6867 parse_author_line(line + STRING_SIZE("author "),
6868 &commit->author, &commit->time);
6869 update_rev_graph(view, graph);
6870 graph = graph->next;
6871 break;
6873 default:
6874 /* Fill in the commit title if it has not already been set. */
6875 if (commit->title[0])
6876 break;
6878 /* Require titles to start with a non-space character at the
6879 * offset used by git log. */
6880 if (strncmp(line, " ", 4))
6881 break;
6882 line += 4;
6883 /* Well, if the title starts with a whitespace character,
6884 * try to be forgiving. Otherwise we end up with no title. */
6885 while (isspace(*line))
6886 line++;
6887 if (*line == '\0')
6888 break;
6889 /* FIXME: More graceful handling of titles; append "..." to
6890 * shortened titles, etc. */
6892 string_expand(commit->title, sizeof(commit->title), line, 1);
6893 view->line[view->lines - 1].dirty = 1;
6894 }
6896 return TRUE;
6897 }
6899 static enum request
6900 main_request(struct view *view, enum request request, struct line *line)
6901 {
6902 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6904 switch (request) {
6905 case REQ_ENTER:
6906 open_view(view, REQ_VIEW_DIFF, flags);
6907 break;
6908 case REQ_REFRESH:
6909 load_refs();
6910 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6911 break;
6912 default:
6913 return request;
6914 }
6916 return REQ_NONE;
6917 }
6919 static bool
6920 grep_refs(struct ref_list *list, regex_t *regex)
6921 {
6922 regmatch_t pmatch;
6923 size_t i;
6925 if (!opt_show_refs || !list)
6926 return FALSE;
6928 for (i = 0; i < list->size; i++) {
6929 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6930 return TRUE;
6931 }
6933 return FALSE;
6934 }
6936 static bool
6937 main_grep(struct view *view, struct line *line)
6938 {
6939 struct commit *commit = line->data;
6940 const char *text[] = {
6941 commit->title,
6942 opt_author ? commit->author : "",
6943 mkdate(&commit->time, opt_date),
6944 NULL
6945 };
6947 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6948 }
6950 static void
6951 main_select(struct view *view, struct line *line)
6952 {
6953 struct commit *commit = line->data;
6955 string_copy_rev(view->ref, commit->id);
6956 string_copy_rev(ref_commit, view->ref);
6957 }
6959 static struct view_ops main_ops = {
6960 "commit",
6961 main_argv,
6962 NULL,
6963 main_read,
6964 main_draw,
6965 main_request,
6966 main_grep,
6967 main_select,
6968 };
6971 /*
6972 * Status management
6973 */
6975 /* Whether or not the curses interface has been initialized. */
6976 static bool cursed = FALSE;
6978 /* Terminal hacks and workarounds. */
6979 static bool use_scroll_redrawwin;
6980 static bool use_scroll_status_wclear;
6982 /* The status window is used for polling keystrokes. */
6983 static WINDOW *status_win;
6985 /* Reading from the prompt? */
6986 static bool input_mode = FALSE;
6988 static bool status_empty = FALSE;
6990 /* Update status and title window. */
6991 static void
6992 report(const char *msg, ...)
6993 {
6994 struct view *view = display[current_view];
6996 if (input_mode)
6997 return;
6999 if (!view) {
7000 char buf[SIZEOF_STR];
7001 va_list args;
7003 va_start(args, msg);
7004 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7005 buf[sizeof(buf) - 1] = 0;
7006 buf[sizeof(buf) - 2] = '.';
7007 buf[sizeof(buf) - 3] = '.';
7008 buf[sizeof(buf) - 4] = '.';
7009 }
7010 va_end(args);
7011 die("%s", buf);
7012 }
7014 if (!status_empty || *msg) {
7015 va_list args;
7017 va_start(args, msg);
7019 wmove(status_win, 0, 0);
7020 if (view->has_scrolled && use_scroll_status_wclear)
7021 wclear(status_win);
7022 if (*msg) {
7023 vwprintw(status_win, msg, args);
7024 status_empty = FALSE;
7025 } else {
7026 status_empty = TRUE;
7027 }
7028 wclrtoeol(status_win);
7029 wnoutrefresh(status_win);
7031 va_end(args);
7032 }
7034 update_view_title(view);
7035 }
7037 static void
7038 init_display(void)
7039 {
7040 const char *term;
7041 int x, y;
7043 /* Initialize the curses library */
7044 if (isatty(STDIN_FILENO)) {
7045 cursed = !!initscr();
7046 opt_tty = stdin;
7047 } else {
7048 /* Leave stdin and stdout alone when acting as a pager. */
7049 opt_tty = fopen("/dev/tty", "r+");
7050 if (!opt_tty)
7051 die("Failed to open /dev/tty");
7052 cursed = !!newterm(NULL, opt_tty, opt_tty);
7053 }
7055 if (!cursed)
7056 die("Failed to initialize curses");
7058 nonl(); /* Disable conversion and detect newlines from input. */
7059 cbreak(); /* Take input chars one at a time, no wait for \n */
7060 noecho(); /* Don't echo input */
7061 leaveok(stdscr, FALSE);
7063 if (has_colors())
7064 init_colors();
7066 getmaxyx(stdscr, y, x);
7067 status_win = newwin(1, 0, y - 1, 0);
7068 if (!status_win)
7069 die("Failed to create status window");
7071 /* Enable keyboard mapping */
7072 keypad(status_win, TRUE);
7073 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7075 TABSIZE = opt_tab_size;
7077 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7078 if (term && !strcmp(term, "gnome-terminal")) {
7079 /* In the gnome-terminal-emulator, the message from
7080 * scrolling up one line when impossible followed by
7081 * scrolling down one line causes corruption of the
7082 * status line. This is fixed by calling wclear. */
7083 use_scroll_status_wclear = TRUE;
7084 use_scroll_redrawwin = FALSE;
7086 } else if (term && !strcmp(term, "xrvt-xpm")) {
7087 /* No problems with full optimizations in xrvt-(unicode)
7088 * and aterm. */
7089 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7091 } else {
7092 /* When scrolling in (u)xterm the last line in the
7093 * scrolling direction will update slowly. */
7094 use_scroll_redrawwin = TRUE;
7095 use_scroll_status_wclear = FALSE;
7096 }
7097 }
7099 static int
7100 get_input(int prompt_position)
7101 {
7102 struct view *view;
7103 int i, key, cursor_y, cursor_x;
7105 if (prompt_position)
7106 input_mode = TRUE;
7108 while (TRUE) {
7109 bool loading = FALSE;
7111 foreach_view (view, i) {
7112 update_view(view);
7113 if (view_is_displayed(view) && view->has_scrolled &&
7114 use_scroll_redrawwin)
7115 redrawwin(view->win);
7116 view->has_scrolled = FALSE;
7117 if (view->pipe)
7118 loading = TRUE;
7119 }
7121 /* Update the cursor position. */
7122 if (prompt_position) {
7123 getbegyx(status_win, cursor_y, cursor_x);
7124 cursor_x = prompt_position;
7125 } else {
7126 view = display[current_view];
7127 getbegyx(view->win, cursor_y, cursor_x);
7128 cursor_x = view->width - 1;
7129 cursor_y += view->lineno - view->offset;
7130 }
7131 setsyx(cursor_y, cursor_x);
7133 /* Refresh, accept single keystroke of input */
7134 doupdate();
7135 nodelay(status_win, loading);
7136 key = wgetch(status_win);
7138 /* wgetch() with nodelay() enabled returns ERR when
7139 * there's no input. */
7140 if (key == ERR) {
7142 } else if (key == KEY_RESIZE) {
7143 int height, width;
7145 getmaxyx(stdscr, height, width);
7147 wresize(status_win, 1, width);
7148 mvwin(status_win, height - 1, 0);
7149 wnoutrefresh(status_win);
7150 resize_display();
7151 redraw_display(TRUE);
7153 } else {
7154 input_mode = FALSE;
7155 return key;
7156 }
7157 }
7158 }
7160 static char *
7161 prompt_input(const char *prompt, input_handler handler, void *data)
7162 {
7163 enum input_status status = INPUT_OK;
7164 static char buf[SIZEOF_STR];
7165 size_t pos = 0;
7167 buf[pos] = 0;
7169 while (status == INPUT_OK || status == INPUT_SKIP) {
7170 int key;
7172 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7173 wclrtoeol(status_win);
7175 key = get_input(pos + 1);
7176 switch (key) {
7177 case KEY_RETURN:
7178 case KEY_ENTER:
7179 case '\n':
7180 status = pos ? INPUT_STOP : INPUT_CANCEL;
7181 break;
7183 case KEY_BACKSPACE:
7184 if (pos > 0)
7185 buf[--pos] = 0;
7186 else
7187 status = INPUT_CANCEL;
7188 break;
7190 case KEY_ESC:
7191 status = INPUT_CANCEL;
7192 break;
7194 default:
7195 if (pos >= sizeof(buf)) {
7196 report("Input string too long");
7197 return NULL;
7198 }
7200 status = handler(data, buf, key);
7201 if (status == INPUT_OK)
7202 buf[pos++] = (char) key;
7203 }
7204 }
7206 /* Clear the status window */
7207 status_empty = FALSE;
7208 report("");
7210 if (status == INPUT_CANCEL)
7211 return NULL;
7213 buf[pos++] = 0;
7215 return buf;
7216 }
7218 static enum input_status
7219 prompt_yesno_handler(void *data, char *buf, int c)
7220 {
7221 if (c == 'y' || c == 'Y')
7222 return INPUT_STOP;
7223 if (c == 'n' || c == 'N')
7224 return INPUT_CANCEL;
7225 return INPUT_SKIP;
7226 }
7228 static bool
7229 prompt_yesno(const char *prompt)
7230 {
7231 char prompt2[SIZEOF_STR];
7233 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7234 return FALSE;
7236 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7237 }
7239 static enum input_status
7240 read_prompt_handler(void *data, char *buf, int c)
7241 {
7242 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7243 }
7245 static char *
7246 read_prompt(const char *prompt)
7247 {
7248 return prompt_input(prompt, read_prompt_handler, NULL);
7249 }
7251 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7252 {
7253 enum input_status status = INPUT_OK;
7254 int size = 0;
7256 while (items[size].text)
7257 size++;
7259 while (status == INPUT_OK) {
7260 const struct menu_item *item = &items[*selected];
7261 int key;
7262 int i;
7264 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7265 prompt, *selected + 1, size);
7266 if (item->hotkey)
7267 wprintw(status_win, "[%c] ", (char) item->hotkey);
7268 wprintw(status_win, "%s", item->text);
7269 wclrtoeol(status_win);
7271 key = get_input(COLS - 1);
7272 switch (key) {
7273 case KEY_RETURN:
7274 case KEY_ENTER:
7275 case '\n':
7276 status = INPUT_STOP;
7277 break;
7279 case KEY_LEFT:
7280 case KEY_UP:
7281 *selected = *selected - 1;
7282 if (*selected < 0)
7283 *selected = size - 1;
7284 break;
7286 case KEY_RIGHT:
7287 case KEY_DOWN:
7288 *selected = (*selected + 1) % size;
7289 break;
7291 case KEY_ESC:
7292 status = INPUT_CANCEL;
7293 break;
7295 default:
7296 for (i = 0; items[i].text; i++)
7297 if (items[i].hotkey == key) {
7298 *selected = i;
7299 status = INPUT_STOP;
7300 break;
7301 }
7302 }
7303 }
7305 /* Clear the status window */
7306 status_empty = FALSE;
7307 report("");
7309 return status != INPUT_CANCEL;
7310 }
7312 /*
7313 * Repository properties
7314 */
7316 static struct ref **refs = NULL;
7317 static size_t refs_size = 0;
7318 static struct ref *refs_head = NULL;
7320 static struct ref_list **ref_lists = NULL;
7321 static size_t ref_lists_size = 0;
7323 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7324 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7325 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7327 static int
7328 compare_refs(const void *ref1_, const void *ref2_)
7329 {
7330 const struct ref *ref1 = *(const struct ref **)ref1_;
7331 const struct ref *ref2 = *(const struct ref **)ref2_;
7333 if (ref1->tag != ref2->tag)
7334 return ref2->tag - ref1->tag;
7335 if (ref1->ltag != ref2->ltag)
7336 return ref2->ltag - ref2->ltag;
7337 if (ref1->head != ref2->head)
7338 return ref2->head - ref1->head;
7339 if (ref1->tracked != ref2->tracked)
7340 return ref2->tracked - ref1->tracked;
7341 if (ref1->remote != ref2->remote)
7342 return ref2->remote - ref1->remote;
7343 return strcmp(ref1->name, ref2->name);
7344 }
7346 static void
7347 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7348 {
7349 size_t i;
7351 for (i = 0; i < refs_size; i++)
7352 if (!visitor(data, refs[i]))
7353 break;
7354 }
7356 static struct ref *
7357 get_ref_head()
7358 {
7359 return refs_head;
7360 }
7362 static struct ref_list *
7363 get_ref_list(const char *id)
7364 {
7365 struct ref_list *list;
7366 size_t i;
7368 for (i = 0; i < ref_lists_size; i++)
7369 if (!strcmp(id, ref_lists[i]->id))
7370 return ref_lists[i];
7372 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7373 return NULL;
7374 list = calloc(1, sizeof(*list));
7375 if (!list)
7376 return NULL;
7378 for (i = 0; i < refs_size; i++) {
7379 if (!strcmp(id, refs[i]->id) &&
7380 realloc_refs_list(&list->refs, list->size, 1))
7381 list->refs[list->size++] = refs[i];
7382 }
7384 if (!list->refs) {
7385 free(list);
7386 return NULL;
7387 }
7389 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7390 ref_lists[ref_lists_size++] = list;
7391 return list;
7392 }
7394 static int
7395 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7396 {
7397 struct ref *ref = NULL;
7398 bool tag = FALSE;
7399 bool ltag = FALSE;
7400 bool remote = FALSE;
7401 bool tracked = FALSE;
7402 bool head = FALSE;
7403 int from = 0, to = refs_size - 1;
7405 if (!prefixcmp(name, "refs/tags/")) {
7406 if (!suffixcmp(name, namelen, "^{}")) {
7407 namelen -= 3;
7408 name[namelen] = 0;
7409 } else {
7410 ltag = TRUE;
7411 }
7413 tag = TRUE;
7414 namelen -= STRING_SIZE("refs/tags/");
7415 name += STRING_SIZE("refs/tags/");
7417 } else if (!prefixcmp(name, "refs/remotes/")) {
7418 remote = TRUE;
7419 namelen -= STRING_SIZE("refs/remotes/");
7420 name += STRING_SIZE("refs/remotes/");
7421 tracked = !strcmp(opt_remote, name);
7423 } else if (!prefixcmp(name, "refs/heads/")) {
7424 namelen -= STRING_SIZE("refs/heads/");
7425 name += STRING_SIZE("refs/heads/");
7426 if (!strncmp(opt_head, name, namelen))
7427 return OK;
7429 } else if (!strcmp(name, "HEAD")) {
7430 head = TRUE;
7431 if (*opt_head) {
7432 namelen = strlen(opt_head);
7433 name = opt_head;
7434 }
7435 }
7437 /* If we are reloading or it's an annotated tag, replace the
7438 * previous SHA1 with the resolved commit id; relies on the fact
7439 * git-ls-remote lists the commit id of an annotated tag right
7440 * before the commit id it points to. */
7441 while (from <= to) {
7442 size_t pos = (to + from) / 2;
7443 int cmp = strcmp(name, refs[pos]->name);
7445 if (!cmp) {
7446 ref = refs[pos];
7447 break;
7448 }
7450 if (cmp < 0)
7451 to = pos - 1;
7452 else
7453 from = pos + 1;
7454 }
7456 if (!ref) {
7457 if (!realloc_refs(&refs, refs_size, 1))
7458 return ERR;
7459 ref = calloc(1, sizeof(*ref) + namelen);
7460 if (!ref)
7461 return ERR;
7462 memmove(refs + from + 1, refs + from,
7463 (refs_size - from) * sizeof(*refs));
7464 refs[from] = ref;
7465 strncpy(ref->name, name, namelen);
7466 refs_size++;
7467 }
7469 ref->head = head;
7470 ref->tag = tag;
7471 ref->ltag = ltag;
7472 ref->remote = remote;
7473 ref->tracked = tracked;
7474 string_copy_rev(ref->id, id);
7476 if (head)
7477 refs_head = ref;
7478 return OK;
7479 }
7481 static int
7482 load_refs(void)
7483 {
7484 const char *head_argv[] = {
7485 "git", "symbolic-ref", "HEAD", NULL
7486 };
7487 static const char *ls_remote_argv[SIZEOF_ARG] = {
7488 "git", "ls-remote", opt_git_dir, NULL
7489 };
7490 static bool init = FALSE;
7491 size_t i;
7493 if (!init) {
7494 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7495 die("TIG_LS_REMOTE contains too many arguments");
7496 init = TRUE;
7497 }
7499 if (!*opt_git_dir)
7500 return OK;
7502 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7503 !prefixcmp(opt_head, "refs/heads/")) {
7504 char *offset = opt_head + STRING_SIZE("refs/heads/");
7506 memmove(opt_head, offset, strlen(offset) + 1);
7507 }
7509 refs_head = NULL;
7510 for (i = 0; i < refs_size; i++)
7511 refs[i]->id[0] = 0;
7513 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7514 return ERR;
7516 /* Update the ref lists to reflect changes. */
7517 for (i = 0; i < ref_lists_size; i++) {
7518 struct ref_list *list = ref_lists[i];
7519 size_t old, new;
7521 for (old = new = 0; old < list->size; old++)
7522 if (!strcmp(list->id, list->refs[old]->id))
7523 list->refs[new++] = list->refs[old];
7524 list->size = new;
7525 }
7527 return OK;
7528 }
7530 static void
7531 set_remote_branch(const char *name, const char *value, size_t valuelen)
7532 {
7533 if (!strcmp(name, ".remote")) {
7534 string_ncopy(opt_remote, value, valuelen);
7536 } else if (*opt_remote && !strcmp(name, ".merge")) {
7537 size_t from = strlen(opt_remote);
7539 if (!prefixcmp(value, "refs/heads/"))
7540 value += STRING_SIZE("refs/heads/");
7542 if (!string_format_from(opt_remote, &from, "/%s", value))
7543 opt_remote[0] = 0;
7544 }
7545 }
7547 static void
7548 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7549 {
7550 const char *argv[SIZEOF_ARG] = { name, "=" };
7551 int argc = 1 + (cmd == option_set_command);
7552 int error = ERR;
7554 if (!argv_from_string(argv, &argc, value))
7555 config_msg = "Too many option arguments";
7556 else
7557 error = cmd(argc, argv);
7559 if (error == ERR)
7560 warn("Option 'tig.%s': %s", name, config_msg);
7561 }
7563 static bool
7564 set_environment_variable(const char *name, const char *value)
7565 {
7566 size_t len = strlen(name) + 1 + strlen(value) + 1;
7567 char *env = malloc(len);
7569 if (env &&
7570 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7571 putenv(env) == 0)
7572 return TRUE;
7573 free(env);
7574 return FALSE;
7575 }
7577 static void
7578 set_work_tree(const char *value)
7579 {
7580 char cwd[SIZEOF_STR];
7582 if (!getcwd(cwd, sizeof(cwd)))
7583 die("Failed to get cwd path: %s", strerror(errno));
7584 if (chdir(opt_git_dir) < 0)
7585 die("Failed to chdir(%s): %s", strerror(errno));
7586 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7587 die("Failed to get git path: %s", strerror(errno));
7588 if (chdir(cwd) < 0)
7589 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7590 if (chdir(value) < 0)
7591 die("Failed to chdir(%s): %s", value, strerror(errno));
7592 if (!getcwd(cwd, sizeof(cwd)))
7593 die("Failed to get cwd path: %s", strerror(errno));
7594 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7595 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7596 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7597 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7598 opt_is_inside_work_tree = TRUE;
7599 }
7601 static int
7602 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7603 {
7604 if (!strcmp(name, "i18n.commitencoding"))
7605 string_ncopy(opt_encoding, value, valuelen);
7607 else if (!strcmp(name, "core.editor"))
7608 string_ncopy(opt_editor, value, valuelen);
7610 else if (!strcmp(name, "core.worktree"))
7611 set_work_tree(value);
7613 else if (!prefixcmp(name, "tig.color."))
7614 set_repo_config_option(name + 10, value, option_color_command);
7616 else if (!prefixcmp(name, "tig.bind."))
7617 set_repo_config_option(name + 9, value, option_bind_command);
7619 else if (!prefixcmp(name, "tig."))
7620 set_repo_config_option(name + 4, value, option_set_command);
7622 else if (*opt_head && !prefixcmp(name, "branch.") &&
7623 !strncmp(name + 7, opt_head, strlen(opt_head)))
7624 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7626 return OK;
7627 }
7629 static int
7630 load_git_config(void)
7631 {
7632 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7634 return io_run_load(config_list_argv, "=", read_repo_config_option);
7635 }
7637 static int
7638 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7639 {
7640 if (!opt_git_dir[0]) {
7641 string_ncopy(opt_git_dir, name, namelen);
7643 } else if (opt_is_inside_work_tree == -1) {
7644 /* This can be 3 different values depending on the
7645 * version of git being used. If git-rev-parse does not
7646 * understand --is-inside-work-tree it will simply echo
7647 * the option else either "true" or "false" is printed.
7648 * Default to true for the unknown case. */
7649 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7651 } else if (*name == '.') {
7652 string_ncopy(opt_cdup, name, namelen);
7654 } else {
7655 string_ncopy(opt_prefix, name, namelen);
7656 }
7658 return OK;
7659 }
7661 static int
7662 load_repo_info(void)
7663 {
7664 const char *rev_parse_argv[] = {
7665 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7666 "--show-cdup", "--show-prefix", NULL
7667 };
7669 return io_run_load(rev_parse_argv, "=", read_repo_info);
7670 }
7673 /*
7674 * Main
7675 */
7677 static const char usage[] =
7678 "tig " TIG_VERSION " (" __DATE__ ")\n"
7679 "\n"
7680 "Usage: tig [options] [revs] [--] [paths]\n"
7681 " or: tig show [options] [revs] [--] [paths]\n"
7682 " or: tig blame [rev] path\n"
7683 " or: tig status\n"
7684 " or: tig < [git command output]\n"
7685 "\n"
7686 "Options:\n"
7687 " -v, --version Show version and exit\n"
7688 " -h, --help Show help message and exit";
7690 static void __NORETURN
7691 quit(int sig)
7692 {
7693 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7694 if (cursed)
7695 endwin();
7696 exit(0);
7697 }
7699 static void __NORETURN
7700 die(const char *err, ...)
7701 {
7702 va_list args;
7704 endwin();
7706 va_start(args, err);
7707 fputs("tig: ", stderr);
7708 vfprintf(stderr, err, args);
7709 fputs("\n", stderr);
7710 va_end(args);
7712 exit(1);
7713 }
7715 static void
7716 warn(const char *msg, ...)
7717 {
7718 va_list args;
7720 va_start(args, msg);
7721 fputs("tig warning: ", stderr);
7722 vfprintf(stderr, msg, args);
7723 fputs("\n", stderr);
7724 va_end(args);
7725 }
7727 static const char ***filter_args;
7729 static int
7730 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7731 {
7732 return argv_append(filter_args, name) ? OK : ERR;
7733 }
7735 static void
7736 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7737 {
7738 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7739 const char **all_argv = NULL;
7741 filter_args = args;
7742 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7743 !argv_append_array(&all_argv, argv) ||
7744 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7745 die("Failed to split arguments");
7746 argv_free(all_argv);
7747 free(all_argv);
7748 }
7750 static void
7751 filter_options(const char *argv[])
7752 {
7753 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7754 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7755 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7756 }
7758 static enum request
7759 parse_options(int argc, const char *argv[])
7760 {
7761 enum request request = REQ_VIEW_MAIN;
7762 const char *subcommand;
7763 bool seen_dashdash = FALSE;
7764 const char **filter_argv = NULL;
7765 int i;
7767 if (!isatty(STDIN_FILENO)) {
7768 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7769 return REQ_VIEW_PAGER;
7770 }
7772 if (argc <= 1)
7773 return REQ_VIEW_MAIN;
7775 subcommand = argv[1];
7776 if (!strcmp(subcommand, "status")) {
7777 if (argc > 2)
7778 warn("ignoring arguments after `%s'", subcommand);
7779 return REQ_VIEW_STATUS;
7781 } else if (!strcmp(subcommand, "blame")) {
7782 if (argc <= 2 || argc > 4)
7783 die("invalid number of options to blame\n\n%s", usage);
7785 i = 2;
7786 if (argc == 4) {
7787 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7788 i++;
7789 }
7791 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7792 return REQ_VIEW_BLAME;
7794 } else if (!strcmp(subcommand, "show")) {
7795 request = REQ_VIEW_DIFF;
7797 } else {
7798 subcommand = NULL;
7799 }
7801 for (i = 1 + !!subcommand; i < argc; i++) {
7802 const char *opt = argv[i];
7804 if (seen_dashdash) {
7805 argv_append(&opt_file_args, opt);
7806 continue;
7808 } else if (!strcmp(opt, "--")) {
7809 seen_dashdash = TRUE;
7810 continue;
7812 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7813 printf("tig version %s\n", TIG_VERSION);
7814 quit(0);
7816 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7817 printf("%s\n", usage);
7818 quit(0);
7820 } else if (!strcmp(opt, "--all")) {
7821 argv_append(&opt_rev_args, opt);
7822 continue;
7823 }
7825 if (!argv_append(&filter_argv, opt))
7826 die("command too long");
7827 }
7829 if (filter_argv)
7830 filter_options(filter_argv);
7832 return request;
7833 }
7835 int
7836 main(int argc, const char *argv[])
7837 {
7838 const char *codeset = "UTF-8";
7839 enum request request = parse_options(argc, argv);
7840 struct view *view;
7841 size_t i;
7843 signal(SIGINT, quit);
7844 signal(SIGPIPE, SIG_IGN);
7846 if (setlocale(LC_ALL, "")) {
7847 codeset = nl_langinfo(CODESET);
7848 }
7850 if (load_repo_info() == ERR)
7851 die("Failed to load repo info.");
7853 if (load_options() == ERR)
7854 die("Failed to load user config.");
7856 if (load_git_config() == ERR)
7857 die("Failed to load repo config.");
7859 /* Require a git repository unless when running in pager mode. */
7860 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7861 die("Not a git repository");
7863 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7864 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7865 if (opt_iconv_in == ICONV_NONE)
7866 die("Failed to initialize character set conversion");
7867 }
7869 if (codeset && strcmp(codeset, "UTF-8")) {
7870 opt_iconv_out = iconv_open(codeset, "UTF-8");
7871 if (opt_iconv_out == ICONV_NONE)
7872 die("Failed to initialize character set conversion");
7873 }
7875 if (load_refs() == ERR)
7876 die("Failed to load refs.");
7878 foreach_view (view, i) {
7879 if (getenv(view->cmd_env))
7880 warn("Use of the %s environment variable is deprecated,"
7881 " use options or TIG_DIFF_ARGS instead",
7882 view->cmd_env);
7883 if (!argv_from_env(view->ops->argv, view->cmd_env))
7884 die("Too many arguments in the `%s` environment variable",
7885 view->cmd_env);
7886 }
7888 init_display();
7890 while (view_driver(display[current_view], request)) {
7891 int key = get_input(0);
7893 view = display[current_view];
7894 request = get_keybinding(view->keymap, key);
7896 /* Some low-level request handling. This keeps access to
7897 * status_win restricted. */
7898 switch (request) {
7899 case REQ_NONE:
7900 report("Unknown key, press %s for help",
7901 get_key(view->keymap, REQ_VIEW_HELP));
7902 break;
7903 case REQ_PROMPT:
7904 {
7905 char *cmd = read_prompt(":");
7907 if (cmd && isdigit(*cmd)) {
7908 int lineno = view->lineno + 1;
7910 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7911 select_view_line(view, lineno - 1);
7912 report("");
7913 } else {
7914 report("Unable to parse '%s' as a line number", cmd);
7915 }
7917 } else if (cmd) {
7918 struct view *next = VIEW(REQ_VIEW_PAGER);
7919 const char *argv[SIZEOF_ARG] = { "git" };
7920 int argc = 1;
7922 /* When running random commands, initially show the
7923 * command in the title. However, it maybe later be
7924 * overwritten if a commit line is selected. */
7925 string_ncopy(next->ref, cmd, strlen(cmd));
7927 if (!argv_from_string(argv, &argc, cmd)) {
7928 report("Too many arguments");
7929 } else if (!prepare_update(next, argv, NULL)) {
7930 report("Failed to format command");
7931 } else {
7932 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7933 }
7934 }
7936 request = REQ_NONE;
7937 break;
7938 }
7939 case REQ_SEARCH:
7940 case REQ_SEARCH_BACK:
7941 {
7942 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7943 char *search = read_prompt(prompt);
7945 if (search)
7946 string_ncopy(opt_search, search, strlen(search));
7947 else if (*opt_search)
7948 request = request == REQ_SEARCH ?
7949 REQ_FIND_NEXT :
7950 REQ_FIND_PREV;
7951 else
7952 request = REQ_NONE;
7953 break;
7954 }
7955 default:
7956 break;
7957 }
7958 }
7960 quit(0);
7962 return 0;
7963 }