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, ""),
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 == NULL) {
3712 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3713 die("Failed to open stdin");
3714 open_view(view, request, OPEN_PREPARED);
3715 break;
3716 }
3718 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3719 report("No pager content, press %s to run command from prompt",
3720 get_key(view->keymap, REQ_PROMPT));
3721 break;
3722 }
3723 open_view(view, request, OPEN_DEFAULT);
3724 break;
3726 case REQ_VIEW_STAGE:
3727 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3728 report("No stage content, press %s to open the status view and choose file",
3729 get_key(view->keymap, REQ_VIEW_STATUS));
3730 break;
3731 }
3732 open_view(view, request, OPEN_DEFAULT);
3733 break;
3735 case REQ_VIEW_STATUS:
3736 if (opt_is_inside_work_tree == FALSE) {
3737 report("The status view requires a working tree");
3738 break;
3739 }
3740 open_view(view, request, OPEN_DEFAULT);
3741 break;
3743 case REQ_VIEW_MAIN:
3744 case REQ_VIEW_DIFF:
3745 case REQ_VIEW_LOG:
3746 case REQ_VIEW_TREE:
3747 case REQ_VIEW_HELP:
3748 case REQ_VIEW_BRANCH:
3749 open_view(view, request, OPEN_DEFAULT);
3750 break;
3752 case REQ_NEXT:
3753 case REQ_PREVIOUS:
3754 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3756 if (view->parent) {
3757 int line;
3759 view = view->parent;
3760 line = view->lineno;
3761 move_view(view, request);
3762 if (view_is_displayed(view))
3763 update_view_title(view);
3764 if (line != view->lineno)
3765 view_request(view, REQ_ENTER);
3766 } else {
3767 move_view(view, request);
3768 }
3769 break;
3771 case REQ_VIEW_NEXT:
3772 {
3773 int nviews = displayed_views();
3774 int next_view = (current_view + 1) % nviews;
3776 if (next_view == current_view) {
3777 report("Only one view is displayed");
3778 break;
3779 }
3781 current_view = next_view;
3782 /* Blur out the title of the previous view. */
3783 update_view_title(view);
3784 report("");
3785 break;
3786 }
3787 case REQ_REFRESH:
3788 report("Refreshing is not yet supported for the %s view", view->name);
3789 break;
3791 case REQ_MAXIMIZE:
3792 if (displayed_views() == 2)
3793 maximize_view(view);
3794 break;
3796 case REQ_OPTIONS:
3797 open_option_menu();
3798 break;
3800 case REQ_TOGGLE_LINENO:
3801 toggle_view_option(&opt_line_number, "line numbers");
3802 break;
3804 case REQ_TOGGLE_DATE:
3805 toggle_date();
3806 break;
3808 case REQ_TOGGLE_AUTHOR:
3809 toggle_author();
3810 break;
3812 case REQ_TOGGLE_REV_GRAPH:
3813 toggle_view_option(&opt_rev_graph, "revision graph display");
3814 break;
3816 case REQ_TOGGLE_REFS:
3817 toggle_view_option(&opt_show_refs, "reference display");
3818 break;
3820 case REQ_TOGGLE_SORT_FIELD:
3821 case REQ_TOGGLE_SORT_ORDER:
3822 report("Sorting is not yet supported for the %s view", view->name);
3823 break;
3825 case REQ_SEARCH:
3826 case REQ_SEARCH_BACK:
3827 search_view(view, request);
3828 break;
3830 case REQ_FIND_NEXT:
3831 case REQ_FIND_PREV:
3832 find_next(view, request);
3833 break;
3835 case REQ_STOP_LOADING:
3836 foreach_view(view, i) {
3837 if (view->pipe)
3838 report("Stopped loading the %s view", view->name),
3839 end_update(view, TRUE);
3840 }
3841 break;
3843 case REQ_SHOW_VERSION:
3844 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3845 return TRUE;
3847 case REQ_SCREEN_REDRAW:
3848 redraw_display(TRUE);
3849 break;
3851 case REQ_EDIT:
3852 report("Nothing to edit");
3853 break;
3855 case REQ_ENTER:
3856 report("Nothing to enter");
3857 break;
3859 case REQ_VIEW_CLOSE:
3860 /* XXX: Mark closed views by letting view->prev point to the
3861 * view itself. Parents to closed view should never be
3862 * followed. */
3863 if (view->prev && view->prev != view) {
3864 maximize_view(view->prev);
3865 view->prev = view;
3866 break;
3867 }
3868 /* Fall-through */
3869 case REQ_QUIT:
3870 return FALSE;
3872 default:
3873 report("Unknown key, press %s for help",
3874 get_key(view->keymap, REQ_VIEW_HELP));
3875 return TRUE;
3876 }
3878 return TRUE;
3879 }
3882 /*
3883 * View backend utilities
3884 */
3886 enum sort_field {
3887 ORDERBY_NAME,
3888 ORDERBY_DATE,
3889 ORDERBY_AUTHOR,
3890 };
3892 struct sort_state {
3893 const enum sort_field *fields;
3894 size_t size, current;
3895 bool reverse;
3896 };
3898 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3899 #define get_sort_field(state) ((state).fields[(state).current])
3900 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3902 static void
3903 sort_view(struct view *view, enum request request, struct sort_state *state,
3904 int (*compare)(const void *, const void *))
3905 {
3906 switch (request) {
3907 case REQ_TOGGLE_SORT_FIELD:
3908 state->current = (state->current + 1) % state->size;
3909 break;
3911 case REQ_TOGGLE_SORT_ORDER:
3912 state->reverse = !state->reverse;
3913 break;
3914 default:
3915 die("Not a sort request");
3916 }
3918 qsort(view->line, view->lines, sizeof(*view->line), compare);
3919 redraw_view(view);
3920 }
3922 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3924 /* Small author cache to reduce memory consumption. It uses binary
3925 * search to lookup or find place to position new entries. No entries
3926 * are ever freed. */
3927 static const char *
3928 get_author(const char *name)
3929 {
3930 static const char **authors;
3931 static size_t authors_size;
3932 int from = 0, to = authors_size - 1;
3934 while (from <= to) {
3935 size_t pos = (to + from) / 2;
3936 int cmp = strcmp(name, authors[pos]);
3938 if (!cmp)
3939 return authors[pos];
3941 if (cmp < 0)
3942 to = pos - 1;
3943 else
3944 from = pos + 1;
3945 }
3947 if (!realloc_authors(&authors, authors_size, 1))
3948 return NULL;
3949 name = strdup(name);
3950 if (!name)
3951 return NULL;
3953 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3954 authors[from] = name;
3955 authors_size++;
3957 return name;
3958 }
3960 static void
3961 parse_timesec(struct time *time, const char *sec)
3962 {
3963 time->sec = (time_t) atol(sec);
3964 }
3966 static void
3967 parse_timezone(struct time *time, const char *zone)
3968 {
3969 long tz;
3971 tz = ('0' - zone[1]) * 60 * 60 * 10;
3972 tz += ('0' - zone[2]) * 60 * 60;
3973 tz += ('0' - zone[3]) * 60 * 10;
3974 tz += ('0' - zone[4]) * 60;
3976 if (zone[0] == '-')
3977 tz = -tz;
3979 time->tz = tz;
3980 time->sec -= tz;
3981 }
3983 /* Parse author lines where the name may be empty:
3984 * author <email@address.tld> 1138474660 +0100
3985 */
3986 static void
3987 parse_author_line(char *ident, const char **author, struct time *time)
3988 {
3989 char *nameend = strchr(ident, '<');
3990 char *emailend = strchr(ident, '>');
3992 if (nameend && emailend)
3993 *nameend = *emailend = 0;
3994 ident = chomp_string(ident);
3995 if (!*ident) {
3996 if (nameend)
3997 ident = chomp_string(nameend + 1);
3998 if (!*ident)
3999 ident = "Unknown";
4000 }
4002 *author = get_author(ident);
4004 /* Parse epoch and timezone */
4005 if (emailend && emailend[1] == ' ') {
4006 char *secs = emailend + 2;
4007 char *zone = strchr(secs, ' ');
4009 parse_timesec(time, secs);
4011 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4012 parse_timezone(time, zone + 1);
4013 }
4014 }
4016 /*
4017 * Pager backend
4018 */
4020 static bool
4021 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4022 {
4023 if (opt_line_number && draw_lineno(view, lineno))
4024 return TRUE;
4026 draw_text(view, line->type, line->data, TRUE);
4027 return TRUE;
4028 }
4030 static bool
4031 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4032 {
4033 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4034 char ref[SIZEOF_STR];
4036 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4037 return TRUE;
4039 /* This is the only fatal call, since it can "corrupt" the buffer. */
4040 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4041 return FALSE;
4043 return TRUE;
4044 }
4046 static void
4047 add_pager_refs(struct view *view, struct line *line)
4048 {
4049 char buf[SIZEOF_STR];
4050 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4051 struct ref_list *list;
4052 size_t bufpos = 0, i;
4053 const char *sep = "Refs: ";
4054 bool is_tag = FALSE;
4056 assert(line->type == LINE_COMMIT);
4058 list = get_ref_list(commit_id);
4059 if (!list) {
4060 if (view->type == VIEW_DIFF)
4061 goto try_add_describe_ref;
4062 return;
4063 }
4065 for (i = 0; i < list->size; i++) {
4066 struct ref *ref = list->refs[i];
4067 const char *fmt = ref->tag ? "%s[%s]" :
4068 ref->remote ? "%s<%s>" : "%s%s";
4070 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4071 return;
4072 sep = ", ";
4073 if (ref->tag)
4074 is_tag = TRUE;
4075 }
4077 if (!is_tag && view->type == VIEW_DIFF) {
4078 try_add_describe_ref:
4079 /* Add <tag>-g<commit_id> "fake" reference. */
4080 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4081 return;
4082 }
4084 if (bufpos == 0)
4085 return;
4087 add_line_text(view, buf, LINE_PP_REFS);
4088 }
4090 static bool
4091 pager_read(struct view *view, char *data)
4092 {
4093 struct line *line;
4095 if (!data)
4096 return TRUE;
4098 line = add_line_text(view, data, get_line_type(data));
4099 if (!line)
4100 return FALSE;
4102 if (line->type == LINE_COMMIT &&
4103 (view->type == VIEW_DIFF ||
4104 view->type == VIEW_LOG))
4105 add_pager_refs(view, line);
4107 return TRUE;
4108 }
4110 static enum request
4111 pager_request(struct view *view, enum request request, struct line *line)
4112 {
4113 int split = 0;
4115 if (request != REQ_ENTER)
4116 return request;
4118 if (line->type == LINE_COMMIT &&
4119 (view->type == VIEW_LOG ||
4120 view->type == VIEW_PAGER)) {
4121 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4122 split = 1;
4123 }
4125 /* Always scroll the view even if it was split. That way
4126 * you can use Enter to scroll through the log view and
4127 * split open each commit diff. */
4128 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4130 /* FIXME: A minor workaround. Scrolling the view will call report("")
4131 * but if we are scrolling a non-current view this won't properly
4132 * update the view title. */
4133 if (split)
4134 update_view_title(view);
4136 return REQ_NONE;
4137 }
4139 static bool
4140 pager_grep(struct view *view, struct line *line)
4141 {
4142 const char *text[] = { line->data, NULL };
4144 return grep_text(view, text);
4145 }
4147 static void
4148 pager_select(struct view *view, struct line *line)
4149 {
4150 if (line->type == LINE_COMMIT) {
4151 char *text = (char *)line->data + STRING_SIZE("commit ");
4153 if (view->type != VIEW_PAGER)
4154 string_copy_rev(view->ref, text);
4155 string_copy_rev(ref_commit, text);
4156 }
4157 }
4159 static struct view_ops pager_ops = {
4160 "line",
4161 NULL,
4162 NULL,
4163 pager_read,
4164 pager_draw,
4165 pager_request,
4166 pager_grep,
4167 pager_select,
4168 };
4170 static const char *log_argv[SIZEOF_ARG] = {
4171 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4172 };
4174 static enum request
4175 log_request(struct view *view, enum request request, struct line *line)
4176 {
4177 switch (request) {
4178 case REQ_REFRESH:
4179 load_refs();
4180 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4181 return REQ_NONE;
4182 default:
4183 return pager_request(view, request, line);
4184 }
4185 }
4187 static struct view_ops log_ops = {
4188 "line",
4189 log_argv,
4190 NULL,
4191 pager_read,
4192 pager_draw,
4193 log_request,
4194 pager_grep,
4195 pager_select,
4196 };
4198 static const char *diff_argv[SIZEOF_ARG] = {
4199 "git", "show", "--pretty=fuller", "--no-color", "--root",
4200 "--patch-with-stat", "--find-copies-harder", "-C",
4201 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4202 };
4204 static bool
4205 diff_read(struct view *view, char *data)
4206 {
4207 if (!data) {
4208 /* Fall back to retry if no diff will be shown. */
4209 if (view->lines == 0 && opt_file_args) {
4210 int pos = argv_size(view->argv)
4211 - argv_size(opt_file_args) - 1;
4213 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4214 for (; view->argv[pos]; pos++) {
4215 free((void *) view->argv[pos]);
4216 view->argv[pos] = NULL;
4217 }
4219 if (view->pipe)
4220 io_done(view->pipe);
4221 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4222 return FALSE;
4223 }
4224 }
4225 return TRUE;
4226 }
4228 return pager_read(view, data);
4229 }
4231 static struct view_ops diff_ops = {
4232 "line",
4233 diff_argv,
4234 NULL,
4235 diff_read,
4236 pager_draw,
4237 pager_request,
4238 pager_grep,
4239 pager_select,
4240 };
4242 /*
4243 * Help backend
4244 */
4246 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4248 static bool
4249 help_open_keymap_title(struct view *view, enum keymap keymap)
4250 {
4251 struct line *line;
4253 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4254 help_keymap_hidden[keymap] ? '+' : '-',
4255 enum_name(keymap_table[keymap]));
4256 if (line)
4257 line->other = keymap;
4259 return help_keymap_hidden[keymap];
4260 }
4262 static void
4263 help_open_keymap(struct view *view, enum keymap keymap)
4264 {
4265 const char *group = NULL;
4266 char buf[SIZEOF_STR];
4267 size_t bufpos;
4268 bool add_title = TRUE;
4269 int i;
4271 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4272 const char *key = NULL;
4274 if (req_info[i].request == REQ_NONE)
4275 continue;
4277 if (!req_info[i].request) {
4278 group = req_info[i].help;
4279 continue;
4280 }
4282 key = get_keys(keymap, req_info[i].request, TRUE);
4283 if (!key || !*key)
4284 continue;
4286 if (add_title && help_open_keymap_title(view, keymap))
4287 return;
4288 add_title = FALSE;
4290 if (group) {
4291 add_line_text(view, group, LINE_HELP_GROUP);
4292 group = NULL;
4293 }
4295 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4296 enum_name(req_info[i]), req_info[i].help);
4297 }
4299 group = "External commands:";
4301 for (i = 0; i < run_requests; i++) {
4302 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4303 const char *key;
4304 int argc;
4306 if (!req || req->keymap != keymap)
4307 continue;
4309 key = get_key_name(req->key);
4310 if (!*key)
4311 key = "(no key defined)";
4313 if (add_title && help_open_keymap_title(view, keymap))
4314 return;
4315 if (group) {
4316 add_line_text(view, group, LINE_HELP_GROUP);
4317 group = NULL;
4318 }
4320 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4321 if (!string_format_from(buf, &bufpos, "%s%s",
4322 argc ? " " : "", req->argv[argc]))
4323 return;
4325 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4326 }
4327 }
4329 static bool
4330 help_open(struct view *view)
4331 {
4332 enum keymap keymap;
4334 reset_view(view);
4335 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4336 add_line_text(view, "", LINE_DEFAULT);
4338 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4339 help_open_keymap(view, keymap);
4341 return TRUE;
4342 }
4344 static enum request
4345 help_request(struct view *view, enum request request, struct line *line)
4346 {
4347 switch (request) {
4348 case REQ_ENTER:
4349 if (line->type == LINE_HELP_KEYMAP) {
4350 help_keymap_hidden[line->other] =
4351 !help_keymap_hidden[line->other];
4352 view->p_restore = TRUE;
4353 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4354 }
4356 return REQ_NONE;
4357 default:
4358 return pager_request(view, request, line);
4359 }
4360 }
4362 static struct view_ops help_ops = {
4363 "line",
4364 NULL,
4365 help_open,
4366 NULL,
4367 pager_draw,
4368 help_request,
4369 pager_grep,
4370 pager_select,
4371 };
4374 /*
4375 * Tree backend
4376 */
4378 struct tree_stack_entry {
4379 struct tree_stack_entry *prev; /* Entry below this in the stack */
4380 unsigned long lineno; /* Line number to restore */
4381 char *name; /* Position of name in opt_path */
4382 };
4384 /* The top of the path stack. */
4385 static struct tree_stack_entry *tree_stack = NULL;
4386 unsigned long tree_lineno = 0;
4388 static void
4389 pop_tree_stack_entry(void)
4390 {
4391 struct tree_stack_entry *entry = tree_stack;
4393 tree_lineno = entry->lineno;
4394 entry->name[0] = 0;
4395 tree_stack = entry->prev;
4396 free(entry);
4397 }
4399 static void
4400 push_tree_stack_entry(const char *name, unsigned long lineno)
4401 {
4402 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4403 size_t pathlen = strlen(opt_path);
4405 if (!entry)
4406 return;
4408 entry->prev = tree_stack;
4409 entry->name = opt_path + pathlen;
4410 tree_stack = entry;
4412 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4413 pop_tree_stack_entry();
4414 return;
4415 }
4417 /* Move the current line to the first tree entry. */
4418 tree_lineno = 1;
4419 entry->lineno = lineno;
4420 }
4422 /* Parse output from git-ls-tree(1):
4423 *
4424 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4425 */
4427 #define SIZEOF_TREE_ATTR \
4428 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4430 #define SIZEOF_TREE_MODE \
4431 STRING_SIZE("100644 ")
4433 #define TREE_ID_OFFSET \
4434 STRING_SIZE("100644 blob ")
4436 struct tree_entry {
4437 char id[SIZEOF_REV];
4438 mode_t mode;
4439 struct time time; /* Date from the author ident. */
4440 const char *author; /* Author of the commit. */
4441 char name[1];
4442 };
4444 static const char *
4445 tree_path(const struct line *line)
4446 {
4447 return ((struct tree_entry *) line->data)->name;
4448 }
4450 static int
4451 tree_compare_entry(const struct line *line1, const struct line *line2)
4452 {
4453 if (line1->type != line2->type)
4454 return line1->type == LINE_TREE_DIR ? -1 : 1;
4455 return strcmp(tree_path(line1), tree_path(line2));
4456 }
4458 static const enum sort_field tree_sort_fields[] = {
4459 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4460 };
4461 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4463 static int
4464 tree_compare(const void *l1, const void *l2)
4465 {
4466 const struct line *line1 = (const struct line *) l1;
4467 const struct line *line2 = (const struct line *) l2;
4468 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4469 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4471 if (line1->type == LINE_TREE_HEAD)
4472 return -1;
4473 if (line2->type == LINE_TREE_HEAD)
4474 return 1;
4476 switch (get_sort_field(tree_sort_state)) {
4477 case ORDERBY_DATE:
4478 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4480 case ORDERBY_AUTHOR:
4481 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4483 case ORDERBY_NAME:
4484 default:
4485 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4486 }
4487 }
4490 static struct line *
4491 tree_entry(struct view *view, enum line_type type, const char *path,
4492 const char *mode, const char *id)
4493 {
4494 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4495 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4497 if (!entry || !line) {
4498 free(entry);
4499 return NULL;
4500 }
4502 strncpy(entry->name, path, strlen(path));
4503 if (mode)
4504 entry->mode = strtoul(mode, NULL, 8);
4505 if (id)
4506 string_copy_rev(entry->id, id);
4508 return line;
4509 }
4511 static bool
4512 tree_read_date(struct view *view, char *text, bool *read_date)
4513 {
4514 static const char *author_name;
4515 static struct time author_time;
4517 if (!text && *read_date) {
4518 *read_date = FALSE;
4519 return TRUE;
4521 } else if (!text) {
4522 char *path = *opt_path ? opt_path : ".";
4523 /* Find next entry to process */
4524 const char *log_file[] = {
4525 "git", "log", "--no-color", "--pretty=raw",
4526 "--cc", "--raw", view->id, "--", path, NULL
4527 };
4529 if (!view->lines) {
4530 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4531 report("Tree is empty");
4532 return TRUE;
4533 }
4535 if (!start_update(view, log_file, opt_cdup)) {
4536 report("Failed to load tree data");
4537 return TRUE;
4538 }
4540 *read_date = TRUE;
4541 return FALSE;
4543 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4544 parse_author_line(text + STRING_SIZE("author "),
4545 &author_name, &author_time);
4547 } else if (*text == ':') {
4548 char *pos;
4549 size_t annotated = 1;
4550 size_t i;
4552 pos = strchr(text, '\t');
4553 if (!pos)
4554 return TRUE;
4555 text = pos + 1;
4556 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4557 text += strlen(opt_path);
4558 pos = strchr(text, '/');
4559 if (pos)
4560 *pos = 0;
4562 for (i = 1; i < view->lines; i++) {
4563 struct line *line = &view->line[i];
4564 struct tree_entry *entry = line->data;
4566 annotated += !!entry->author;
4567 if (entry->author || strcmp(entry->name, text))
4568 continue;
4570 entry->author = author_name;
4571 entry->time = author_time;
4572 line->dirty = 1;
4573 break;
4574 }
4576 if (annotated == view->lines)
4577 io_kill(view->pipe);
4578 }
4579 return TRUE;
4580 }
4582 static bool
4583 tree_read(struct view *view, char *text)
4584 {
4585 static bool read_date = FALSE;
4586 struct tree_entry *data;
4587 struct line *entry, *line;
4588 enum line_type type;
4589 size_t textlen = text ? strlen(text) : 0;
4590 char *path = text + SIZEOF_TREE_ATTR;
4592 if (read_date || !text)
4593 return tree_read_date(view, text, &read_date);
4595 if (textlen <= SIZEOF_TREE_ATTR)
4596 return FALSE;
4597 if (view->lines == 0 &&
4598 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4599 return FALSE;
4601 /* Strip the path part ... */
4602 if (*opt_path) {
4603 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4604 size_t striplen = strlen(opt_path);
4606 if (pathlen > striplen)
4607 memmove(path, path + striplen,
4608 pathlen - striplen + 1);
4610 /* Insert "link" to parent directory. */
4611 if (view->lines == 1 &&
4612 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4613 return FALSE;
4614 }
4616 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4617 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4618 if (!entry)
4619 return FALSE;
4620 data = entry->data;
4622 /* Skip "Directory ..." and ".." line. */
4623 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4624 if (tree_compare_entry(line, entry) <= 0)
4625 continue;
4627 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4629 line->data = data;
4630 line->type = type;
4631 for (; line <= entry; line++)
4632 line->dirty = line->cleareol = 1;
4633 return TRUE;
4634 }
4636 if (tree_lineno > view->lineno) {
4637 view->lineno = tree_lineno;
4638 tree_lineno = 0;
4639 }
4641 return TRUE;
4642 }
4644 static bool
4645 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4646 {
4647 struct tree_entry *entry = line->data;
4649 if (line->type == LINE_TREE_HEAD) {
4650 if (draw_text(view, line->type, "Directory path /", TRUE))
4651 return TRUE;
4652 } else {
4653 if (draw_mode(view, entry->mode))
4654 return TRUE;
4656 if (opt_author && draw_author(view, entry->author))
4657 return TRUE;
4659 if (opt_date && draw_date(view, &entry->time))
4660 return TRUE;
4661 }
4662 if (draw_text(view, line->type, entry->name, TRUE))
4663 return TRUE;
4664 return TRUE;
4665 }
4667 static void
4668 open_blob_editor(const char *id)
4669 {
4670 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4671 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4672 int fd = mkstemp(file);
4674 if (fd == -1)
4675 report("Failed to create temporary file");
4676 else if (!io_run_append(blob_argv, fd))
4677 report("Failed to save blob data to file");
4678 else
4679 open_editor(file);
4680 if (fd != -1)
4681 unlink(file);
4682 }
4684 static enum request
4685 tree_request(struct view *view, enum request request, struct line *line)
4686 {
4687 enum open_flags flags;
4688 struct tree_entry *entry = line->data;
4690 switch (request) {
4691 case REQ_VIEW_BLAME:
4692 if (line->type != LINE_TREE_FILE) {
4693 report("Blame only supported for files");
4694 return REQ_NONE;
4695 }
4697 string_copy(opt_ref, view->vid);
4698 return request;
4700 case REQ_EDIT:
4701 if (line->type != LINE_TREE_FILE) {
4702 report("Edit only supported for files");
4703 } else if (!is_head_commit(view->vid)) {
4704 open_blob_editor(entry->id);
4705 } else {
4706 open_editor(opt_file);
4707 }
4708 return REQ_NONE;
4710 case REQ_TOGGLE_SORT_FIELD:
4711 case REQ_TOGGLE_SORT_ORDER:
4712 sort_view(view, request, &tree_sort_state, tree_compare);
4713 return REQ_NONE;
4715 case REQ_PARENT:
4716 if (!*opt_path) {
4717 /* quit view if at top of tree */
4718 return REQ_VIEW_CLOSE;
4719 }
4720 /* fake 'cd ..' */
4721 line = &view->line[1];
4722 break;
4724 case REQ_ENTER:
4725 break;
4727 default:
4728 return request;
4729 }
4731 /* Cleanup the stack if the tree view is at a different tree. */
4732 while (!*opt_path && tree_stack)
4733 pop_tree_stack_entry();
4735 switch (line->type) {
4736 case LINE_TREE_DIR:
4737 /* Depending on whether it is a subdirectory or parent link
4738 * mangle the path buffer. */
4739 if (line == &view->line[1] && *opt_path) {
4740 pop_tree_stack_entry();
4742 } else {
4743 const char *basename = tree_path(line);
4745 push_tree_stack_entry(basename, view->lineno);
4746 }
4748 /* Trees and subtrees share the same ID, so they are not not
4749 * unique like blobs. */
4750 flags = OPEN_RELOAD;
4751 request = REQ_VIEW_TREE;
4752 break;
4754 case LINE_TREE_FILE:
4755 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4756 request = REQ_VIEW_BLOB;
4757 break;
4759 default:
4760 return REQ_NONE;
4761 }
4763 open_view(view, request, flags);
4764 if (request == REQ_VIEW_TREE)
4765 view->lineno = tree_lineno;
4767 return REQ_NONE;
4768 }
4770 static bool
4771 tree_grep(struct view *view, struct line *line)
4772 {
4773 struct tree_entry *entry = line->data;
4774 const char *text[] = {
4775 entry->name,
4776 opt_author ? entry->author : "",
4777 mkdate(&entry->time, opt_date),
4778 NULL
4779 };
4781 return grep_text(view, text);
4782 }
4784 static void
4785 tree_select(struct view *view, struct line *line)
4786 {
4787 struct tree_entry *entry = line->data;
4789 if (line->type == LINE_TREE_FILE) {
4790 string_copy_rev(ref_blob, entry->id);
4791 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4793 } else if (line->type != LINE_TREE_DIR) {
4794 return;
4795 }
4797 string_copy_rev(view->ref, entry->id);
4798 }
4800 static bool
4801 tree_prepare(struct view *view)
4802 {
4803 if (view->lines == 0 && opt_prefix[0]) {
4804 char *pos = opt_prefix;
4806 while (pos && *pos) {
4807 char *end = strchr(pos, '/');
4809 if (end)
4810 *end = 0;
4811 push_tree_stack_entry(pos, 0);
4812 pos = end;
4813 if (end) {
4814 *end = '/';
4815 pos++;
4816 }
4817 }
4819 } else if (strcmp(view->vid, view->id)) {
4820 opt_path[0] = 0;
4821 }
4823 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4824 }
4826 static const char *tree_argv[SIZEOF_ARG] = {
4827 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4828 };
4830 static struct view_ops tree_ops = {
4831 "file",
4832 tree_argv,
4833 NULL,
4834 tree_read,
4835 tree_draw,
4836 tree_request,
4837 tree_grep,
4838 tree_select,
4839 tree_prepare,
4840 };
4842 static bool
4843 blob_read(struct view *view, char *line)
4844 {
4845 if (!line)
4846 return TRUE;
4847 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4848 }
4850 static enum request
4851 blob_request(struct view *view, enum request request, struct line *line)
4852 {
4853 switch (request) {
4854 case REQ_EDIT:
4855 open_blob_editor(view->vid);
4856 return REQ_NONE;
4857 default:
4858 return pager_request(view, request, line);
4859 }
4860 }
4862 static const char *blob_argv[SIZEOF_ARG] = {
4863 "git", "cat-file", "blob", "%(blob)", NULL
4864 };
4866 static struct view_ops blob_ops = {
4867 "line",
4868 blob_argv,
4869 NULL,
4870 blob_read,
4871 pager_draw,
4872 blob_request,
4873 pager_grep,
4874 pager_select,
4875 };
4877 /*
4878 * Blame backend
4879 *
4880 * Loading the blame view is a two phase job:
4881 *
4882 * 1. File content is read either using opt_file from the
4883 * filesystem or using git-cat-file.
4884 * 2. Then blame information is incrementally added by
4885 * reading output from git-blame.
4886 */
4888 struct blame_commit {
4889 char id[SIZEOF_REV]; /* SHA1 ID. */
4890 char title[128]; /* First line of the commit message. */
4891 const char *author; /* Author of the commit. */
4892 struct time time; /* Date from the author ident. */
4893 char filename[128]; /* Name of file. */
4894 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4895 char parent_filename[128]; /* Parent/previous name of file. */
4896 };
4898 struct blame {
4899 struct blame_commit *commit;
4900 unsigned long lineno;
4901 char text[1];
4902 };
4904 static bool
4905 blame_open(struct view *view)
4906 {
4907 char path[SIZEOF_STR];
4908 size_t i;
4910 if (!view->prev && *opt_prefix) {
4911 string_copy(path, opt_file);
4912 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4913 return FALSE;
4914 }
4916 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4917 const char *blame_cat_file_argv[] = {
4918 "git", "cat-file", "blob", path, NULL
4919 };
4921 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4922 !start_update(view, blame_cat_file_argv, opt_cdup))
4923 return FALSE;
4924 }
4926 /* First pass: remove multiple references to the same commit. */
4927 for (i = 0; i < view->lines; i++) {
4928 struct blame *blame = view->line[i].data;
4930 if (blame->commit && blame->commit->id[0])
4931 blame->commit->id[0] = 0;
4932 else
4933 blame->commit = NULL;
4934 }
4936 /* Second pass: free existing references. */
4937 for (i = 0; i < view->lines; i++) {
4938 struct blame *blame = view->line[i].data;
4940 if (blame->commit)
4941 free(blame->commit);
4942 }
4944 setup_update(view, opt_file);
4945 string_format(view->ref, "%s ...", opt_file);
4947 return TRUE;
4948 }
4950 static struct blame_commit *
4951 get_blame_commit(struct view *view, const char *id)
4952 {
4953 size_t i;
4955 for (i = 0; i < view->lines; i++) {
4956 struct blame *blame = view->line[i].data;
4958 if (!blame->commit)
4959 continue;
4961 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4962 return blame->commit;
4963 }
4965 {
4966 struct blame_commit *commit = calloc(1, sizeof(*commit));
4968 if (commit)
4969 string_ncopy(commit->id, id, SIZEOF_REV);
4970 return commit;
4971 }
4972 }
4974 static bool
4975 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4976 {
4977 const char *pos = *posref;
4979 *posref = NULL;
4980 pos = strchr(pos + 1, ' ');
4981 if (!pos || !isdigit(pos[1]))
4982 return FALSE;
4983 *number = atoi(pos + 1);
4984 if (*number < min || *number > max)
4985 return FALSE;
4987 *posref = pos;
4988 return TRUE;
4989 }
4991 static struct blame_commit *
4992 parse_blame_commit(struct view *view, const char *text, int *blamed)
4993 {
4994 struct blame_commit *commit;
4995 struct blame *blame;
4996 const char *pos = text + SIZEOF_REV - 2;
4997 size_t orig_lineno = 0;
4998 size_t lineno;
4999 size_t group;
5001 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5002 return NULL;
5004 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5005 !parse_number(&pos, &lineno, 1, view->lines) ||
5006 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5007 return NULL;
5009 commit = get_blame_commit(view, text);
5010 if (!commit)
5011 return NULL;
5013 *blamed += group;
5014 while (group--) {
5015 struct line *line = &view->line[lineno + group - 1];
5017 blame = line->data;
5018 blame->commit = commit;
5019 blame->lineno = orig_lineno + group - 1;
5020 line->dirty = 1;
5021 }
5023 return commit;
5024 }
5026 static bool
5027 blame_read_file(struct view *view, const char *line, bool *read_file)
5028 {
5029 if (!line) {
5030 const char *blame_argv[] = {
5031 "git", "blame", "--incremental",
5032 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5033 };
5035 if (view->lines == 0 && !view->prev)
5036 die("No blame exist for %s", view->vid);
5038 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5039 report("Failed to load blame data");
5040 return TRUE;
5041 }
5043 *read_file = FALSE;
5044 return FALSE;
5046 } else {
5047 size_t linelen = strlen(line);
5048 struct blame *blame = malloc(sizeof(*blame) + linelen);
5050 if (!blame)
5051 return FALSE;
5053 blame->commit = NULL;
5054 strncpy(blame->text, line, linelen);
5055 blame->text[linelen] = 0;
5056 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5057 }
5058 }
5060 static bool
5061 match_blame_header(const char *name, char **line)
5062 {
5063 size_t namelen = strlen(name);
5064 bool matched = !strncmp(name, *line, namelen);
5066 if (matched)
5067 *line += namelen;
5069 return matched;
5070 }
5072 static bool
5073 blame_read(struct view *view, char *line)
5074 {
5075 static struct blame_commit *commit = NULL;
5076 static int blamed = 0;
5077 static bool read_file = TRUE;
5079 if (read_file)
5080 return blame_read_file(view, line, &read_file);
5082 if (!line) {
5083 /* Reset all! */
5084 commit = NULL;
5085 blamed = 0;
5086 read_file = TRUE;
5087 string_format(view->ref, "%s", view->vid);
5088 if (view_is_displayed(view)) {
5089 update_view_title(view);
5090 redraw_view_from(view, 0);
5091 }
5092 return TRUE;
5093 }
5095 if (!commit) {
5096 commit = parse_blame_commit(view, line, &blamed);
5097 string_format(view->ref, "%s %2d%%", view->vid,
5098 view->lines ? blamed * 100 / view->lines : 0);
5100 } else if (match_blame_header("author ", &line)) {
5101 commit->author = get_author(line);
5103 } else if (match_blame_header("author-time ", &line)) {
5104 parse_timesec(&commit->time, line);
5106 } else if (match_blame_header("author-tz ", &line)) {
5107 parse_timezone(&commit->time, line);
5109 } else if (match_blame_header("summary ", &line)) {
5110 string_ncopy(commit->title, line, strlen(line));
5112 } else if (match_blame_header("previous ", &line)) {
5113 if (strlen(line) <= SIZEOF_REV)
5114 return FALSE;
5115 string_copy_rev(commit->parent_id, line);
5116 line += SIZEOF_REV;
5117 string_ncopy(commit->parent_filename, line, strlen(line));
5119 } else if (match_blame_header("filename ", &line)) {
5120 string_ncopy(commit->filename, line, strlen(line));
5121 commit = NULL;
5122 }
5124 return TRUE;
5125 }
5127 static bool
5128 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5129 {
5130 struct blame *blame = line->data;
5131 struct time *time = NULL;
5132 const char *id = NULL, *author = NULL;
5134 if (blame->commit && *blame->commit->filename) {
5135 id = blame->commit->id;
5136 author = blame->commit->author;
5137 time = &blame->commit->time;
5138 }
5140 if (opt_date && draw_date(view, time))
5141 return TRUE;
5143 if (opt_author && draw_author(view, author))
5144 return TRUE;
5146 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5147 return TRUE;
5149 if (draw_lineno(view, lineno))
5150 return TRUE;
5152 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5153 return TRUE;
5154 }
5156 static bool
5157 check_blame_commit(struct blame *blame, bool check_null_id)
5158 {
5159 if (!blame->commit)
5160 report("Commit data not loaded yet");
5161 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5162 report("No commit exist for the selected line");
5163 else
5164 return TRUE;
5165 return FALSE;
5166 }
5168 static void
5169 setup_blame_parent_line(struct view *view, struct blame *blame)
5170 {
5171 char from[SIZEOF_REF + SIZEOF_STR];
5172 char to[SIZEOF_REF + SIZEOF_STR];
5173 const char *diff_tree_argv[] = {
5174 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5175 "-U0", from, to, "--", NULL
5176 };
5177 struct io io;
5178 int parent_lineno = -1;
5179 int blamed_lineno = -1;
5180 char *line;
5182 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5183 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5184 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5185 return;
5187 while ((line = io_get(&io, '\n', TRUE))) {
5188 if (*line == '@') {
5189 char *pos = strchr(line, '+');
5191 parent_lineno = atoi(line + 4);
5192 if (pos)
5193 blamed_lineno = atoi(pos + 1);
5195 } else if (*line == '+' && parent_lineno != -1) {
5196 if (blame->lineno == blamed_lineno - 1 &&
5197 !strcmp(blame->text, line + 1)) {
5198 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5199 break;
5200 }
5201 blamed_lineno++;
5202 }
5203 }
5205 io_done(&io);
5206 }
5208 static enum request
5209 blame_request(struct view *view, enum request request, struct line *line)
5210 {
5211 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5212 struct blame *blame = line->data;
5214 switch (request) {
5215 case REQ_VIEW_BLAME:
5216 if (check_blame_commit(blame, TRUE)) {
5217 string_copy(opt_ref, blame->commit->id);
5218 string_copy(opt_file, blame->commit->filename);
5219 if (blame->lineno)
5220 view->lineno = blame->lineno;
5221 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5222 }
5223 break;
5225 case REQ_PARENT:
5226 if (!check_blame_commit(blame, TRUE))
5227 break;
5228 if (!*blame->commit->parent_id) {
5229 report("The selected commit has no parents");
5230 } else {
5231 string_copy_rev(opt_ref, blame->commit->parent_id);
5232 string_copy(opt_file, blame->commit->parent_filename);
5233 setup_blame_parent_line(view, blame);
5234 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5235 }
5236 break;
5238 case REQ_ENTER:
5239 if (!check_blame_commit(blame, FALSE))
5240 break;
5242 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5243 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5244 break;
5246 if (!strcmp(blame->commit->id, NULL_ID)) {
5247 struct view *diff = VIEW(REQ_VIEW_DIFF);
5248 const char *diff_index_argv[] = {
5249 "git", "diff-index", "--root", "--patch-with-stat",
5250 "-C", "-M", "HEAD", "--", view->vid, NULL
5251 };
5253 if (!*blame->commit->parent_id) {
5254 diff_index_argv[1] = "diff";
5255 diff_index_argv[2] = "--no-color";
5256 diff_index_argv[6] = "--";
5257 diff_index_argv[7] = "/dev/null";
5258 }
5260 if (!prepare_update(diff, diff_index_argv, NULL)) {
5261 report("Failed to allocate diff command");
5262 break;
5263 }
5264 flags |= OPEN_PREPARED;
5265 }
5267 open_view(view, REQ_VIEW_DIFF, flags);
5268 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5269 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5270 break;
5272 default:
5273 return request;
5274 }
5276 return REQ_NONE;
5277 }
5279 static bool
5280 blame_grep(struct view *view, struct line *line)
5281 {
5282 struct blame *blame = line->data;
5283 struct blame_commit *commit = blame->commit;
5284 const char *text[] = {
5285 blame->text,
5286 commit ? commit->title : "",
5287 commit ? commit->id : "",
5288 commit && opt_author ? commit->author : "",
5289 commit ? mkdate(&commit->time, opt_date) : "",
5290 NULL
5291 };
5293 return grep_text(view, text);
5294 }
5296 static void
5297 blame_select(struct view *view, struct line *line)
5298 {
5299 struct blame *blame = line->data;
5300 struct blame_commit *commit = blame->commit;
5302 if (!commit)
5303 return;
5305 if (!strcmp(commit->id, NULL_ID))
5306 string_ncopy(ref_commit, "HEAD", 4);
5307 else
5308 string_copy_rev(ref_commit, commit->id);
5309 }
5311 static struct view_ops blame_ops = {
5312 "line",
5313 NULL,
5314 blame_open,
5315 blame_read,
5316 blame_draw,
5317 blame_request,
5318 blame_grep,
5319 blame_select,
5320 };
5322 /*
5323 * Branch backend
5324 */
5326 struct branch {
5327 const char *author; /* Author of the last commit. */
5328 struct time time; /* Date of the last activity. */
5329 const struct ref *ref; /* Name and commit ID information. */
5330 };
5332 static const struct ref branch_all;
5334 static const enum sort_field branch_sort_fields[] = {
5335 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5336 };
5337 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5339 static int
5340 branch_compare(const void *l1, const void *l2)
5341 {
5342 const struct branch *branch1 = ((const struct line *) l1)->data;
5343 const struct branch *branch2 = ((const struct line *) l2)->data;
5345 switch (get_sort_field(branch_sort_state)) {
5346 case ORDERBY_DATE:
5347 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5349 case ORDERBY_AUTHOR:
5350 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5352 case ORDERBY_NAME:
5353 default:
5354 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5355 }
5356 }
5358 static bool
5359 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5360 {
5361 struct branch *branch = line->data;
5362 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5364 if (opt_date && draw_date(view, &branch->time))
5365 return TRUE;
5367 if (opt_author && draw_author(view, branch->author))
5368 return TRUE;
5370 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5371 return TRUE;
5372 }
5374 static enum request
5375 branch_request(struct view *view, enum request request, struct line *line)
5376 {
5377 struct branch *branch = line->data;
5379 switch (request) {
5380 case REQ_REFRESH:
5381 load_refs();
5382 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5383 return REQ_NONE;
5385 case REQ_TOGGLE_SORT_FIELD:
5386 case REQ_TOGGLE_SORT_ORDER:
5387 sort_view(view, request, &branch_sort_state, branch_compare);
5388 return REQ_NONE;
5390 case REQ_ENTER:
5391 {
5392 const struct ref *ref = branch->ref;
5393 const char *all_branches_argv[] = {
5394 "git", "log", "--no-color", "--pretty=raw", "--parents",
5395 "--topo-order",
5396 ref == &branch_all ? "--all" : ref->name, NULL
5397 };
5398 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5400 if (!prepare_update(main_view, all_branches_argv, NULL))
5401 report("Failed to load view of all branches");
5402 else
5403 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5404 return REQ_NONE;
5405 }
5406 default:
5407 return request;
5408 }
5409 }
5411 static bool
5412 branch_read(struct view *view, char *line)
5413 {
5414 static char id[SIZEOF_REV];
5415 struct branch *reference;
5416 size_t i;
5418 if (!line)
5419 return TRUE;
5421 switch (get_line_type(line)) {
5422 case LINE_COMMIT:
5423 string_copy_rev(id, line + STRING_SIZE("commit "));
5424 return TRUE;
5426 case LINE_AUTHOR:
5427 for (i = 0, reference = NULL; i < view->lines; i++) {
5428 struct branch *branch = view->line[i].data;
5430 if (strcmp(branch->ref->id, id))
5431 continue;
5433 view->line[i].dirty = TRUE;
5434 if (reference) {
5435 branch->author = reference->author;
5436 branch->time = reference->time;
5437 continue;
5438 }
5440 parse_author_line(line + STRING_SIZE("author "),
5441 &branch->author, &branch->time);
5442 reference = branch;
5443 }
5444 return TRUE;
5446 default:
5447 return TRUE;
5448 }
5450 }
5452 static bool
5453 branch_open_visitor(void *data, const struct ref *ref)
5454 {
5455 struct view *view = data;
5456 struct branch *branch;
5458 if (ref->tag || ref->ltag || ref->remote)
5459 return TRUE;
5461 branch = calloc(1, sizeof(*branch));
5462 if (!branch)
5463 return FALSE;
5465 branch->ref = ref;
5466 return !!add_line_data(view, branch, LINE_DEFAULT);
5467 }
5469 static bool
5470 branch_open(struct view *view)
5471 {
5472 const char *branch_log[] = {
5473 "git", "log", "--no-color", "--pretty=raw",
5474 "--simplify-by-decoration", "--all", NULL
5475 };
5477 if (!start_update(view, branch_log, NULL)) {
5478 report("Failed to load branch data");
5479 return TRUE;
5480 }
5482 setup_update(view, view->id);
5483 branch_open_visitor(view, &branch_all);
5484 foreach_ref(branch_open_visitor, view);
5485 view->p_restore = TRUE;
5487 return TRUE;
5488 }
5490 static bool
5491 branch_grep(struct view *view, struct line *line)
5492 {
5493 struct branch *branch = line->data;
5494 const char *text[] = {
5495 branch->ref->name,
5496 branch->author,
5497 NULL
5498 };
5500 return grep_text(view, text);
5501 }
5503 static void
5504 branch_select(struct view *view, struct line *line)
5505 {
5506 struct branch *branch = line->data;
5508 string_copy_rev(view->ref, branch->ref->id);
5509 string_copy_rev(ref_commit, branch->ref->id);
5510 string_copy_rev(ref_head, branch->ref->id);
5511 string_copy_rev(ref_branch, branch->ref->name);
5512 }
5514 static struct view_ops branch_ops = {
5515 "branch",
5516 NULL,
5517 branch_open,
5518 branch_read,
5519 branch_draw,
5520 branch_request,
5521 branch_grep,
5522 branch_select,
5523 };
5525 /*
5526 * Status backend
5527 */
5529 struct status {
5530 char status;
5531 struct {
5532 mode_t mode;
5533 char rev[SIZEOF_REV];
5534 char name[SIZEOF_STR];
5535 } old;
5536 struct {
5537 mode_t mode;
5538 char rev[SIZEOF_REV];
5539 char name[SIZEOF_STR];
5540 } new;
5541 };
5543 static char status_onbranch[SIZEOF_STR];
5544 static struct status stage_status;
5545 static enum line_type stage_line_type;
5546 static size_t stage_chunks;
5547 static int *stage_chunk;
5549 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5551 /* This should work even for the "On branch" line. */
5552 static inline bool
5553 status_has_none(struct view *view, struct line *line)
5554 {
5555 return line < view->line + view->lines && !line[1].data;
5556 }
5558 /* Get fields from the diff line:
5559 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5560 */
5561 static inline bool
5562 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5563 {
5564 const char *old_mode = buf + 1;
5565 const char *new_mode = buf + 8;
5566 const char *old_rev = buf + 15;
5567 const char *new_rev = buf + 56;
5568 const char *status = buf + 97;
5570 if (bufsize < 98 ||
5571 old_mode[-1] != ':' ||
5572 new_mode[-1] != ' ' ||
5573 old_rev[-1] != ' ' ||
5574 new_rev[-1] != ' ' ||
5575 status[-1] != ' ')
5576 return FALSE;
5578 file->status = *status;
5580 string_copy_rev(file->old.rev, old_rev);
5581 string_copy_rev(file->new.rev, new_rev);
5583 file->old.mode = strtoul(old_mode, NULL, 8);
5584 file->new.mode = strtoul(new_mode, NULL, 8);
5586 file->old.name[0] = file->new.name[0] = 0;
5588 return TRUE;
5589 }
5591 static bool
5592 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5593 {
5594 struct status *unmerged = NULL;
5595 char *buf;
5596 struct io io;
5598 if (!io_run(&io, IO_RD, opt_cdup, argv))
5599 return FALSE;
5601 add_line_data(view, NULL, type);
5603 while ((buf = io_get(&io, 0, TRUE))) {
5604 struct status *file = unmerged;
5606 if (!file) {
5607 file = calloc(1, sizeof(*file));
5608 if (!file || !add_line_data(view, file, type))
5609 goto error_out;
5610 }
5612 /* Parse diff info part. */
5613 if (status) {
5614 file->status = status;
5615 if (status == 'A')
5616 string_copy(file->old.rev, NULL_ID);
5618 } else if (!file->status || file == unmerged) {
5619 if (!status_get_diff(file, buf, strlen(buf)))
5620 goto error_out;
5622 buf = io_get(&io, 0, TRUE);
5623 if (!buf)
5624 break;
5626 /* Collapse all modified entries that follow an
5627 * associated unmerged entry. */
5628 if (unmerged == file) {
5629 unmerged->status = 'U';
5630 unmerged = NULL;
5631 } else if (file->status == 'U') {
5632 unmerged = file;
5633 }
5634 }
5636 /* Grab the old name for rename/copy. */
5637 if (!*file->old.name &&
5638 (file->status == 'R' || file->status == 'C')) {
5639 string_ncopy(file->old.name, buf, strlen(buf));
5641 buf = io_get(&io, 0, TRUE);
5642 if (!buf)
5643 break;
5644 }
5646 /* git-ls-files just delivers a NUL separated list of
5647 * file names similar to the second half of the
5648 * git-diff-* output. */
5649 string_ncopy(file->new.name, buf, strlen(buf));
5650 if (!*file->old.name)
5651 string_copy(file->old.name, file->new.name);
5652 file = NULL;
5653 }
5655 if (io_error(&io)) {
5656 error_out:
5657 io_done(&io);
5658 return FALSE;
5659 }
5661 if (!view->line[view->lines - 1].data)
5662 add_line_data(view, NULL, LINE_STAT_NONE);
5664 io_done(&io);
5665 return TRUE;
5666 }
5668 /* Don't show unmerged entries in the staged section. */
5669 static const char *status_diff_index_argv[] = {
5670 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5671 "--cached", "-M", "HEAD", NULL
5672 };
5674 static const char *status_diff_files_argv[] = {
5675 "git", "diff-files", "-z", NULL
5676 };
5678 static const char *status_list_other_argv[] = {
5679 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5680 };
5682 static const char *status_list_no_head_argv[] = {
5683 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5684 };
5686 static const char *update_index_argv[] = {
5687 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5688 };
5690 /* Restore the previous line number to stay in the context or select a
5691 * line with something that can be updated. */
5692 static void
5693 status_restore(struct view *view)
5694 {
5695 if (view->p_lineno >= view->lines)
5696 view->p_lineno = view->lines - 1;
5697 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5698 view->p_lineno++;
5699 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5700 view->p_lineno--;
5702 /* If the above fails, always skip the "On branch" line. */
5703 if (view->p_lineno < view->lines)
5704 view->lineno = view->p_lineno;
5705 else
5706 view->lineno = 1;
5708 if (view->lineno < view->offset)
5709 view->offset = view->lineno;
5710 else if (view->offset + view->height <= view->lineno)
5711 view->offset = view->lineno - view->height + 1;
5713 view->p_restore = FALSE;
5714 }
5716 static void
5717 status_update_onbranch(void)
5718 {
5719 static const char *paths[][2] = {
5720 { "rebase-apply/rebasing", "Rebasing" },
5721 { "rebase-apply/applying", "Applying mailbox" },
5722 { "rebase-apply/", "Rebasing mailbox" },
5723 { "rebase-merge/interactive", "Interactive rebase" },
5724 { "rebase-merge/", "Rebase merge" },
5725 { "MERGE_HEAD", "Merging" },
5726 { "BISECT_LOG", "Bisecting" },
5727 { "HEAD", "On branch" },
5728 };
5729 char buf[SIZEOF_STR];
5730 struct stat stat;
5731 int i;
5733 if (is_initial_commit()) {
5734 string_copy(status_onbranch, "Initial commit");
5735 return;
5736 }
5738 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5739 char *head = opt_head;
5741 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5742 lstat(buf, &stat) < 0)
5743 continue;
5745 if (!*opt_head) {
5746 struct io io;
5748 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5749 io_read_buf(&io, buf, sizeof(buf))) {
5750 head = buf;
5751 if (!prefixcmp(head, "refs/heads/"))
5752 head += STRING_SIZE("refs/heads/");
5753 }
5754 }
5756 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5757 string_copy(status_onbranch, opt_head);
5758 return;
5759 }
5761 string_copy(status_onbranch, "Not currently on any branch");
5762 }
5764 /* First parse staged info using git-diff-index(1), then parse unstaged
5765 * info using git-diff-files(1), and finally untracked files using
5766 * git-ls-files(1). */
5767 static bool
5768 status_open(struct view *view)
5769 {
5770 reset_view(view);
5772 add_line_data(view, NULL, LINE_STAT_HEAD);
5773 status_update_onbranch();
5775 io_run_bg(update_index_argv);
5777 if (is_initial_commit()) {
5778 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5779 return FALSE;
5780 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5781 return FALSE;
5782 }
5784 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5785 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5786 return FALSE;
5788 /* Restore the exact position or use the specialized restore
5789 * mode? */
5790 if (!view->p_restore)
5791 status_restore(view);
5792 return TRUE;
5793 }
5795 static bool
5796 status_draw(struct view *view, struct line *line, unsigned int lineno)
5797 {
5798 struct status *status = line->data;
5799 enum line_type type;
5800 const char *text;
5802 if (!status) {
5803 switch (line->type) {
5804 case LINE_STAT_STAGED:
5805 type = LINE_STAT_SECTION;
5806 text = "Changes to be committed:";
5807 break;
5809 case LINE_STAT_UNSTAGED:
5810 type = LINE_STAT_SECTION;
5811 text = "Changed but not updated:";
5812 break;
5814 case LINE_STAT_UNTRACKED:
5815 type = LINE_STAT_SECTION;
5816 text = "Untracked files:";
5817 break;
5819 case LINE_STAT_NONE:
5820 type = LINE_DEFAULT;
5821 text = " (no files)";
5822 break;
5824 case LINE_STAT_HEAD:
5825 type = LINE_STAT_HEAD;
5826 text = status_onbranch;
5827 break;
5829 default:
5830 return FALSE;
5831 }
5832 } else {
5833 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5835 buf[0] = status->status;
5836 if (draw_text(view, line->type, buf, TRUE))
5837 return TRUE;
5838 type = LINE_DEFAULT;
5839 text = status->new.name;
5840 }
5842 draw_text(view, type, text, TRUE);
5843 return TRUE;
5844 }
5846 static enum request
5847 status_load_error(struct view *view, struct view *stage, const char *path)
5848 {
5849 if (displayed_views() == 2 || display[current_view] != view)
5850 maximize_view(view);
5851 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5852 return REQ_NONE;
5853 }
5855 static enum request
5856 status_enter(struct view *view, struct line *line)
5857 {
5858 struct status *status = line->data;
5859 const char *oldpath = status ? status->old.name : NULL;
5860 /* Diffs for unmerged entries are empty when passing the new
5861 * path, so leave it empty. */
5862 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5863 const char *info;
5864 enum open_flags split;
5865 struct view *stage = VIEW(REQ_VIEW_STAGE);
5867 if (line->type == LINE_STAT_NONE ||
5868 (!status && line[1].type == LINE_STAT_NONE)) {
5869 report("No file to diff");
5870 return REQ_NONE;
5871 }
5873 switch (line->type) {
5874 case LINE_STAT_STAGED:
5875 if (is_initial_commit()) {
5876 const char *no_head_diff_argv[] = {
5877 "git", "diff", "--no-color", "--patch-with-stat",
5878 "--", "/dev/null", newpath, NULL
5879 };
5881 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5882 return status_load_error(view, stage, newpath);
5883 } else {
5884 const char *index_show_argv[] = {
5885 "git", "diff-index", "--root", "--patch-with-stat",
5886 "-C", "-M", "--cached", "HEAD", "--",
5887 oldpath, newpath, NULL
5888 };
5890 if (!prepare_update(stage, index_show_argv, opt_cdup))
5891 return status_load_error(view, stage, newpath);
5892 }
5894 if (status)
5895 info = "Staged changes to %s";
5896 else
5897 info = "Staged changes";
5898 break;
5900 case LINE_STAT_UNSTAGED:
5901 {
5902 const char *files_show_argv[] = {
5903 "git", "diff-files", "--root", "--patch-with-stat",
5904 "-C", "-M", "--", oldpath, newpath, NULL
5905 };
5907 if (!prepare_update(stage, files_show_argv, opt_cdup))
5908 return status_load_error(view, stage, newpath);
5909 if (status)
5910 info = "Unstaged changes to %s";
5911 else
5912 info = "Unstaged changes";
5913 break;
5914 }
5915 case LINE_STAT_UNTRACKED:
5916 if (!newpath) {
5917 report("No file to show");
5918 return REQ_NONE;
5919 }
5921 if (!suffixcmp(status->new.name, -1, "/")) {
5922 report("Cannot display a directory");
5923 return REQ_NONE;
5924 }
5926 if (!prepare_update_file(stage, newpath))
5927 return status_load_error(view, stage, newpath);
5928 info = "Untracked file %s";
5929 break;
5931 case LINE_STAT_HEAD:
5932 return REQ_NONE;
5934 default:
5935 die("line type %d not handled in switch", line->type);
5936 }
5938 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5939 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5940 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5941 if (status) {
5942 stage_status = *status;
5943 } else {
5944 memset(&stage_status, 0, sizeof(stage_status));
5945 }
5947 stage_line_type = line->type;
5948 stage_chunks = 0;
5949 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5950 }
5952 return REQ_NONE;
5953 }
5955 static bool
5956 status_exists(struct status *status, enum line_type type)
5957 {
5958 struct view *view = VIEW(REQ_VIEW_STATUS);
5959 unsigned long lineno;
5961 for (lineno = 0; lineno < view->lines; lineno++) {
5962 struct line *line = &view->line[lineno];
5963 struct status *pos = line->data;
5965 if (line->type != type)
5966 continue;
5967 if (!pos && (!status || !status->status) && line[1].data) {
5968 select_view_line(view, lineno);
5969 return TRUE;
5970 }
5971 if (pos && !strcmp(status->new.name, pos->new.name)) {
5972 select_view_line(view, lineno);
5973 return TRUE;
5974 }
5975 }
5977 return FALSE;
5978 }
5981 static bool
5982 status_update_prepare(struct io *io, enum line_type type)
5983 {
5984 const char *staged_argv[] = {
5985 "git", "update-index", "-z", "--index-info", NULL
5986 };
5987 const char *others_argv[] = {
5988 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5989 };
5991 switch (type) {
5992 case LINE_STAT_STAGED:
5993 return io_run(io, IO_WR, opt_cdup, staged_argv);
5995 case LINE_STAT_UNSTAGED:
5996 case LINE_STAT_UNTRACKED:
5997 return io_run(io, IO_WR, opt_cdup, others_argv);
5999 default:
6000 die("line type %d not handled in switch", type);
6001 return FALSE;
6002 }
6003 }
6005 static bool
6006 status_update_write(struct io *io, struct status *status, enum line_type type)
6007 {
6008 char buf[SIZEOF_STR];
6009 size_t bufsize = 0;
6011 switch (type) {
6012 case LINE_STAT_STAGED:
6013 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6014 status->old.mode,
6015 status->old.rev,
6016 status->old.name, 0))
6017 return FALSE;
6018 break;
6020 case LINE_STAT_UNSTAGED:
6021 case LINE_STAT_UNTRACKED:
6022 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6023 return FALSE;
6024 break;
6026 default:
6027 die("line type %d not handled in switch", type);
6028 }
6030 return io_write(io, buf, bufsize);
6031 }
6033 static bool
6034 status_update_file(struct status *status, enum line_type type)
6035 {
6036 struct io io;
6037 bool result;
6039 if (!status_update_prepare(&io, type))
6040 return FALSE;
6042 result = status_update_write(&io, status, type);
6043 return io_done(&io) && result;
6044 }
6046 static bool
6047 status_update_files(struct view *view, struct line *line)
6048 {
6049 char buf[sizeof(view->ref)];
6050 struct io io;
6051 bool result = TRUE;
6052 struct line *pos = view->line + view->lines;
6053 int files = 0;
6054 int file, done;
6055 int cursor_y = -1, cursor_x = -1;
6057 if (!status_update_prepare(&io, line->type))
6058 return FALSE;
6060 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6061 files++;
6063 string_copy(buf, view->ref);
6064 getsyx(cursor_y, cursor_x);
6065 for (file = 0, done = 5; result && file < files; line++, file++) {
6066 int almost_done = file * 100 / files;
6068 if (almost_done > done) {
6069 done = almost_done;
6070 string_format(view->ref, "updating file %u of %u (%d%% done)",
6071 file, files, done);
6072 update_view_title(view);
6073 setsyx(cursor_y, cursor_x);
6074 doupdate();
6075 }
6076 result = status_update_write(&io, line->data, line->type);
6077 }
6078 string_copy(view->ref, buf);
6080 return io_done(&io) && result;
6081 }
6083 static bool
6084 status_update(struct view *view)
6085 {
6086 struct line *line = &view->line[view->lineno];
6088 assert(view->lines);
6090 if (!line->data) {
6091 /* This should work even for the "On branch" line. */
6092 if (line < view->line + view->lines && !line[1].data) {
6093 report("Nothing to update");
6094 return FALSE;
6095 }
6097 if (!status_update_files(view, line + 1)) {
6098 report("Failed to update file status");
6099 return FALSE;
6100 }
6102 } else if (!status_update_file(line->data, line->type)) {
6103 report("Failed to update file status");
6104 return FALSE;
6105 }
6107 return TRUE;
6108 }
6110 static bool
6111 status_revert(struct status *status, enum line_type type, bool has_none)
6112 {
6113 if (!status || type != LINE_STAT_UNSTAGED) {
6114 if (type == LINE_STAT_STAGED) {
6115 report("Cannot revert changes to staged files");
6116 } else if (type == LINE_STAT_UNTRACKED) {
6117 report("Cannot revert changes to untracked files");
6118 } else if (has_none) {
6119 report("Nothing to revert");
6120 } else {
6121 report("Cannot revert changes to multiple files");
6122 }
6124 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6125 char mode[10] = "100644";
6126 const char *reset_argv[] = {
6127 "git", "update-index", "--cacheinfo", mode,
6128 status->old.rev, status->old.name, NULL
6129 };
6130 const char *checkout_argv[] = {
6131 "git", "checkout", "--", status->old.name, NULL
6132 };
6134 if (status->status == 'U') {
6135 string_format(mode, "%5o", status->old.mode);
6137 if (status->old.mode == 0 && status->new.mode == 0) {
6138 reset_argv[2] = "--force-remove";
6139 reset_argv[3] = status->old.name;
6140 reset_argv[4] = NULL;
6141 }
6143 if (!io_run_fg(reset_argv, opt_cdup))
6144 return FALSE;
6145 if (status->old.mode == 0 && status->new.mode == 0)
6146 return TRUE;
6147 }
6149 return io_run_fg(checkout_argv, opt_cdup);
6150 }
6152 return FALSE;
6153 }
6155 static enum request
6156 status_request(struct view *view, enum request request, struct line *line)
6157 {
6158 struct status *status = line->data;
6160 switch (request) {
6161 case REQ_STATUS_UPDATE:
6162 if (!status_update(view))
6163 return REQ_NONE;
6164 break;
6166 case REQ_STATUS_REVERT:
6167 if (!status_revert(status, line->type, status_has_none(view, line)))
6168 return REQ_NONE;
6169 break;
6171 case REQ_STATUS_MERGE:
6172 if (!status || status->status != 'U') {
6173 report("Merging only possible for files with unmerged status ('U').");
6174 return REQ_NONE;
6175 }
6176 open_mergetool(status->new.name);
6177 break;
6179 case REQ_EDIT:
6180 if (!status)
6181 return request;
6182 if (status->status == 'D') {
6183 report("File has been deleted.");
6184 return REQ_NONE;
6185 }
6187 open_editor(status->new.name);
6188 break;
6190 case REQ_VIEW_BLAME:
6191 if (status)
6192 opt_ref[0] = 0;
6193 return request;
6195 case REQ_ENTER:
6196 /* After returning the status view has been split to
6197 * show the stage view. No further reloading is
6198 * necessary. */
6199 return status_enter(view, line);
6201 case REQ_REFRESH:
6202 /* Simply reload the view. */
6203 break;
6205 default:
6206 return request;
6207 }
6209 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6211 return REQ_NONE;
6212 }
6214 static void
6215 status_select(struct view *view, struct line *line)
6216 {
6217 struct status *status = line->data;
6218 char file[SIZEOF_STR] = "all files";
6219 const char *text;
6220 const char *key;
6222 if (status && !string_format(file, "'%s'", status->new.name))
6223 return;
6225 if (!status && line[1].type == LINE_STAT_NONE)
6226 line++;
6228 switch (line->type) {
6229 case LINE_STAT_STAGED:
6230 text = "Press %s to unstage %s for commit";
6231 break;
6233 case LINE_STAT_UNSTAGED:
6234 text = "Press %s to stage %s for commit";
6235 break;
6237 case LINE_STAT_UNTRACKED:
6238 text = "Press %s to stage %s for addition";
6239 break;
6241 case LINE_STAT_HEAD:
6242 case LINE_STAT_NONE:
6243 text = "Nothing to update";
6244 break;
6246 default:
6247 die("line type %d not handled in switch", line->type);
6248 }
6250 if (status && status->status == 'U') {
6251 text = "Press %s to resolve conflict in %s";
6252 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6254 } else {
6255 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6256 }
6258 string_format(view->ref, text, key, file);
6259 if (status)
6260 string_copy(opt_file, status->new.name);
6261 }
6263 static bool
6264 status_grep(struct view *view, struct line *line)
6265 {
6266 struct status *status = line->data;
6268 if (status) {
6269 const char buf[2] = { status->status, 0 };
6270 const char *text[] = { status->new.name, buf, NULL };
6272 return grep_text(view, text);
6273 }
6275 return FALSE;
6276 }
6278 static struct view_ops status_ops = {
6279 "file",
6280 NULL,
6281 status_open,
6282 NULL,
6283 status_draw,
6284 status_request,
6285 status_grep,
6286 status_select,
6287 };
6290 static bool
6291 stage_diff_write(struct io *io, struct line *line, struct line *end)
6292 {
6293 while (line < end) {
6294 if (!io_write(io, line->data, strlen(line->data)) ||
6295 !io_write(io, "\n", 1))
6296 return FALSE;
6297 line++;
6298 if (line->type == LINE_DIFF_CHUNK ||
6299 line->type == LINE_DIFF_HEADER)
6300 break;
6301 }
6303 return TRUE;
6304 }
6306 static struct line *
6307 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6308 {
6309 for (; view->line < line; line--)
6310 if (line->type == type)
6311 return line;
6313 return NULL;
6314 }
6316 static bool
6317 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6318 {
6319 const char *apply_argv[SIZEOF_ARG] = {
6320 "git", "apply", "--whitespace=nowarn", NULL
6321 };
6322 struct line *diff_hdr;
6323 struct io io;
6324 int argc = 3;
6326 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6327 if (!diff_hdr)
6328 return FALSE;
6330 if (!revert)
6331 apply_argv[argc++] = "--cached";
6332 if (revert || stage_line_type == LINE_STAT_STAGED)
6333 apply_argv[argc++] = "-R";
6334 apply_argv[argc++] = "-";
6335 apply_argv[argc++] = NULL;
6336 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6337 return FALSE;
6339 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6340 !stage_diff_write(&io, chunk, view->line + view->lines))
6341 chunk = NULL;
6343 io_done(&io);
6344 io_run_bg(update_index_argv);
6346 return chunk ? TRUE : FALSE;
6347 }
6349 static bool
6350 stage_update(struct view *view, struct line *line)
6351 {
6352 struct line *chunk = NULL;
6354 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6355 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6357 if (chunk) {
6358 if (!stage_apply_chunk(view, chunk, FALSE)) {
6359 report("Failed to apply chunk");
6360 return FALSE;
6361 }
6363 } else if (!stage_status.status) {
6364 view = VIEW(REQ_VIEW_STATUS);
6366 for (line = view->line; line < view->line + view->lines; line++)
6367 if (line->type == stage_line_type)
6368 break;
6370 if (!status_update_files(view, line + 1)) {
6371 report("Failed to update files");
6372 return FALSE;
6373 }
6375 } else if (!status_update_file(&stage_status, stage_line_type)) {
6376 report("Failed to update file");
6377 return FALSE;
6378 }
6380 return TRUE;
6381 }
6383 static bool
6384 stage_revert(struct view *view, struct line *line)
6385 {
6386 struct line *chunk = NULL;
6388 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6389 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6391 if (chunk) {
6392 if (!prompt_yesno("Are you sure you want to revert changes?"))
6393 return FALSE;
6395 if (!stage_apply_chunk(view, chunk, TRUE)) {
6396 report("Failed to revert chunk");
6397 return FALSE;
6398 }
6399 return TRUE;
6401 } else {
6402 return status_revert(stage_status.status ? &stage_status : NULL,
6403 stage_line_type, FALSE);
6404 }
6405 }
6408 static void
6409 stage_next(struct view *view, struct line *line)
6410 {
6411 int i;
6413 if (!stage_chunks) {
6414 for (line = view->line; line < view->line + view->lines; line++) {
6415 if (line->type != LINE_DIFF_CHUNK)
6416 continue;
6418 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6419 report("Allocation failure");
6420 return;
6421 }
6423 stage_chunk[stage_chunks++] = line - view->line;
6424 }
6425 }
6427 for (i = 0; i < stage_chunks; i++) {
6428 if (stage_chunk[i] > view->lineno) {
6429 do_scroll_view(view, stage_chunk[i] - view->lineno);
6430 report("Chunk %d of %d", i + 1, stage_chunks);
6431 return;
6432 }
6433 }
6435 report("No next chunk found");
6436 }
6438 static enum request
6439 stage_request(struct view *view, enum request request, struct line *line)
6440 {
6441 switch (request) {
6442 case REQ_STATUS_UPDATE:
6443 if (!stage_update(view, line))
6444 return REQ_NONE;
6445 break;
6447 case REQ_STATUS_REVERT:
6448 if (!stage_revert(view, line))
6449 return REQ_NONE;
6450 break;
6452 case REQ_STAGE_NEXT:
6453 if (stage_line_type == LINE_STAT_UNTRACKED) {
6454 report("File is untracked; press %s to add",
6455 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6456 return REQ_NONE;
6457 }
6458 stage_next(view, line);
6459 return REQ_NONE;
6461 case REQ_EDIT:
6462 if (!stage_status.new.name[0])
6463 return request;
6464 if (stage_status.status == 'D') {
6465 report("File has been deleted.");
6466 return REQ_NONE;
6467 }
6469 open_editor(stage_status.new.name);
6470 break;
6472 case REQ_REFRESH:
6473 /* Reload everything ... */
6474 break;
6476 case REQ_VIEW_BLAME:
6477 if (stage_status.new.name[0]) {
6478 string_copy(opt_file, stage_status.new.name);
6479 opt_ref[0] = 0;
6480 }
6481 return request;
6483 case REQ_ENTER:
6484 return pager_request(view, request, line);
6486 default:
6487 return request;
6488 }
6490 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6491 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6493 /* Check whether the staged entry still exists, and close the
6494 * stage view if it doesn't. */
6495 if (!status_exists(&stage_status, stage_line_type)) {
6496 status_restore(VIEW(REQ_VIEW_STATUS));
6497 return REQ_VIEW_CLOSE;
6498 }
6500 if (stage_line_type == LINE_STAT_UNTRACKED) {
6501 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6502 report("Cannot display a directory");
6503 return REQ_NONE;
6504 }
6506 if (!prepare_update_file(view, stage_status.new.name)) {
6507 report("Failed to open file: %s", strerror(errno));
6508 return REQ_NONE;
6509 }
6510 }
6511 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6513 return REQ_NONE;
6514 }
6516 static struct view_ops stage_ops = {
6517 "line",
6518 NULL,
6519 NULL,
6520 pager_read,
6521 pager_draw,
6522 stage_request,
6523 pager_grep,
6524 pager_select,
6525 };
6528 /*
6529 * Revision graph
6530 */
6532 struct commit {
6533 char id[SIZEOF_REV]; /* SHA1 ID. */
6534 char title[128]; /* First line of the commit message. */
6535 const char *author; /* Author of the commit. */
6536 struct time time; /* Date from the author ident. */
6537 struct ref_list *refs; /* Repository references. */
6538 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6539 size_t graph_size; /* The width of the graph array. */
6540 bool has_parents; /* Rewritten --parents seen. */
6541 };
6543 /* Size of rev graph with no "padding" columns */
6544 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6546 struct rev_graph {
6547 struct rev_graph *prev, *next, *parents;
6548 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6549 size_t size;
6550 struct commit *commit;
6551 size_t pos;
6552 unsigned int boundary:1;
6553 };
6555 /* Parents of the commit being visualized. */
6556 static struct rev_graph graph_parents[4];
6558 /* The current stack of revisions on the graph. */
6559 static struct rev_graph graph_stacks[4] = {
6560 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6561 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6562 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6563 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6564 };
6566 static inline bool
6567 graph_parent_is_merge(struct rev_graph *graph)
6568 {
6569 return graph->parents->size > 1;
6570 }
6572 static inline void
6573 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6574 {
6575 struct commit *commit = graph->commit;
6577 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6578 commit->graph[commit->graph_size++] = symbol;
6579 }
6581 static void
6582 clear_rev_graph(struct rev_graph *graph)
6583 {
6584 graph->boundary = 0;
6585 graph->size = graph->pos = 0;
6586 graph->commit = NULL;
6587 memset(graph->parents, 0, sizeof(*graph->parents));
6588 }
6590 static void
6591 done_rev_graph(struct rev_graph *graph)
6592 {
6593 if (graph_parent_is_merge(graph) &&
6594 graph->pos < graph->size - 1 &&
6595 graph->next->size == graph->size + graph->parents->size - 1) {
6596 size_t i = graph->pos + graph->parents->size - 1;
6598 graph->commit->graph_size = i * 2;
6599 while (i < graph->next->size - 1) {
6600 append_to_rev_graph(graph, ' ');
6601 append_to_rev_graph(graph, '\\');
6602 i++;
6603 }
6604 }
6606 clear_rev_graph(graph);
6607 }
6609 static void
6610 push_rev_graph(struct rev_graph *graph, const char *parent)
6611 {
6612 int i;
6614 /* "Collapse" duplicate parents lines.
6615 *
6616 * FIXME: This needs to also update update the drawn graph but
6617 * for now it just serves as a method for pruning graph lines. */
6618 for (i = 0; i < graph->size; i++)
6619 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6620 return;
6622 if (graph->size < SIZEOF_REVITEMS) {
6623 string_copy_rev(graph->rev[graph->size++], parent);
6624 }
6625 }
6627 static chtype
6628 get_rev_graph_symbol(struct rev_graph *graph)
6629 {
6630 chtype symbol;
6632 if (graph->boundary)
6633 symbol = REVGRAPH_BOUND;
6634 else if (graph->parents->size == 0)
6635 symbol = REVGRAPH_INIT;
6636 else if (graph_parent_is_merge(graph))
6637 symbol = REVGRAPH_MERGE;
6638 else if (graph->pos >= graph->size)
6639 symbol = REVGRAPH_BRANCH;
6640 else
6641 symbol = REVGRAPH_COMMIT;
6643 return symbol;
6644 }
6646 static void
6647 draw_rev_graph(struct rev_graph *graph)
6648 {
6649 struct rev_filler {
6650 chtype separator, line;
6651 };
6652 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6653 static struct rev_filler fillers[] = {
6654 { ' ', '|' },
6655 { '`', '.' },
6656 { '\'', ' ' },
6657 { '/', ' ' },
6658 };
6659 chtype symbol = get_rev_graph_symbol(graph);
6660 struct rev_filler *filler;
6661 size_t i;
6663 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6664 filler = &fillers[DEFAULT];
6666 for (i = 0; i < graph->pos; i++) {
6667 append_to_rev_graph(graph, filler->line);
6668 if (graph_parent_is_merge(graph->prev) &&
6669 graph->prev->pos == i)
6670 filler = &fillers[RSHARP];
6672 append_to_rev_graph(graph, filler->separator);
6673 }
6675 /* Place the symbol for this revision. */
6676 append_to_rev_graph(graph, symbol);
6678 if (graph->prev->size > graph->size)
6679 filler = &fillers[RDIAG];
6680 else
6681 filler = &fillers[DEFAULT];
6683 i++;
6685 for (; i < graph->size; i++) {
6686 append_to_rev_graph(graph, filler->separator);
6687 append_to_rev_graph(graph, filler->line);
6688 if (graph_parent_is_merge(graph->prev) &&
6689 i < graph->prev->pos + graph->parents->size)
6690 filler = &fillers[RSHARP];
6691 if (graph->prev->size > graph->size)
6692 filler = &fillers[LDIAG];
6693 }
6695 if (graph->prev->size > graph->size) {
6696 append_to_rev_graph(graph, filler->separator);
6697 if (filler->line != ' ')
6698 append_to_rev_graph(graph, filler->line);
6699 }
6700 }
6702 /* Prepare the next rev graph */
6703 static void
6704 prepare_rev_graph(struct rev_graph *graph)
6705 {
6706 size_t i;
6708 /* First, traverse all lines of revisions up to the active one. */
6709 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6710 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6711 break;
6713 push_rev_graph(graph->next, graph->rev[graph->pos]);
6714 }
6716 /* Interleave the new revision parent(s). */
6717 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6718 push_rev_graph(graph->next, graph->parents->rev[i]);
6720 /* Lastly, put any remaining revisions. */
6721 for (i = graph->pos + 1; i < graph->size; i++)
6722 push_rev_graph(graph->next, graph->rev[i]);
6723 }
6725 static void
6726 update_rev_graph(struct view *view, struct rev_graph *graph)
6727 {
6728 /* If this is the finalizing update ... */
6729 if (graph->commit)
6730 prepare_rev_graph(graph);
6732 /* Graph visualization needs a one rev look-ahead,
6733 * so the first update doesn't visualize anything. */
6734 if (!graph->prev->commit)
6735 return;
6737 if (view->lines > 2)
6738 view->line[view->lines - 3].dirty = 1;
6739 if (view->lines > 1)
6740 view->line[view->lines - 2].dirty = 1;
6741 draw_rev_graph(graph->prev);
6742 done_rev_graph(graph->prev->prev);
6743 }
6746 /*
6747 * Main view backend
6748 */
6750 static const char *main_argv[SIZEOF_ARG] = {
6751 "git", "log", "--no-color", "--pretty=raw", "--parents",
6752 "--topo-order", "%(diffargs)", "%(revargs)",
6753 "--", "%(fileargs)", NULL
6754 };
6756 static bool
6757 main_draw(struct view *view, struct line *line, unsigned int lineno)
6758 {
6759 struct commit *commit = line->data;
6761 if (!commit->author)
6762 return FALSE;
6764 if (opt_date && draw_date(view, &commit->time))
6765 return TRUE;
6767 if (opt_author && draw_author(view, commit->author))
6768 return TRUE;
6770 if (opt_rev_graph && commit->graph_size &&
6771 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6772 return TRUE;
6774 if (opt_show_refs && commit->refs) {
6775 size_t i;
6777 for (i = 0; i < commit->refs->size; i++) {
6778 struct ref *ref = commit->refs->refs[i];
6779 enum line_type type;
6781 if (ref->head)
6782 type = LINE_MAIN_HEAD;
6783 else if (ref->ltag)
6784 type = LINE_MAIN_LOCAL_TAG;
6785 else if (ref->tag)
6786 type = LINE_MAIN_TAG;
6787 else if (ref->tracked)
6788 type = LINE_MAIN_TRACKED;
6789 else if (ref->remote)
6790 type = LINE_MAIN_REMOTE;
6791 else
6792 type = LINE_MAIN_REF;
6794 if (draw_text(view, type, "[", TRUE) ||
6795 draw_text(view, type, ref->name, TRUE) ||
6796 draw_text(view, type, "]", TRUE))
6797 return TRUE;
6799 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6800 return TRUE;
6801 }
6802 }
6804 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6805 return TRUE;
6806 }
6808 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6809 static bool
6810 main_read(struct view *view, char *line)
6811 {
6812 static struct rev_graph *graph = graph_stacks;
6813 enum line_type type;
6814 struct commit *commit;
6816 if (!line) {
6817 int i;
6819 if (!view->lines && !view->prev)
6820 die("No revisions match the given arguments.");
6821 if (view->lines > 0) {
6822 commit = view->line[view->lines - 1].data;
6823 view->line[view->lines - 1].dirty = 1;
6824 if (!commit->author) {
6825 view->lines--;
6826 free(commit);
6827 graph->commit = NULL;
6828 }
6829 }
6830 update_rev_graph(view, graph);
6832 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6833 clear_rev_graph(&graph_stacks[i]);
6834 return TRUE;
6835 }
6837 type = get_line_type(line);
6838 if (type == LINE_COMMIT) {
6839 commit = calloc(1, sizeof(struct commit));
6840 if (!commit)
6841 return FALSE;
6843 line += STRING_SIZE("commit ");
6844 if (*line == '-') {
6845 graph->boundary = 1;
6846 line++;
6847 }
6849 string_copy_rev(commit->id, line);
6850 commit->refs = get_ref_list(commit->id);
6851 graph->commit = commit;
6852 add_line_data(view, commit, LINE_MAIN_COMMIT);
6854 while ((line = strchr(line, ' '))) {
6855 line++;
6856 push_rev_graph(graph->parents, line);
6857 commit->has_parents = TRUE;
6858 }
6859 return TRUE;
6860 }
6862 if (!view->lines)
6863 return TRUE;
6864 commit = view->line[view->lines - 1].data;
6866 switch (type) {
6867 case LINE_PARENT:
6868 if (commit->has_parents)
6869 break;
6870 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6871 break;
6873 case LINE_AUTHOR:
6874 parse_author_line(line + STRING_SIZE("author "),
6875 &commit->author, &commit->time);
6876 update_rev_graph(view, graph);
6877 graph = graph->next;
6878 break;
6880 default:
6881 /* Fill in the commit title if it has not already been set. */
6882 if (commit->title[0])
6883 break;
6885 /* Require titles to start with a non-space character at the
6886 * offset used by git log. */
6887 if (strncmp(line, " ", 4))
6888 break;
6889 line += 4;
6890 /* Well, if the title starts with a whitespace character,
6891 * try to be forgiving. Otherwise we end up with no title. */
6892 while (isspace(*line))
6893 line++;
6894 if (*line == '\0')
6895 break;
6896 /* FIXME: More graceful handling of titles; append "..." to
6897 * shortened titles, etc. */
6899 string_expand(commit->title, sizeof(commit->title), line, 1);
6900 view->line[view->lines - 1].dirty = 1;
6901 }
6903 return TRUE;
6904 }
6906 static enum request
6907 main_request(struct view *view, enum request request, struct line *line)
6908 {
6909 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6911 switch (request) {
6912 case REQ_ENTER:
6913 open_view(view, REQ_VIEW_DIFF, flags);
6914 break;
6915 case REQ_REFRESH:
6916 load_refs();
6917 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6918 break;
6919 default:
6920 return request;
6921 }
6923 return REQ_NONE;
6924 }
6926 static bool
6927 grep_refs(struct ref_list *list, regex_t *regex)
6928 {
6929 regmatch_t pmatch;
6930 size_t i;
6932 if (!opt_show_refs || !list)
6933 return FALSE;
6935 for (i = 0; i < list->size; i++) {
6936 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6937 return TRUE;
6938 }
6940 return FALSE;
6941 }
6943 static bool
6944 main_grep(struct view *view, struct line *line)
6945 {
6946 struct commit *commit = line->data;
6947 const char *text[] = {
6948 commit->title,
6949 opt_author ? commit->author : "",
6950 mkdate(&commit->time, opt_date),
6951 NULL
6952 };
6954 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6955 }
6957 static void
6958 main_select(struct view *view, struct line *line)
6959 {
6960 struct commit *commit = line->data;
6962 string_copy_rev(view->ref, commit->id);
6963 string_copy_rev(ref_commit, view->ref);
6964 }
6966 static struct view_ops main_ops = {
6967 "commit",
6968 main_argv,
6969 NULL,
6970 main_read,
6971 main_draw,
6972 main_request,
6973 main_grep,
6974 main_select,
6975 };
6978 /*
6979 * Status management
6980 */
6982 /* Whether or not the curses interface has been initialized. */
6983 static bool cursed = FALSE;
6985 /* Terminal hacks and workarounds. */
6986 static bool use_scroll_redrawwin;
6987 static bool use_scroll_status_wclear;
6989 /* The status window is used for polling keystrokes. */
6990 static WINDOW *status_win;
6992 /* Reading from the prompt? */
6993 static bool input_mode = FALSE;
6995 static bool status_empty = FALSE;
6997 /* Update status and title window. */
6998 static void
6999 report(const char *msg, ...)
7000 {
7001 struct view *view = display[current_view];
7003 if (input_mode)
7004 return;
7006 if (!view) {
7007 char buf[SIZEOF_STR];
7008 va_list args;
7010 va_start(args, msg);
7011 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7012 buf[sizeof(buf) - 1] = 0;
7013 buf[sizeof(buf) - 2] = '.';
7014 buf[sizeof(buf) - 3] = '.';
7015 buf[sizeof(buf) - 4] = '.';
7016 }
7017 va_end(args);
7018 die("%s", buf);
7019 }
7021 if (!status_empty || *msg) {
7022 va_list args;
7024 va_start(args, msg);
7026 wmove(status_win, 0, 0);
7027 if (view->has_scrolled && use_scroll_status_wclear)
7028 wclear(status_win);
7029 if (*msg) {
7030 vwprintw(status_win, msg, args);
7031 status_empty = FALSE;
7032 } else {
7033 status_empty = TRUE;
7034 }
7035 wclrtoeol(status_win);
7036 wnoutrefresh(status_win);
7038 va_end(args);
7039 }
7041 update_view_title(view);
7042 }
7044 static void
7045 init_display(void)
7046 {
7047 const char *term;
7048 int x, y;
7050 /* Initialize the curses library */
7051 if (isatty(STDIN_FILENO)) {
7052 cursed = !!initscr();
7053 opt_tty = stdin;
7054 } else {
7055 /* Leave stdin and stdout alone when acting as a pager. */
7056 opt_tty = fopen("/dev/tty", "r+");
7057 if (!opt_tty)
7058 die("Failed to open /dev/tty");
7059 cursed = !!newterm(NULL, opt_tty, opt_tty);
7060 }
7062 if (!cursed)
7063 die("Failed to initialize curses");
7065 nonl(); /* Disable conversion and detect newlines from input. */
7066 cbreak(); /* Take input chars one at a time, no wait for \n */
7067 noecho(); /* Don't echo input */
7068 leaveok(stdscr, FALSE);
7070 if (has_colors())
7071 init_colors();
7073 getmaxyx(stdscr, y, x);
7074 status_win = newwin(1, 0, y - 1, 0);
7075 if (!status_win)
7076 die("Failed to create status window");
7078 /* Enable keyboard mapping */
7079 keypad(status_win, TRUE);
7080 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7082 TABSIZE = opt_tab_size;
7084 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7085 if (term && !strcmp(term, "gnome-terminal")) {
7086 /* In the gnome-terminal-emulator, the message from
7087 * scrolling up one line when impossible followed by
7088 * scrolling down one line causes corruption of the
7089 * status line. This is fixed by calling wclear. */
7090 use_scroll_status_wclear = TRUE;
7091 use_scroll_redrawwin = FALSE;
7093 } else if (term && !strcmp(term, "xrvt-xpm")) {
7094 /* No problems with full optimizations in xrvt-(unicode)
7095 * and aterm. */
7096 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7098 } else {
7099 /* When scrolling in (u)xterm the last line in the
7100 * scrolling direction will update slowly. */
7101 use_scroll_redrawwin = TRUE;
7102 use_scroll_status_wclear = FALSE;
7103 }
7104 }
7106 static int
7107 get_input(int prompt_position)
7108 {
7109 struct view *view;
7110 int i, key, cursor_y, cursor_x;
7112 if (prompt_position)
7113 input_mode = TRUE;
7115 while (TRUE) {
7116 bool loading = FALSE;
7118 foreach_view (view, i) {
7119 update_view(view);
7120 if (view_is_displayed(view) && view->has_scrolled &&
7121 use_scroll_redrawwin)
7122 redrawwin(view->win);
7123 view->has_scrolled = FALSE;
7124 if (view->pipe)
7125 loading = TRUE;
7126 }
7128 /* Update the cursor position. */
7129 if (prompt_position) {
7130 getbegyx(status_win, cursor_y, cursor_x);
7131 cursor_x = prompt_position;
7132 } else {
7133 view = display[current_view];
7134 getbegyx(view->win, cursor_y, cursor_x);
7135 cursor_x = view->width - 1;
7136 cursor_y += view->lineno - view->offset;
7137 }
7138 setsyx(cursor_y, cursor_x);
7140 /* Refresh, accept single keystroke of input */
7141 doupdate();
7142 nodelay(status_win, loading);
7143 key = wgetch(status_win);
7145 /* wgetch() with nodelay() enabled returns ERR when
7146 * there's no input. */
7147 if (key == ERR) {
7149 } else if (key == KEY_RESIZE) {
7150 int height, width;
7152 getmaxyx(stdscr, height, width);
7154 wresize(status_win, 1, width);
7155 mvwin(status_win, height - 1, 0);
7156 wnoutrefresh(status_win);
7157 resize_display();
7158 redraw_display(TRUE);
7160 } else {
7161 input_mode = FALSE;
7162 return key;
7163 }
7164 }
7165 }
7167 static char *
7168 prompt_input(const char *prompt, input_handler handler, void *data)
7169 {
7170 enum input_status status = INPUT_OK;
7171 static char buf[SIZEOF_STR];
7172 size_t pos = 0;
7174 buf[pos] = 0;
7176 while (status == INPUT_OK || status == INPUT_SKIP) {
7177 int key;
7179 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7180 wclrtoeol(status_win);
7182 key = get_input(pos + 1);
7183 switch (key) {
7184 case KEY_RETURN:
7185 case KEY_ENTER:
7186 case '\n':
7187 status = pos ? INPUT_STOP : INPUT_CANCEL;
7188 break;
7190 case KEY_BACKSPACE:
7191 if (pos > 0)
7192 buf[--pos] = 0;
7193 else
7194 status = INPUT_CANCEL;
7195 break;
7197 case KEY_ESC:
7198 status = INPUT_CANCEL;
7199 break;
7201 default:
7202 if (pos >= sizeof(buf)) {
7203 report("Input string too long");
7204 return NULL;
7205 }
7207 status = handler(data, buf, key);
7208 if (status == INPUT_OK)
7209 buf[pos++] = (char) key;
7210 }
7211 }
7213 /* Clear the status window */
7214 status_empty = FALSE;
7215 report("");
7217 if (status == INPUT_CANCEL)
7218 return NULL;
7220 buf[pos++] = 0;
7222 return buf;
7223 }
7225 static enum input_status
7226 prompt_yesno_handler(void *data, char *buf, int c)
7227 {
7228 if (c == 'y' || c == 'Y')
7229 return INPUT_STOP;
7230 if (c == 'n' || c == 'N')
7231 return INPUT_CANCEL;
7232 return INPUT_SKIP;
7233 }
7235 static bool
7236 prompt_yesno(const char *prompt)
7237 {
7238 char prompt2[SIZEOF_STR];
7240 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7241 return FALSE;
7243 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7244 }
7246 static enum input_status
7247 read_prompt_handler(void *data, char *buf, int c)
7248 {
7249 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7250 }
7252 static char *
7253 read_prompt(const char *prompt)
7254 {
7255 return prompt_input(prompt, read_prompt_handler, NULL);
7256 }
7258 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7259 {
7260 enum input_status status = INPUT_OK;
7261 int size = 0;
7263 while (items[size].text)
7264 size++;
7266 while (status == INPUT_OK) {
7267 const struct menu_item *item = &items[*selected];
7268 int key;
7269 int i;
7271 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7272 prompt, *selected + 1, size);
7273 if (item->hotkey)
7274 wprintw(status_win, "[%c] ", (char) item->hotkey);
7275 wprintw(status_win, "%s", item->text);
7276 wclrtoeol(status_win);
7278 key = get_input(COLS - 1);
7279 switch (key) {
7280 case KEY_RETURN:
7281 case KEY_ENTER:
7282 case '\n':
7283 status = INPUT_STOP;
7284 break;
7286 case KEY_LEFT:
7287 case KEY_UP:
7288 *selected = *selected - 1;
7289 if (*selected < 0)
7290 *selected = size - 1;
7291 break;
7293 case KEY_RIGHT:
7294 case KEY_DOWN:
7295 *selected = (*selected + 1) % size;
7296 break;
7298 case KEY_ESC:
7299 status = INPUT_CANCEL;
7300 break;
7302 default:
7303 for (i = 0; items[i].text; i++)
7304 if (items[i].hotkey == key) {
7305 *selected = i;
7306 status = INPUT_STOP;
7307 break;
7308 }
7309 }
7310 }
7312 /* Clear the status window */
7313 status_empty = FALSE;
7314 report("");
7316 return status != INPUT_CANCEL;
7317 }
7319 /*
7320 * Repository properties
7321 */
7323 static struct ref **refs = NULL;
7324 static size_t refs_size = 0;
7325 static struct ref *refs_head = NULL;
7327 static struct ref_list **ref_lists = NULL;
7328 static size_t ref_lists_size = 0;
7330 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7331 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7332 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7334 static int
7335 compare_refs(const void *ref1_, const void *ref2_)
7336 {
7337 const struct ref *ref1 = *(const struct ref **)ref1_;
7338 const struct ref *ref2 = *(const struct ref **)ref2_;
7340 if (ref1->tag != ref2->tag)
7341 return ref2->tag - ref1->tag;
7342 if (ref1->ltag != ref2->ltag)
7343 return ref2->ltag - ref2->ltag;
7344 if (ref1->head != ref2->head)
7345 return ref2->head - ref1->head;
7346 if (ref1->tracked != ref2->tracked)
7347 return ref2->tracked - ref1->tracked;
7348 if (ref1->remote != ref2->remote)
7349 return ref2->remote - ref1->remote;
7350 return strcmp(ref1->name, ref2->name);
7351 }
7353 static void
7354 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7355 {
7356 size_t i;
7358 for (i = 0; i < refs_size; i++)
7359 if (!visitor(data, refs[i]))
7360 break;
7361 }
7363 static struct ref *
7364 get_ref_head()
7365 {
7366 return refs_head;
7367 }
7369 static struct ref_list *
7370 get_ref_list(const char *id)
7371 {
7372 struct ref_list *list;
7373 size_t i;
7375 for (i = 0; i < ref_lists_size; i++)
7376 if (!strcmp(id, ref_lists[i]->id))
7377 return ref_lists[i];
7379 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7380 return NULL;
7381 list = calloc(1, sizeof(*list));
7382 if (!list)
7383 return NULL;
7385 for (i = 0; i < refs_size; i++) {
7386 if (!strcmp(id, refs[i]->id) &&
7387 realloc_refs_list(&list->refs, list->size, 1))
7388 list->refs[list->size++] = refs[i];
7389 }
7391 if (!list->refs) {
7392 free(list);
7393 return NULL;
7394 }
7396 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7397 ref_lists[ref_lists_size++] = list;
7398 return list;
7399 }
7401 static int
7402 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7403 {
7404 struct ref *ref = NULL;
7405 bool tag = FALSE;
7406 bool ltag = FALSE;
7407 bool remote = FALSE;
7408 bool tracked = FALSE;
7409 bool head = FALSE;
7410 int from = 0, to = refs_size - 1;
7412 if (!prefixcmp(name, "refs/tags/")) {
7413 if (!suffixcmp(name, namelen, "^{}")) {
7414 namelen -= 3;
7415 name[namelen] = 0;
7416 } else {
7417 ltag = TRUE;
7418 }
7420 tag = TRUE;
7421 namelen -= STRING_SIZE("refs/tags/");
7422 name += STRING_SIZE("refs/tags/");
7424 } else if (!prefixcmp(name, "refs/remotes/")) {
7425 remote = TRUE;
7426 namelen -= STRING_SIZE("refs/remotes/");
7427 name += STRING_SIZE("refs/remotes/");
7428 tracked = !strcmp(opt_remote, name);
7430 } else if (!prefixcmp(name, "refs/heads/")) {
7431 namelen -= STRING_SIZE("refs/heads/");
7432 name += STRING_SIZE("refs/heads/");
7433 if (!strncmp(opt_head, name, namelen))
7434 return OK;
7436 } else if (!strcmp(name, "HEAD")) {
7437 head = TRUE;
7438 if (*opt_head) {
7439 namelen = strlen(opt_head);
7440 name = opt_head;
7441 }
7442 }
7444 /* If we are reloading or it's an annotated tag, replace the
7445 * previous SHA1 with the resolved commit id; relies on the fact
7446 * git-ls-remote lists the commit id of an annotated tag right
7447 * before the commit id it points to. */
7448 while (from <= to) {
7449 size_t pos = (to + from) / 2;
7450 int cmp = strcmp(name, refs[pos]->name);
7452 if (!cmp) {
7453 ref = refs[pos];
7454 break;
7455 }
7457 if (cmp < 0)
7458 to = pos - 1;
7459 else
7460 from = pos + 1;
7461 }
7463 if (!ref) {
7464 if (!realloc_refs(&refs, refs_size, 1))
7465 return ERR;
7466 ref = calloc(1, sizeof(*ref) + namelen);
7467 if (!ref)
7468 return ERR;
7469 memmove(refs + from + 1, refs + from,
7470 (refs_size - from) * sizeof(*refs));
7471 refs[from] = ref;
7472 strncpy(ref->name, name, namelen);
7473 refs_size++;
7474 }
7476 ref->head = head;
7477 ref->tag = tag;
7478 ref->ltag = ltag;
7479 ref->remote = remote;
7480 ref->tracked = tracked;
7481 string_copy_rev(ref->id, id);
7483 if (head)
7484 refs_head = ref;
7485 return OK;
7486 }
7488 static int
7489 load_refs(void)
7490 {
7491 const char *head_argv[] = {
7492 "git", "symbolic-ref", "HEAD", NULL
7493 };
7494 static const char *ls_remote_argv[SIZEOF_ARG] = {
7495 "git", "ls-remote", opt_git_dir, NULL
7496 };
7497 static bool init = FALSE;
7498 size_t i;
7500 if (!init) {
7501 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7502 die("TIG_LS_REMOTE contains too many arguments");
7503 init = TRUE;
7504 }
7506 if (!*opt_git_dir)
7507 return OK;
7509 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7510 !prefixcmp(opt_head, "refs/heads/")) {
7511 char *offset = opt_head + STRING_SIZE("refs/heads/");
7513 memmove(opt_head, offset, strlen(offset) + 1);
7514 }
7516 refs_head = NULL;
7517 for (i = 0; i < refs_size; i++)
7518 refs[i]->id[0] = 0;
7520 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7521 return ERR;
7523 /* Update the ref lists to reflect changes. */
7524 for (i = 0; i < ref_lists_size; i++) {
7525 struct ref_list *list = ref_lists[i];
7526 size_t old, new;
7528 for (old = new = 0; old < list->size; old++)
7529 if (!strcmp(list->id, list->refs[old]->id))
7530 list->refs[new++] = list->refs[old];
7531 list->size = new;
7532 }
7534 return OK;
7535 }
7537 static void
7538 set_remote_branch(const char *name, const char *value, size_t valuelen)
7539 {
7540 if (!strcmp(name, ".remote")) {
7541 string_ncopy(opt_remote, value, valuelen);
7543 } else if (*opt_remote && !strcmp(name, ".merge")) {
7544 size_t from = strlen(opt_remote);
7546 if (!prefixcmp(value, "refs/heads/"))
7547 value += STRING_SIZE("refs/heads/");
7549 if (!string_format_from(opt_remote, &from, "/%s", value))
7550 opt_remote[0] = 0;
7551 }
7552 }
7554 static void
7555 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7556 {
7557 const char *argv[SIZEOF_ARG] = { name, "=" };
7558 int argc = 1 + (cmd == option_set_command);
7559 int error = ERR;
7561 if (!argv_from_string(argv, &argc, value))
7562 config_msg = "Too many option arguments";
7563 else
7564 error = cmd(argc, argv);
7566 if (error == ERR)
7567 warn("Option 'tig.%s': %s", name, config_msg);
7568 }
7570 static bool
7571 set_environment_variable(const char *name, const char *value)
7572 {
7573 size_t len = strlen(name) + 1 + strlen(value) + 1;
7574 char *env = malloc(len);
7576 if (env &&
7577 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7578 putenv(env) == 0)
7579 return TRUE;
7580 free(env);
7581 return FALSE;
7582 }
7584 static void
7585 set_work_tree(const char *value)
7586 {
7587 char cwd[SIZEOF_STR];
7589 if (!getcwd(cwd, sizeof(cwd)))
7590 die("Failed to get cwd path: %s", strerror(errno));
7591 if (chdir(opt_git_dir) < 0)
7592 die("Failed to chdir(%s): %s", strerror(errno));
7593 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7594 die("Failed to get git path: %s", strerror(errno));
7595 if (chdir(cwd) < 0)
7596 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7597 if (chdir(value) < 0)
7598 die("Failed to chdir(%s): %s", value, strerror(errno));
7599 if (!getcwd(cwd, sizeof(cwd)))
7600 die("Failed to get cwd path: %s", strerror(errno));
7601 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7602 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7603 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7604 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7605 opt_is_inside_work_tree = TRUE;
7606 }
7608 static int
7609 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7610 {
7611 if (!strcmp(name, "i18n.commitencoding"))
7612 string_ncopy(opt_encoding, value, valuelen);
7614 else if (!strcmp(name, "core.editor"))
7615 string_ncopy(opt_editor, value, valuelen);
7617 else if (!strcmp(name, "core.worktree"))
7618 set_work_tree(value);
7620 else if (!prefixcmp(name, "tig.color."))
7621 set_repo_config_option(name + 10, value, option_color_command);
7623 else if (!prefixcmp(name, "tig.bind."))
7624 set_repo_config_option(name + 9, value, option_bind_command);
7626 else if (!prefixcmp(name, "tig."))
7627 set_repo_config_option(name + 4, value, option_set_command);
7629 else if (*opt_head && !prefixcmp(name, "branch.") &&
7630 !strncmp(name + 7, opt_head, strlen(opt_head)))
7631 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7633 return OK;
7634 }
7636 static int
7637 load_git_config(void)
7638 {
7639 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7641 return io_run_load(config_list_argv, "=", read_repo_config_option);
7642 }
7644 static int
7645 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7646 {
7647 if (!opt_git_dir[0]) {
7648 string_ncopy(opt_git_dir, name, namelen);
7650 } else if (opt_is_inside_work_tree == -1) {
7651 /* This can be 3 different values depending on the
7652 * version of git being used. If git-rev-parse does not
7653 * understand --is-inside-work-tree it will simply echo
7654 * the option else either "true" or "false" is printed.
7655 * Default to true for the unknown case. */
7656 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7658 } else if (*name == '.') {
7659 string_ncopy(opt_cdup, name, namelen);
7661 } else {
7662 string_ncopy(opt_prefix, name, namelen);
7663 }
7665 return OK;
7666 }
7668 static int
7669 load_repo_info(void)
7670 {
7671 const char *rev_parse_argv[] = {
7672 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7673 "--show-cdup", "--show-prefix", NULL
7674 };
7676 return io_run_load(rev_parse_argv, "=", read_repo_info);
7677 }
7680 /*
7681 * Main
7682 */
7684 static const char usage[] =
7685 "tig " TIG_VERSION " (" __DATE__ ")\n"
7686 "\n"
7687 "Usage: tig [options] [revs] [--] [paths]\n"
7688 " or: tig show [options] [revs] [--] [paths]\n"
7689 " or: tig blame [rev] path\n"
7690 " or: tig status\n"
7691 " or: tig < [git command output]\n"
7692 "\n"
7693 "Options:\n"
7694 " -v, --version Show version and exit\n"
7695 " -h, --help Show help message and exit";
7697 static void __NORETURN
7698 quit(int sig)
7699 {
7700 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7701 if (cursed)
7702 endwin();
7703 exit(0);
7704 }
7706 static void __NORETURN
7707 die(const char *err, ...)
7708 {
7709 va_list args;
7711 endwin();
7713 va_start(args, err);
7714 fputs("tig: ", stderr);
7715 vfprintf(stderr, err, args);
7716 fputs("\n", stderr);
7717 va_end(args);
7719 exit(1);
7720 }
7722 static void
7723 warn(const char *msg, ...)
7724 {
7725 va_list args;
7727 va_start(args, msg);
7728 fputs("tig warning: ", stderr);
7729 vfprintf(stderr, msg, args);
7730 fputs("\n", stderr);
7731 va_end(args);
7732 }
7734 static const char ***filter_args;
7736 static int
7737 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7738 {
7739 return argv_append(filter_args, name) ? OK : ERR;
7740 }
7742 static void
7743 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7744 {
7745 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7746 const char **all_argv = NULL;
7748 filter_args = args;
7749 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7750 !argv_append_array(&all_argv, argv) ||
7751 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7752 die("Failed to split arguments");
7753 argv_free(all_argv);
7754 free(all_argv);
7755 }
7757 static void
7758 filter_options(const char *argv[])
7759 {
7760 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7761 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7762 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7763 }
7765 static enum request
7766 parse_options(int argc, const char *argv[])
7767 {
7768 enum request request = REQ_VIEW_MAIN;
7769 const char *subcommand;
7770 bool seen_dashdash = FALSE;
7771 const char **filter_argv = NULL;
7772 int i;
7774 if (!isatty(STDIN_FILENO))
7775 return REQ_VIEW_PAGER;
7777 if (argc <= 1)
7778 return REQ_VIEW_MAIN;
7780 subcommand = argv[1];
7781 if (!strcmp(subcommand, "status")) {
7782 if (argc > 2)
7783 warn("ignoring arguments after `%s'", subcommand);
7784 return REQ_VIEW_STATUS;
7786 } else if (!strcmp(subcommand, "blame")) {
7787 if (argc <= 2 || argc > 4)
7788 die("invalid number of options to blame\n\n%s", usage);
7790 i = 2;
7791 if (argc == 4) {
7792 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7793 i++;
7794 }
7796 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7797 return REQ_VIEW_BLAME;
7799 } else if (!strcmp(subcommand, "show")) {
7800 request = REQ_VIEW_DIFF;
7802 } else {
7803 subcommand = NULL;
7804 }
7806 for (i = 1 + !!subcommand; i < argc; i++) {
7807 const char *opt = argv[i];
7809 if (seen_dashdash) {
7810 argv_append(&opt_file_args, opt);
7811 continue;
7813 } else if (!strcmp(opt, "--")) {
7814 seen_dashdash = TRUE;
7815 continue;
7817 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7818 printf("tig version %s\n", TIG_VERSION);
7819 quit(0);
7821 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7822 printf("%s\n", usage);
7823 quit(0);
7825 } else if (!strcmp(opt, "--all")) {
7826 argv_append(&opt_rev_args, opt);
7827 continue;
7828 }
7830 if (!argv_append(&filter_argv, opt))
7831 die("command too long");
7832 }
7834 if (filter_argv)
7835 filter_options(filter_argv);
7837 return request;
7838 }
7840 int
7841 main(int argc, const char *argv[])
7842 {
7843 const char *codeset = "UTF-8";
7844 enum request request = parse_options(argc, argv);
7845 struct view *view;
7846 size_t i;
7848 signal(SIGINT, quit);
7849 signal(SIGPIPE, SIG_IGN);
7851 if (setlocale(LC_ALL, "")) {
7852 codeset = nl_langinfo(CODESET);
7853 }
7855 if (load_repo_info() == ERR)
7856 die("Failed to load repo info.");
7858 if (load_options() == ERR)
7859 die("Failed to load user config.");
7861 if (load_git_config() == ERR)
7862 die("Failed to load repo config.");
7864 /* Require a git repository unless when running in pager mode. */
7865 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7866 die("Not a git repository");
7868 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7869 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7870 if (opt_iconv_in == ICONV_NONE)
7871 die("Failed to initialize character set conversion");
7872 }
7874 if (codeset && strcmp(codeset, "UTF-8")) {
7875 opt_iconv_out = iconv_open(codeset, "UTF-8");
7876 if (opt_iconv_out == ICONV_NONE)
7877 die("Failed to initialize character set conversion");
7878 }
7880 if (load_refs() == ERR)
7881 die("Failed to load refs.");
7883 foreach_view (view, i) {
7884 if (getenv(view->cmd_env))
7885 warn("Use of the %s environment variable is deprecated,"
7886 " use options or TIG_DIFF_ARGS instead",
7887 view->cmd_env);
7888 if (!argv_from_env(view->ops->argv, view->cmd_env))
7889 die("Too many arguments in the `%s` environment variable",
7890 view->cmd_env);
7891 }
7893 init_display();
7895 while (view_driver(display[current_view], request)) {
7896 int key = get_input(0);
7898 view = display[current_view];
7899 request = get_keybinding(view->keymap, key);
7901 /* Some low-level request handling. This keeps access to
7902 * status_win restricted. */
7903 switch (request) {
7904 case REQ_NONE:
7905 report("Unknown key, press %s for help",
7906 get_key(view->keymap, REQ_VIEW_HELP));
7907 break;
7908 case REQ_PROMPT:
7909 {
7910 char *cmd = read_prompt(":");
7912 if (cmd && isdigit(*cmd)) {
7913 int lineno = view->lineno + 1;
7915 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7916 select_view_line(view, lineno - 1);
7917 report("");
7918 } else {
7919 report("Unable to parse '%s' as a line number", cmd);
7920 }
7922 } else if (cmd) {
7923 struct view *next = VIEW(REQ_VIEW_PAGER);
7924 const char *argv[SIZEOF_ARG] = { "git" };
7925 int argc = 1;
7927 /* When running random commands, initially show the
7928 * command in the title. However, it maybe later be
7929 * overwritten if a commit line is selected. */
7930 string_ncopy(next->ref, cmd, strlen(cmd));
7932 if (!argv_from_string(argv, &argc, cmd)) {
7933 report("Too many arguments");
7934 } else if (!prepare_update(next, argv, NULL)) {
7935 report("Failed to format command");
7936 } else {
7937 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7938 }
7939 }
7941 request = REQ_NONE;
7942 break;
7943 }
7944 case REQ_SEARCH:
7945 case REQ_SEARCH_BACK:
7946 {
7947 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7948 char *search = read_prompt(prompt);
7950 if (search)
7951 string_ncopy(opt_search, search, strlen(search));
7952 else if (*opt_search)
7953 request = request == REQ_SEARCH ?
7954 REQ_FIND_NEXT :
7955 REQ_FIND_PREV;
7956 else
7957 request = REQ_NONE;
7958 break;
7959 }
7960 default:
7961 break;
7962 }
7963 }
7965 quit(0);
7967 return 0;
7968 }