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) == 2 && name[0] == '^' && isprint(*name))
1624 return (int)name[1] & 0x1f;
1625 if (strlen(name) == 1 && isprint(*name))
1626 return (int) *name;
1627 return ERR;
1628 }
1630 static const char *
1631 get_key_name(int key_value)
1632 {
1633 static char key_char[] = "'X'\0";
1634 const char *seq = NULL;
1635 int key;
1637 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1638 if (key_table[key].value == key_value)
1639 seq = key_table[key].name;
1641 if (seq == NULL && key_value < 0x7f) {
1642 char *s = key_char + 1;
1644 if (key_value >= 0x20) {
1645 *s++ = key_value;
1646 } else {
1647 *s++ = '^';
1648 *s++ = 0x40 | (key_value & 0x1f);
1649 }
1650 *s++ = '\'';
1651 *s++ = '\0';
1652 seq = key_char;
1653 }
1655 return seq ? seq : "(no key)";
1656 }
1658 static bool
1659 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1660 {
1661 const char *sep = *pos > 0 ? ", " : "";
1662 const char *keyname = get_key_name(keybinding->alias);
1664 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1665 }
1667 static bool
1668 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1669 enum keymap keymap, bool all)
1670 {
1671 int i;
1673 for (i = 0; i < keybindings[keymap].size; i++) {
1674 if (keybindings[keymap].data[i].request == request) {
1675 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1676 return FALSE;
1677 if (!all)
1678 break;
1679 }
1680 }
1682 return TRUE;
1683 }
1685 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1687 static const char *
1688 get_keys(enum keymap keymap, enum request request, bool all)
1689 {
1690 static char buf[BUFSIZ];
1691 size_t pos = 0;
1692 int i;
1694 buf[pos] = 0;
1696 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1697 return "Too many keybindings!";
1698 if (pos > 0 && !all)
1699 return buf;
1701 if (keymap != KEYMAP_GENERIC) {
1702 /* Only the generic keymap includes the default keybindings when
1703 * listing all keys. */
1704 if (all)
1705 return buf;
1707 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1708 return "Too many keybindings!";
1709 if (pos)
1710 return buf;
1711 }
1713 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1714 if (default_keybindings[i].request == request) {
1715 if (!append_key(buf, &pos, &default_keybindings[i]))
1716 return "Too many keybindings!";
1717 if (!all)
1718 return buf;
1719 }
1720 }
1722 return buf;
1723 }
1725 struct run_request {
1726 enum keymap keymap;
1727 int key;
1728 const char **argv;
1729 };
1731 static struct run_request *run_request;
1732 static size_t run_requests;
1734 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1736 static enum request
1737 add_run_request(enum keymap keymap, int key, const char **argv)
1738 {
1739 struct run_request *req;
1741 if (!realloc_run_requests(&run_request, run_requests, 1))
1742 return REQ_NONE;
1744 req = &run_request[run_requests];
1745 req->keymap = keymap;
1746 req->key = key;
1747 req->argv = NULL;
1749 if (!argv_copy(&req->argv, argv))
1750 return REQ_NONE;
1752 return REQ_NONE + ++run_requests;
1753 }
1755 static struct run_request *
1756 get_run_request(enum request request)
1757 {
1758 if (request <= REQ_NONE)
1759 return NULL;
1760 return &run_request[request - REQ_NONE - 1];
1761 }
1763 static void
1764 add_builtin_run_requests(void)
1765 {
1766 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1767 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1768 const char *commit[] = { "git", "commit", NULL };
1769 const char *gc[] = { "git", "gc", NULL };
1770 struct run_request reqs[] = {
1771 { KEYMAP_MAIN, 'C', cherry_pick },
1772 { KEYMAP_STATUS, 'C', commit },
1773 { KEYMAP_BRANCH, 'C', checkout },
1774 { KEYMAP_GENERIC, 'G', gc },
1775 };
1776 int i;
1778 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1779 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1781 if (req != reqs[i].key)
1782 continue;
1783 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1784 if (req != REQ_NONE)
1785 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1786 }
1787 }
1789 /*
1790 * User config file handling.
1791 */
1793 static int config_lineno;
1794 static bool config_errors;
1795 static const char *config_msg;
1797 static const struct enum_map color_map[] = {
1798 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1799 COLOR_MAP(DEFAULT),
1800 COLOR_MAP(BLACK),
1801 COLOR_MAP(BLUE),
1802 COLOR_MAP(CYAN),
1803 COLOR_MAP(GREEN),
1804 COLOR_MAP(MAGENTA),
1805 COLOR_MAP(RED),
1806 COLOR_MAP(WHITE),
1807 COLOR_MAP(YELLOW),
1808 };
1810 static const struct enum_map attr_map[] = {
1811 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1812 ATTR_MAP(NORMAL),
1813 ATTR_MAP(BLINK),
1814 ATTR_MAP(BOLD),
1815 ATTR_MAP(DIM),
1816 ATTR_MAP(REVERSE),
1817 ATTR_MAP(STANDOUT),
1818 ATTR_MAP(UNDERLINE),
1819 };
1821 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1823 static int parse_step(double *opt, const char *arg)
1824 {
1825 *opt = atoi(arg);
1826 if (!strchr(arg, '%'))
1827 return OK;
1829 /* "Shift down" so 100% and 1 does not conflict. */
1830 *opt = (*opt - 1) / 100;
1831 if (*opt >= 1.0) {
1832 *opt = 0.99;
1833 config_msg = "Step value larger than 100%";
1834 return ERR;
1835 }
1836 if (*opt < 0.0) {
1837 *opt = 1;
1838 config_msg = "Invalid step value";
1839 return ERR;
1840 }
1841 return OK;
1842 }
1844 static int
1845 parse_int(int *opt, const char *arg, int min, int max)
1846 {
1847 int value = atoi(arg);
1849 if (min <= value && value <= max) {
1850 *opt = value;
1851 return OK;
1852 }
1854 config_msg = "Integer value out of bound";
1855 return ERR;
1856 }
1858 static bool
1859 set_color(int *color, const char *name)
1860 {
1861 if (map_enum(color, color_map, name))
1862 return TRUE;
1863 if (!prefixcmp(name, "color"))
1864 return parse_int(color, name + 5, 0, 255) == OK;
1865 return FALSE;
1866 }
1868 /* Wants: object fgcolor bgcolor [attribute] */
1869 static int
1870 option_color_command(int argc, const char *argv[])
1871 {
1872 struct line_info *info;
1874 if (argc < 3) {
1875 config_msg = "Wrong number of arguments given to color command";
1876 return ERR;
1877 }
1879 info = get_line_info(argv[0]);
1880 if (!info) {
1881 static const struct enum_map obsolete[] = {
1882 ENUM_MAP("main-delim", LINE_DELIMITER),
1883 ENUM_MAP("main-date", LINE_DATE),
1884 ENUM_MAP("main-author", LINE_AUTHOR),
1885 };
1886 int index;
1888 if (!map_enum(&index, obsolete, argv[0])) {
1889 config_msg = "Unknown color name";
1890 return ERR;
1891 }
1892 info = &line_info[index];
1893 }
1895 if (!set_color(&info->fg, argv[1]) ||
1896 !set_color(&info->bg, argv[2])) {
1897 config_msg = "Unknown color";
1898 return ERR;
1899 }
1901 info->attr = 0;
1902 while (argc-- > 3) {
1903 int attr;
1905 if (!set_attribute(&attr, argv[argc])) {
1906 config_msg = "Unknown attribute";
1907 return ERR;
1908 }
1909 info->attr |= attr;
1910 }
1912 return OK;
1913 }
1915 static int parse_bool(bool *opt, const char *arg)
1916 {
1917 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1918 ? TRUE : FALSE;
1919 return OK;
1920 }
1922 static int parse_enum_do(unsigned int *opt, const char *arg,
1923 const struct enum_map *map, size_t map_size)
1924 {
1925 bool is_true;
1927 assert(map_size > 1);
1929 if (map_enum_do(map, map_size, (int *) opt, arg))
1930 return OK;
1932 if (parse_bool(&is_true, arg) != OK)
1933 return ERR;
1935 *opt = is_true ? map[1].value : map[0].value;
1936 return OK;
1937 }
1939 #define parse_enum(opt, arg, map) \
1940 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1942 static int
1943 parse_string(char *opt, const char *arg, size_t optsize)
1944 {
1945 int arglen = strlen(arg);
1947 switch (arg[0]) {
1948 case '\"':
1949 case '\'':
1950 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1951 config_msg = "Unmatched quotation";
1952 return ERR;
1953 }
1954 arg += 1; arglen -= 2;
1955 default:
1956 string_ncopy_do(opt, optsize, arg, arglen);
1957 return OK;
1958 }
1959 }
1961 /* Wants: name = value */
1962 static int
1963 option_set_command(int argc, const char *argv[])
1964 {
1965 if (argc != 3) {
1966 config_msg = "Wrong number of arguments given to set command";
1967 return ERR;
1968 }
1970 if (strcmp(argv[1], "=")) {
1971 config_msg = "No value assigned";
1972 return ERR;
1973 }
1975 if (!strcmp(argv[0], "show-author"))
1976 return parse_enum(&opt_author, argv[2], author_map);
1978 if (!strcmp(argv[0], "show-date"))
1979 return parse_enum(&opt_date, argv[2], date_map);
1981 if (!strcmp(argv[0], "show-rev-graph"))
1982 return parse_bool(&opt_rev_graph, argv[2]);
1984 if (!strcmp(argv[0], "show-refs"))
1985 return parse_bool(&opt_show_refs, argv[2]);
1987 if (!strcmp(argv[0], "show-line-numbers"))
1988 return parse_bool(&opt_line_number, argv[2]);
1990 if (!strcmp(argv[0], "line-graphics"))
1991 return parse_bool(&opt_line_graphics, argv[2]);
1993 if (!strcmp(argv[0], "line-number-interval"))
1994 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1996 if (!strcmp(argv[0], "author-width"))
1997 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1999 if (!strcmp(argv[0], "horizontal-scroll"))
2000 return parse_step(&opt_hscroll, argv[2]);
2002 if (!strcmp(argv[0], "split-view-height"))
2003 return parse_step(&opt_scale_split_view, argv[2]);
2005 if (!strcmp(argv[0], "tab-size"))
2006 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2008 if (!strcmp(argv[0], "commit-encoding"))
2009 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2011 config_msg = "Unknown variable name";
2012 return ERR;
2013 }
2015 /* Wants: mode request key */
2016 static int
2017 option_bind_command(int argc, const char *argv[])
2018 {
2019 enum request request;
2020 int keymap = -1;
2021 int key;
2023 if (argc < 3) {
2024 config_msg = "Wrong number of arguments given to bind command";
2025 return ERR;
2026 }
2028 if (!set_keymap(&keymap, argv[0])) {
2029 config_msg = "Unknown key map";
2030 return ERR;
2031 }
2033 key = get_key_value(argv[1]);
2034 if (key == ERR) {
2035 config_msg = "Unknown key";
2036 return ERR;
2037 }
2039 request = get_request(argv[2]);
2040 if (request == REQ_UNKNOWN) {
2041 static const struct enum_map obsolete[] = {
2042 ENUM_MAP("cherry-pick", REQ_NONE),
2043 ENUM_MAP("screen-resize", REQ_NONE),
2044 ENUM_MAP("tree-parent", REQ_PARENT),
2045 };
2046 int alias;
2048 if (map_enum(&alias, obsolete, argv[2])) {
2049 if (alias != REQ_NONE)
2050 add_keybinding(keymap, alias, key);
2051 config_msg = "Obsolete request name";
2052 return ERR;
2053 }
2054 }
2055 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2056 request = add_run_request(keymap, key, argv + 2);
2057 if (request == REQ_UNKNOWN) {
2058 config_msg = "Unknown request name";
2059 return ERR;
2060 }
2062 add_keybinding(keymap, request, key);
2064 return OK;
2065 }
2067 static int
2068 set_option(const char *opt, char *value)
2069 {
2070 const char *argv[SIZEOF_ARG];
2071 int argc = 0;
2073 if (!argv_from_string(argv, &argc, value)) {
2074 config_msg = "Too many option arguments";
2075 return ERR;
2076 }
2078 if (!strcmp(opt, "color"))
2079 return option_color_command(argc, argv);
2081 if (!strcmp(opt, "set"))
2082 return option_set_command(argc, argv);
2084 if (!strcmp(opt, "bind"))
2085 return option_bind_command(argc, argv);
2087 config_msg = "Unknown option command";
2088 return ERR;
2089 }
2091 static int
2092 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2093 {
2094 int status = OK;
2096 config_lineno++;
2097 config_msg = "Internal error";
2099 /* Check for comment markers, since read_properties() will
2100 * only ensure opt and value are split at first " \t". */
2101 optlen = strcspn(opt, "#");
2102 if (optlen == 0)
2103 return OK;
2105 if (opt[optlen] != 0) {
2106 config_msg = "No option value";
2107 status = ERR;
2109 } else {
2110 /* Look for comment endings in the value. */
2111 size_t len = strcspn(value, "#");
2113 if (len < valuelen) {
2114 valuelen = len;
2115 value[valuelen] = 0;
2116 }
2118 status = set_option(opt, value);
2119 }
2121 if (status == ERR) {
2122 warn("Error on line %d, near '%.*s': %s",
2123 config_lineno, (int) optlen, opt, config_msg);
2124 config_errors = TRUE;
2125 }
2127 /* Always keep going if errors are encountered. */
2128 return OK;
2129 }
2131 static void
2132 load_option_file(const char *path)
2133 {
2134 struct io io;
2136 /* It's OK that the file doesn't exist. */
2137 if (!io_open(&io, "%s", path))
2138 return;
2140 config_lineno = 0;
2141 config_errors = FALSE;
2143 if (io_load(&io, " \t", read_option) == ERR ||
2144 config_errors == TRUE)
2145 warn("Errors while loading %s.", path);
2146 }
2148 static int
2149 load_options(void)
2150 {
2151 const char *home = getenv("HOME");
2152 const char *tigrc_user = getenv("TIGRC_USER");
2153 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2154 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2155 char buf[SIZEOF_STR];
2157 if (!tigrc_system)
2158 tigrc_system = SYSCONFDIR "/tigrc";
2159 load_option_file(tigrc_system);
2161 if (!tigrc_user) {
2162 if (!home || !string_format(buf, "%s/.tigrc", home))
2163 return ERR;
2164 tigrc_user = buf;
2165 }
2166 load_option_file(tigrc_user);
2168 /* Add _after_ loading config files to avoid adding run requests
2169 * that conflict with keybindings. */
2170 add_builtin_run_requests();
2172 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2173 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2174 int argc = 0;
2176 if (!string_format(buf, "%s", tig_diff_opts) ||
2177 !argv_from_string(diff_opts, &argc, buf))
2178 die("TIG_DIFF_OPTS contains too many arguments");
2179 else if (!argv_copy(&opt_diff_args, diff_opts))
2180 die("Failed to format TIG_DIFF_OPTS arguments");
2181 }
2183 return OK;
2184 }
2187 /*
2188 * The viewer
2189 */
2191 struct view;
2192 struct view_ops;
2194 /* The display array of active views and the index of the current view. */
2195 static struct view *display[2];
2196 static unsigned int current_view;
2198 #define foreach_displayed_view(view, i) \
2199 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2201 #define displayed_views() (display[1] != NULL ? 2 : 1)
2203 /* Current head and commit ID */
2204 static char ref_blob[SIZEOF_REF] = "";
2205 static char ref_commit[SIZEOF_REF] = "HEAD";
2206 static char ref_head[SIZEOF_REF] = "HEAD";
2207 static char ref_branch[SIZEOF_REF] = "";
2209 enum view_type {
2210 VIEW_MAIN,
2211 VIEW_DIFF,
2212 VIEW_LOG,
2213 VIEW_TREE,
2214 VIEW_BLOB,
2215 VIEW_BLAME,
2216 VIEW_BRANCH,
2217 VIEW_HELP,
2218 VIEW_PAGER,
2219 VIEW_STATUS,
2220 VIEW_STAGE,
2221 };
2223 struct view {
2224 enum view_type type; /* View type */
2225 const char *name; /* View name */
2226 const char *cmd_env; /* Command line set via environment */
2227 const char *id; /* Points to either of ref_{head,commit,blob} */
2229 struct view_ops *ops; /* View operations */
2231 enum keymap keymap; /* What keymap does this view have */
2232 bool git_dir; /* Whether the view requires a git directory. */
2234 char ref[SIZEOF_REF]; /* Hovered commit reference */
2235 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2237 int height, width; /* The width and height of the main window */
2238 WINDOW *win; /* The main window */
2239 WINDOW *title; /* The title window living below the main window */
2241 /* Navigation */
2242 unsigned long offset; /* Offset of the window top */
2243 unsigned long yoffset; /* Offset from the window side. */
2244 unsigned long lineno; /* Current line number */
2245 unsigned long p_offset; /* Previous offset of the window top */
2246 unsigned long p_yoffset;/* Previous offset from the window side */
2247 unsigned long p_lineno; /* Previous current line number */
2248 bool p_restore; /* Should the previous position be restored. */
2250 /* Searching */
2251 char grep[SIZEOF_STR]; /* Search string */
2252 regex_t *regex; /* Pre-compiled regexp */
2254 /* If non-NULL, points to the view that opened this view. If this view
2255 * is closed tig will switch back to the parent view. */
2256 struct view *parent;
2257 struct view *prev;
2259 /* Buffering */
2260 size_t lines; /* Total number of lines */
2261 struct line *line; /* Line index */
2262 unsigned int digits; /* Number of digits in the lines member. */
2264 /* Drawing */
2265 struct line *curline; /* Line currently being drawn. */
2266 enum line_type curtype; /* Attribute currently used for drawing. */
2267 unsigned long col; /* Column when drawing. */
2268 bool has_scrolled; /* View was scrolled. */
2270 /* Loading */
2271 const char **argv; /* Shell command arguments. */
2272 const char *dir; /* Directory from which to execute. */
2273 struct io io;
2274 struct io *pipe;
2275 time_t start_time;
2276 time_t update_secs;
2277 };
2279 struct view_ops {
2280 /* What type of content being displayed. Used in the title bar. */
2281 const char *type;
2282 /* Default command arguments. */
2283 const char **argv;
2284 /* Open and reads in all view content. */
2285 bool (*open)(struct view *view);
2286 /* Read one line; updates view->line. */
2287 bool (*read)(struct view *view, char *data);
2288 /* Draw one line; @lineno must be < view->height. */
2289 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2290 /* Depending on view handle a special requests. */
2291 enum request (*request)(struct view *view, enum request request, struct line *line);
2292 /* Search for regexp in a line. */
2293 bool (*grep)(struct view *view, struct line *line);
2294 /* Select line */
2295 void (*select)(struct view *view, struct line *line);
2296 /* Prepare view for loading */
2297 bool (*prepare)(struct view *view);
2298 };
2300 static struct view_ops blame_ops;
2301 static struct view_ops blob_ops;
2302 static struct view_ops diff_ops;
2303 static struct view_ops help_ops;
2304 static struct view_ops log_ops;
2305 static struct view_ops main_ops;
2306 static struct view_ops pager_ops;
2307 static struct view_ops stage_ops;
2308 static struct view_ops status_ops;
2309 static struct view_ops tree_ops;
2310 static struct view_ops branch_ops;
2312 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2313 { type, name, #env, ref, ops, map, git }
2315 #define VIEW_(id, name, ops, git, ref) \
2316 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2318 static struct view views[] = {
2319 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2320 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2321 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2322 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2323 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2324 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2325 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2326 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2327 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2328 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2329 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2330 };
2332 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2334 #define foreach_view(view, i) \
2335 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2337 #define view_is_displayed(view) \
2338 (view == display[0] || view == display[1])
2340 static enum request
2341 view_request(struct view *view, enum request request)
2342 {
2343 if (!view || !view->lines)
2344 return request;
2345 return view->ops->request(view, request, &view->line[view->lineno]);
2346 }
2349 /*
2350 * View drawing.
2351 */
2353 static inline void
2354 set_view_attr(struct view *view, enum line_type type)
2355 {
2356 if (!view->curline->selected && view->curtype != type) {
2357 (void) wattrset(view->win, get_line_attr(type));
2358 wchgat(view->win, -1, 0, type, NULL);
2359 view->curtype = type;
2360 }
2361 }
2363 static int
2364 draw_chars(struct view *view, enum line_type type, const char *string,
2365 int max_len, bool use_tilde)
2366 {
2367 static char out_buffer[BUFSIZ * 2];
2368 int len = 0;
2369 int col = 0;
2370 int trimmed = FALSE;
2371 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2373 if (max_len <= 0)
2374 return 0;
2376 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2378 set_view_attr(view, type);
2379 if (len > 0) {
2380 if (opt_iconv_out != ICONV_NONE) {
2381 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2382 size_t inlen = len + 1;
2384 char *outbuf = out_buffer;
2385 size_t outlen = sizeof(out_buffer);
2387 size_t ret;
2389 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2390 if (ret != (size_t) -1) {
2391 string = out_buffer;
2392 len = sizeof(out_buffer) - outlen;
2393 }
2394 }
2396 waddnstr(view->win, string, len);
2397 }
2398 if (trimmed && use_tilde) {
2399 set_view_attr(view, LINE_DELIMITER);
2400 waddch(view->win, '~');
2401 col++;
2402 }
2404 return col;
2405 }
2407 static int
2408 draw_space(struct view *view, enum line_type type, int max, int spaces)
2409 {
2410 static char space[] = " ";
2411 int col = 0;
2413 spaces = MIN(max, spaces);
2415 while (spaces > 0) {
2416 int len = MIN(spaces, sizeof(space) - 1);
2418 col += draw_chars(view, type, space, len, FALSE);
2419 spaces -= len;
2420 }
2422 return col;
2423 }
2425 static bool
2426 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2427 {
2428 char text[SIZEOF_STR];
2430 do {
2431 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2433 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2434 string += pos;
2435 } while (*string && view->width + view->yoffset > view->col);
2437 return view->width + view->yoffset <= view->col;
2438 }
2440 static bool
2441 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2442 {
2443 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2444 int max = view->width + view->yoffset - view->col;
2445 int i;
2447 if (max < size)
2448 size = max;
2450 set_view_attr(view, type);
2451 /* Using waddch() instead of waddnstr() ensures that
2452 * they'll be rendered correctly for the cursor line. */
2453 for (i = skip; i < size; i++)
2454 waddch(view->win, graphic[i]);
2456 view->col += size;
2457 if (size < max && skip <= size)
2458 waddch(view->win, ' ');
2459 view->col++;
2461 return view->width + view->yoffset <= view->col;
2462 }
2464 static bool
2465 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2466 {
2467 int max = MIN(view->width + view->yoffset - view->col, len);
2468 int col;
2470 if (text)
2471 col = draw_chars(view, type, text, max - 1, trim);
2472 else
2473 col = draw_space(view, type, max - 1, max - 1);
2475 view->col += col;
2476 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2477 return view->width + view->yoffset <= view->col;
2478 }
2480 static bool
2481 draw_date(struct view *view, struct time *time)
2482 {
2483 const char *date = mkdate(time, opt_date);
2484 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2486 return draw_field(view, LINE_DATE, date, cols, FALSE);
2487 }
2489 static bool
2490 draw_author(struct view *view, const char *author)
2491 {
2492 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2493 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2495 if (abbreviate && author)
2496 author = get_author_initials(author);
2498 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2499 }
2501 static bool
2502 draw_mode(struct view *view, mode_t mode)
2503 {
2504 const char *str;
2506 if (S_ISDIR(mode))
2507 str = "drwxr-xr-x";
2508 else if (S_ISLNK(mode))
2509 str = "lrwxrwxrwx";
2510 else if (S_ISGITLINK(mode))
2511 str = "m---------";
2512 else if (S_ISREG(mode) && mode & S_IXUSR)
2513 str = "-rwxr-xr-x";
2514 else if (S_ISREG(mode))
2515 str = "-rw-r--r--";
2516 else
2517 str = "----------";
2519 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2520 }
2522 static bool
2523 draw_lineno(struct view *view, unsigned int lineno)
2524 {
2525 char number[10];
2526 int digits3 = view->digits < 3 ? 3 : view->digits;
2527 int max = MIN(view->width + view->yoffset - view->col, digits3);
2528 char *text = NULL;
2529 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2531 lineno += view->offset + 1;
2532 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2533 static char fmt[] = "%1ld";
2535 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2536 if (string_format(number, fmt, lineno))
2537 text = number;
2538 }
2539 if (text)
2540 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2541 else
2542 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2543 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2544 }
2546 static bool
2547 draw_view_line(struct view *view, unsigned int lineno)
2548 {
2549 struct line *line;
2550 bool selected = (view->offset + lineno == view->lineno);
2552 assert(view_is_displayed(view));
2554 if (view->offset + lineno >= view->lines)
2555 return FALSE;
2557 line = &view->line[view->offset + lineno];
2559 wmove(view->win, lineno, 0);
2560 if (line->cleareol)
2561 wclrtoeol(view->win);
2562 view->col = 0;
2563 view->curline = line;
2564 view->curtype = LINE_NONE;
2565 line->selected = FALSE;
2566 line->dirty = line->cleareol = 0;
2568 if (selected) {
2569 set_view_attr(view, LINE_CURSOR);
2570 line->selected = TRUE;
2571 view->ops->select(view, line);
2572 }
2574 return view->ops->draw(view, line, lineno);
2575 }
2577 static void
2578 redraw_view_dirty(struct view *view)
2579 {
2580 bool dirty = FALSE;
2581 int lineno;
2583 for (lineno = 0; lineno < view->height; lineno++) {
2584 if (view->offset + lineno >= view->lines)
2585 break;
2586 if (!view->line[view->offset + lineno].dirty)
2587 continue;
2588 dirty = TRUE;
2589 if (!draw_view_line(view, lineno))
2590 break;
2591 }
2593 if (!dirty)
2594 return;
2595 wnoutrefresh(view->win);
2596 }
2598 static void
2599 redraw_view_from(struct view *view, int lineno)
2600 {
2601 assert(0 <= lineno && lineno < view->height);
2603 for (; lineno < view->height; lineno++) {
2604 if (!draw_view_line(view, lineno))
2605 break;
2606 }
2608 wnoutrefresh(view->win);
2609 }
2611 static void
2612 redraw_view(struct view *view)
2613 {
2614 werase(view->win);
2615 redraw_view_from(view, 0);
2616 }
2619 static void
2620 update_view_title(struct view *view)
2621 {
2622 char buf[SIZEOF_STR];
2623 char state[SIZEOF_STR];
2624 size_t bufpos = 0, statelen = 0;
2626 assert(view_is_displayed(view));
2628 if (view->type != VIEW_STATUS && view->lines) {
2629 unsigned int view_lines = view->offset + view->height;
2630 unsigned int lines = view->lines
2631 ? MIN(view_lines, view->lines) * 100 / view->lines
2632 : 0;
2634 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2635 view->ops->type,
2636 view->lineno + 1,
2637 view->lines,
2638 lines);
2640 }
2642 if (view->pipe) {
2643 time_t secs = time(NULL) - view->start_time;
2645 /* Three git seconds are a long time ... */
2646 if (secs > 2)
2647 string_format_from(state, &statelen, " loading %lds", secs);
2648 }
2650 string_format_from(buf, &bufpos, "[%s]", view->name);
2651 if (*view->ref && bufpos < view->width) {
2652 size_t refsize = strlen(view->ref);
2653 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2655 if (minsize < view->width)
2656 refsize = view->width - minsize + 7;
2657 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2658 }
2660 if (statelen && bufpos < view->width) {
2661 string_format_from(buf, &bufpos, "%s", state);
2662 }
2664 if (view == display[current_view])
2665 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2666 else
2667 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2669 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2670 wclrtoeol(view->title);
2671 wnoutrefresh(view->title);
2672 }
2674 static int
2675 apply_step(double step, int value)
2676 {
2677 if (step >= 1)
2678 return (int) step;
2679 value *= step + 0.01;
2680 return value ? value : 1;
2681 }
2683 static void
2684 resize_display(void)
2685 {
2686 int offset, i;
2687 struct view *base = display[0];
2688 struct view *view = display[1] ? display[1] : display[0];
2690 /* Setup window dimensions */
2692 getmaxyx(stdscr, base->height, base->width);
2694 /* Make room for the status window. */
2695 base->height -= 1;
2697 if (view != base) {
2698 /* Horizontal split. */
2699 view->width = base->width;
2700 view->height = apply_step(opt_scale_split_view, base->height);
2701 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2702 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2703 base->height -= view->height;
2705 /* Make room for the title bar. */
2706 view->height -= 1;
2707 }
2709 /* Make room for the title bar. */
2710 base->height -= 1;
2712 offset = 0;
2714 foreach_displayed_view (view, i) {
2715 if (!view->win) {
2716 view->win = newwin(view->height, 0, offset, 0);
2717 if (!view->win)
2718 die("Failed to create %s view", view->name);
2720 scrollok(view->win, FALSE);
2722 view->title = newwin(1, 0, offset + view->height, 0);
2723 if (!view->title)
2724 die("Failed to create title window");
2726 } else {
2727 wresize(view->win, view->height, view->width);
2728 mvwin(view->win, offset, 0);
2729 mvwin(view->title, offset + view->height, 0);
2730 }
2732 offset += view->height + 1;
2733 }
2734 }
2736 static void
2737 redraw_display(bool clear)
2738 {
2739 struct view *view;
2740 int i;
2742 foreach_displayed_view (view, i) {
2743 if (clear)
2744 wclear(view->win);
2745 redraw_view(view);
2746 update_view_title(view);
2747 }
2748 }
2751 /*
2752 * Option management
2753 */
2755 static void
2756 toggle_enum_option_do(unsigned int *opt, const char *help,
2757 const struct enum_map *map, size_t size)
2758 {
2759 *opt = (*opt + 1) % size;
2760 redraw_display(FALSE);
2761 report("Displaying %s %s", enum_name(map[*opt]), help);
2762 }
2764 #define toggle_enum_option(opt, help, map) \
2765 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2767 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2768 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2770 static void
2771 toggle_view_option(bool *option, const char *help)
2772 {
2773 *option = !*option;
2774 redraw_display(FALSE);
2775 report("%sabling %s", *option ? "En" : "Dis", help);
2776 }
2778 static void
2779 open_option_menu(void)
2780 {
2781 const struct menu_item menu[] = {
2782 { '.', "line numbers", &opt_line_number },
2783 { 'D', "date display", &opt_date },
2784 { 'A', "author display", &opt_author },
2785 { 'g', "revision graph display", &opt_rev_graph },
2786 { 'F', "reference display", &opt_show_refs },
2787 { 0 }
2788 };
2789 int selected = 0;
2791 if (prompt_menu("Toggle option", menu, &selected)) {
2792 if (menu[selected].data == &opt_date)
2793 toggle_date();
2794 else if (menu[selected].data == &opt_author)
2795 toggle_author();
2796 else
2797 toggle_view_option(menu[selected].data, menu[selected].text);
2798 }
2799 }
2801 static void
2802 maximize_view(struct view *view)
2803 {
2804 memset(display, 0, sizeof(display));
2805 current_view = 0;
2806 display[current_view] = view;
2807 resize_display();
2808 redraw_display(FALSE);
2809 report("");
2810 }
2813 /*
2814 * Navigation
2815 */
2817 static bool
2818 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2819 {
2820 if (lineno >= view->lines)
2821 lineno = view->lines > 0 ? view->lines - 1 : 0;
2823 if (offset > lineno || offset + view->height <= lineno) {
2824 unsigned long half = view->height / 2;
2826 if (lineno > half)
2827 offset = lineno - half;
2828 else
2829 offset = 0;
2830 }
2832 if (offset != view->offset || lineno != view->lineno) {
2833 view->offset = offset;
2834 view->lineno = lineno;
2835 return TRUE;
2836 }
2838 return FALSE;
2839 }
2841 /* Scrolling backend */
2842 static void
2843 do_scroll_view(struct view *view, int lines)
2844 {
2845 bool redraw_current_line = FALSE;
2847 /* The rendering expects the new offset. */
2848 view->offset += lines;
2850 assert(0 <= view->offset && view->offset < view->lines);
2851 assert(lines);
2853 /* Move current line into the view. */
2854 if (view->lineno < view->offset) {
2855 view->lineno = view->offset;
2856 redraw_current_line = TRUE;
2857 } else if (view->lineno >= view->offset + view->height) {
2858 view->lineno = view->offset + view->height - 1;
2859 redraw_current_line = TRUE;
2860 }
2862 assert(view->offset <= view->lineno && view->lineno < view->lines);
2864 /* Redraw the whole screen if scrolling is pointless. */
2865 if (view->height < ABS(lines)) {
2866 redraw_view(view);
2868 } else {
2869 int line = lines > 0 ? view->height - lines : 0;
2870 int end = line + ABS(lines);
2872 scrollok(view->win, TRUE);
2873 wscrl(view->win, lines);
2874 scrollok(view->win, FALSE);
2876 while (line < end && draw_view_line(view, line))
2877 line++;
2879 if (redraw_current_line)
2880 draw_view_line(view, view->lineno - view->offset);
2881 wnoutrefresh(view->win);
2882 }
2884 view->has_scrolled = TRUE;
2885 report("");
2886 }
2888 /* Scroll frontend */
2889 static void
2890 scroll_view(struct view *view, enum request request)
2891 {
2892 int lines = 1;
2894 assert(view_is_displayed(view));
2896 switch (request) {
2897 case REQ_SCROLL_LEFT:
2898 if (view->yoffset == 0) {
2899 report("Cannot scroll beyond the first column");
2900 return;
2901 }
2902 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2903 view->yoffset = 0;
2904 else
2905 view->yoffset -= apply_step(opt_hscroll, view->width);
2906 redraw_view_from(view, 0);
2907 report("");
2908 return;
2909 case REQ_SCROLL_RIGHT:
2910 view->yoffset += apply_step(opt_hscroll, view->width);
2911 redraw_view(view);
2912 report("");
2913 return;
2914 case REQ_SCROLL_PAGE_DOWN:
2915 lines = view->height;
2916 case REQ_SCROLL_LINE_DOWN:
2917 if (view->offset + lines > view->lines)
2918 lines = view->lines - view->offset;
2920 if (lines == 0 || view->offset + view->height >= view->lines) {
2921 report("Cannot scroll beyond the last line");
2922 return;
2923 }
2924 break;
2926 case REQ_SCROLL_PAGE_UP:
2927 lines = view->height;
2928 case REQ_SCROLL_LINE_UP:
2929 if (lines > view->offset)
2930 lines = view->offset;
2932 if (lines == 0) {
2933 report("Cannot scroll beyond the first line");
2934 return;
2935 }
2937 lines = -lines;
2938 break;
2940 default:
2941 die("request %d not handled in switch", request);
2942 }
2944 do_scroll_view(view, lines);
2945 }
2947 /* Cursor moving */
2948 static void
2949 move_view(struct view *view, enum request request)
2950 {
2951 int scroll_steps = 0;
2952 int steps;
2954 switch (request) {
2955 case REQ_MOVE_FIRST_LINE:
2956 steps = -view->lineno;
2957 break;
2959 case REQ_MOVE_LAST_LINE:
2960 steps = view->lines - view->lineno - 1;
2961 break;
2963 case REQ_MOVE_PAGE_UP:
2964 steps = view->height > view->lineno
2965 ? -view->lineno : -view->height;
2966 break;
2968 case REQ_MOVE_PAGE_DOWN:
2969 steps = view->lineno + view->height >= view->lines
2970 ? view->lines - view->lineno - 1 : view->height;
2971 break;
2973 case REQ_MOVE_UP:
2974 steps = -1;
2975 break;
2977 case REQ_MOVE_DOWN:
2978 steps = 1;
2979 break;
2981 default:
2982 die("request %d not handled in switch", request);
2983 }
2985 if (steps <= 0 && view->lineno == 0) {
2986 report("Cannot move beyond the first line");
2987 return;
2989 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2990 report("Cannot move beyond the last line");
2991 return;
2992 }
2994 /* Move the current line */
2995 view->lineno += steps;
2996 assert(0 <= view->lineno && view->lineno < view->lines);
2998 /* Check whether the view needs to be scrolled */
2999 if (view->lineno < view->offset ||
3000 view->lineno >= view->offset + view->height) {
3001 scroll_steps = steps;
3002 if (steps < 0 && -steps > view->offset) {
3003 scroll_steps = -view->offset;
3005 } else if (steps > 0) {
3006 if (view->lineno == view->lines - 1 &&
3007 view->lines > view->height) {
3008 scroll_steps = view->lines - view->offset - 1;
3009 if (scroll_steps >= view->height)
3010 scroll_steps -= view->height - 1;
3011 }
3012 }
3013 }
3015 if (!view_is_displayed(view)) {
3016 view->offset += scroll_steps;
3017 assert(0 <= view->offset && view->offset < view->lines);
3018 view->ops->select(view, &view->line[view->lineno]);
3019 return;
3020 }
3022 /* Repaint the old "current" line if we be scrolling */
3023 if (ABS(steps) < view->height)
3024 draw_view_line(view, view->lineno - steps - view->offset);
3026 if (scroll_steps) {
3027 do_scroll_view(view, scroll_steps);
3028 return;
3029 }
3031 /* Draw the current line */
3032 draw_view_line(view, view->lineno - view->offset);
3034 wnoutrefresh(view->win);
3035 report("");
3036 }
3039 /*
3040 * Searching
3041 */
3043 static void search_view(struct view *view, enum request request);
3045 static bool
3046 grep_text(struct view *view, const char *text[])
3047 {
3048 regmatch_t pmatch;
3049 size_t i;
3051 for (i = 0; text[i]; i++)
3052 if (*text[i] &&
3053 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3054 return TRUE;
3055 return FALSE;
3056 }
3058 static void
3059 select_view_line(struct view *view, unsigned long lineno)
3060 {
3061 unsigned long old_lineno = view->lineno;
3062 unsigned long old_offset = view->offset;
3064 if (goto_view_line(view, view->offset, lineno)) {
3065 if (view_is_displayed(view)) {
3066 if (old_offset != view->offset) {
3067 redraw_view(view);
3068 } else {
3069 draw_view_line(view, old_lineno - view->offset);
3070 draw_view_line(view, view->lineno - view->offset);
3071 wnoutrefresh(view->win);
3072 }
3073 } else {
3074 view->ops->select(view, &view->line[view->lineno]);
3075 }
3076 }
3077 }
3079 static void
3080 find_next(struct view *view, enum request request)
3081 {
3082 unsigned long lineno = view->lineno;
3083 int direction;
3085 if (!*view->grep) {
3086 if (!*opt_search)
3087 report("No previous search");
3088 else
3089 search_view(view, request);
3090 return;
3091 }
3093 switch (request) {
3094 case REQ_SEARCH:
3095 case REQ_FIND_NEXT:
3096 direction = 1;
3097 break;
3099 case REQ_SEARCH_BACK:
3100 case REQ_FIND_PREV:
3101 direction = -1;
3102 break;
3104 default:
3105 return;
3106 }
3108 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3109 lineno += direction;
3111 /* Note, lineno is unsigned long so will wrap around in which case it
3112 * will become bigger than view->lines. */
3113 for (; lineno < view->lines; lineno += direction) {
3114 if (view->ops->grep(view, &view->line[lineno])) {
3115 select_view_line(view, lineno);
3116 report("Line %ld matches '%s'", lineno + 1, view->grep);
3117 return;
3118 }
3119 }
3121 report("No match found for '%s'", view->grep);
3122 }
3124 static void
3125 search_view(struct view *view, enum request request)
3126 {
3127 int regex_err;
3129 if (view->regex) {
3130 regfree(view->regex);
3131 *view->grep = 0;
3132 } else {
3133 view->regex = calloc(1, sizeof(*view->regex));
3134 if (!view->regex)
3135 return;
3136 }
3138 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3139 if (regex_err != 0) {
3140 char buf[SIZEOF_STR] = "unknown error";
3142 regerror(regex_err, view->regex, buf, sizeof(buf));
3143 report("Search failed: %s", buf);
3144 return;
3145 }
3147 string_copy(view->grep, opt_search);
3149 find_next(view, request);
3150 }
3152 /*
3153 * Incremental updating
3154 */
3156 static void
3157 reset_view(struct view *view)
3158 {
3159 int i;
3161 for (i = 0; i < view->lines; i++)
3162 free(view->line[i].data);
3163 free(view->line);
3165 view->p_offset = view->offset;
3166 view->p_yoffset = view->yoffset;
3167 view->p_lineno = view->lineno;
3169 view->line = NULL;
3170 view->offset = 0;
3171 view->yoffset = 0;
3172 view->lines = 0;
3173 view->lineno = 0;
3174 view->vid[0] = 0;
3175 view->update_secs = 0;
3176 }
3178 static const char *
3179 format_arg(const char *name)
3180 {
3181 static struct {
3182 const char *name;
3183 size_t namelen;
3184 const char *value;
3185 const char *value_if_empty;
3186 } vars[] = {
3187 #define FORMAT_VAR(name, value, value_if_empty) \
3188 { name, STRING_SIZE(name), value, value_if_empty }
3189 FORMAT_VAR("%(directory)", opt_path, ""),
3190 FORMAT_VAR("%(file)", opt_file, ""),
3191 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3192 FORMAT_VAR("%(head)", ref_head, ""),
3193 FORMAT_VAR("%(commit)", ref_commit, ""),
3194 FORMAT_VAR("%(blob)", ref_blob, ""),
3195 FORMAT_VAR("%(branch)", ref_branch, ""),
3196 };
3197 int i;
3199 for (i = 0; i < ARRAY_SIZE(vars); i++)
3200 if (!strncmp(name, vars[i].name, vars[i].namelen))
3201 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3203 report("Unknown replacement: `%s`", name);
3204 return NULL;
3205 }
3207 static bool
3208 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3209 {
3210 char buf[SIZEOF_STR];
3211 int argc;
3213 argv_free(*dst_argv);
3215 for (argc = 0; src_argv[argc]; argc++) {
3216 const char *arg = src_argv[argc];
3217 size_t bufpos = 0;
3219 if (!strcmp(arg, "%(fileargs)")) {
3220 if (!argv_append_array(dst_argv, opt_file_args))
3221 break;
3222 continue;
3224 } else if (!strcmp(arg, "%(diffargs)")) {
3225 if (!argv_append_array(dst_argv, opt_diff_args))
3226 break;
3227 continue;
3229 } else if (!strcmp(arg, "%(revargs)")) {
3230 if (!argv_append_array(dst_argv, opt_rev_args))
3231 break;
3232 continue;
3233 }
3235 while (arg) {
3236 char *next = strstr(arg, "%(");
3237 int len = next - arg;
3238 const char *value;
3240 if (!next || !replace) {
3241 len = strlen(arg);
3242 value = "";
3244 } else {
3245 value = format_arg(next);
3247 if (!value) {
3248 return FALSE;
3249 }
3250 }
3252 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3253 return FALSE;
3255 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3256 }
3258 if (!argv_append(dst_argv, buf))
3259 break;
3260 }
3262 return src_argv[argc] == NULL;
3263 }
3265 static bool
3266 restore_view_position(struct view *view)
3267 {
3268 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3269 return FALSE;
3271 /* Changing the view position cancels the restoring. */
3272 /* FIXME: Changing back to the first line is not detected. */
3273 if (view->offset != 0 || view->lineno != 0) {
3274 view->p_restore = FALSE;
3275 return FALSE;
3276 }
3278 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3279 view_is_displayed(view))
3280 werase(view->win);
3282 view->yoffset = view->p_yoffset;
3283 view->p_restore = FALSE;
3285 return TRUE;
3286 }
3288 static void
3289 end_update(struct view *view, bool force)
3290 {
3291 if (!view->pipe)
3292 return;
3293 while (!view->ops->read(view, NULL))
3294 if (!force)
3295 return;
3296 if (force)
3297 io_kill(view->pipe);
3298 io_done(view->pipe);
3299 view->pipe = NULL;
3300 }
3302 static void
3303 setup_update(struct view *view, const char *vid)
3304 {
3305 reset_view(view);
3306 string_copy_rev(view->vid, vid);
3307 view->pipe = &view->io;
3308 view->start_time = time(NULL);
3309 }
3311 static bool
3312 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3313 {
3314 view->dir = dir;
3315 return format_argv(&view->argv, argv, replace);
3316 }
3318 static bool
3319 prepare_update(struct view *view, const char *argv[], const char *dir)
3320 {
3321 if (view->pipe)
3322 end_update(view, TRUE);
3323 return prepare_io(view, dir, argv, FALSE);
3324 }
3326 static bool
3327 start_update(struct view *view, const char **argv, const char *dir)
3328 {
3329 if (view->pipe)
3330 io_done(view->pipe);
3331 return prepare_io(view, dir, argv, FALSE) &&
3332 io_run(&view->io, IO_RD, dir, view->argv);
3333 }
3335 static bool
3336 prepare_update_file(struct view *view, const char *name)
3337 {
3338 if (view->pipe)
3339 end_update(view, TRUE);
3340 argv_free(view->argv);
3341 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3342 }
3344 static bool
3345 begin_update(struct view *view, bool refresh)
3346 {
3347 if (view->pipe)
3348 end_update(view, TRUE);
3350 if (!refresh) {
3351 if (view->ops->prepare) {
3352 if (!view->ops->prepare(view))
3353 return FALSE;
3354 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3355 return FALSE;
3356 }
3358 /* Put the current ref_* value to the view title ref
3359 * member. This is needed by the blob view. Most other
3360 * views sets it automatically after loading because the
3361 * first line is a commit line. */
3362 string_copy_rev(view->ref, view->id);
3363 }
3365 if (view->argv && view->argv[0] &&
3366 !io_run(&view->io, IO_RD, view->dir, view->argv))
3367 return FALSE;
3369 setup_update(view, view->id);
3371 return TRUE;
3372 }
3374 static bool
3375 update_view(struct view *view)
3376 {
3377 char out_buffer[BUFSIZ * 2];
3378 char *line;
3379 /* Clear the view and redraw everything since the tree sorting
3380 * might have rearranged things. */
3381 bool redraw = view->lines == 0;
3382 bool can_read = TRUE;
3384 if (!view->pipe)
3385 return TRUE;
3387 if (!io_can_read(view->pipe)) {
3388 if (view->lines == 0 && view_is_displayed(view)) {
3389 time_t secs = time(NULL) - view->start_time;
3391 if (secs > 1 && secs > view->update_secs) {
3392 if (view->update_secs == 0)
3393 redraw_view(view);
3394 update_view_title(view);
3395 view->update_secs = secs;
3396 }
3397 }
3398 return TRUE;
3399 }
3401 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3402 if (opt_iconv_in != ICONV_NONE) {
3403 ICONV_CONST char *inbuf = line;
3404 size_t inlen = strlen(line) + 1;
3406 char *outbuf = out_buffer;
3407 size_t outlen = sizeof(out_buffer);
3409 size_t ret;
3411 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3412 if (ret != (size_t) -1)
3413 line = out_buffer;
3414 }
3416 if (!view->ops->read(view, line)) {
3417 report("Allocation failure");
3418 end_update(view, TRUE);
3419 return FALSE;
3420 }
3421 }
3423 {
3424 unsigned long lines = view->lines;
3425 int digits;
3427 for (digits = 0; lines; digits++)
3428 lines /= 10;
3430 /* Keep the displayed view in sync with line number scaling. */
3431 if (digits != view->digits) {
3432 view->digits = digits;
3433 if (opt_line_number || view->type == VIEW_BLAME)
3434 redraw = TRUE;
3435 }
3436 }
3438 if (io_error(view->pipe)) {
3439 report("Failed to read: %s", io_strerror(view->pipe));
3440 end_update(view, TRUE);
3442 } else if (io_eof(view->pipe)) {
3443 if (view_is_displayed(view))
3444 report("");
3445 end_update(view, FALSE);
3446 }
3448 if (restore_view_position(view))
3449 redraw = TRUE;
3451 if (!view_is_displayed(view))
3452 return TRUE;
3454 if (redraw)
3455 redraw_view_from(view, 0);
3456 else
3457 redraw_view_dirty(view);
3459 /* Update the title _after_ the redraw so that if the redraw picks up a
3460 * commit reference in view->ref it'll be available here. */
3461 update_view_title(view);
3462 return TRUE;
3463 }
3465 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3467 static struct line *
3468 add_line_data(struct view *view, void *data, enum line_type type)
3469 {
3470 struct line *line;
3472 if (!realloc_lines(&view->line, view->lines, 1))
3473 return NULL;
3475 line = &view->line[view->lines++];
3476 memset(line, 0, sizeof(*line));
3477 line->type = type;
3478 line->data = data;
3479 line->dirty = 1;
3481 return line;
3482 }
3484 static struct line *
3485 add_line_text(struct view *view, const char *text, enum line_type type)
3486 {
3487 char *data = text ? strdup(text) : NULL;
3489 return data ? add_line_data(view, data, type) : NULL;
3490 }
3492 static struct line *
3493 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3494 {
3495 char buf[SIZEOF_STR];
3496 va_list args;
3498 va_start(args, fmt);
3499 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3500 buf[0] = 0;
3501 va_end(args);
3503 return buf[0] ? add_line_text(view, buf, type) : NULL;
3504 }
3506 /*
3507 * View opening
3508 */
3510 enum open_flags {
3511 OPEN_DEFAULT = 0, /* Use default view switching. */
3512 OPEN_SPLIT = 1, /* Split current view. */
3513 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3514 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3515 OPEN_PREPARED = 32, /* Open already prepared command. */
3516 };
3518 static void
3519 open_view(struct view *prev, enum request request, enum open_flags flags)
3520 {
3521 bool split = !!(flags & OPEN_SPLIT);
3522 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3523 bool nomaximize = !!(flags & OPEN_REFRESH);
3524 struct view *view = VIEW(request);
3525 int nviews = displayed_views();
3526 struct view *base_view = display[0];
3528 if (view == prev && nviews == 1 && !reload) {
3529 report("Already in %s view", view->name);
3530 return;
3531 }
3533 if (view->git_dir && !opt_git_dir[0]) {
3534 report("The %s view is disabled in pager view", view->name);
3535 return;
3536 }
3538 if (split) {
3539 display[1] = view;
3540 current_view = 1;
3541 view->parent = prev;
3542 } else if (!nomaximize) {
3543 /* Maximize the current view. */
3544 memset(display, 0, sizeof(display));
3545 current_view = 0;
3546 display[current_view] = view;
3547 }
3549 /* No prev signals that this is the first loaded view. */
3550 if (prev && view != prev) {
3551 view->prev = prev;
3552 }
3554 /* Resize the view when switching between split- and full-screen,
3555 * or when switching between two different full-screen views. */
3556 if (nviews != displayed_views() ||
3557 (nviews == 1 && base_view != display[0]))
3558 resize_display();
3560 if (view->ops->open) {
3561 if (view->pipe)
3562 end_update(view, TRUE);
3563 if (!view->ops->open(view)) {
3564 report("Failed to load %s view", view->name);
3565 return;
3566 }
3567 restore_view_position(view);
3569 } else if ((reload || strcmp(view->vid, view->id)) &&
3570 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3571 report("Failed to load %s view", view->name);
3572 return;
3573 }
3575 if (split && prev->lineno - prev->offset >= prev->height) {
3576 /* Take the title line into account. */
3577 int lines = prev->lineno - prev->offset - prev->height + 1;
3579 /* Scroll the view that was split if the current line is
3580 * outside the new limited view. */
3581 do_scroll_view(prev, lines);
3582 }
3584 if (prev && view != prev && split && view_is_displayed(prev)) {
3585 /* "Blur" the previous view. */
3586 update_view_title(prev);
3587 }
3589 if (view->pipe && view->lines == 0) {
3590 /* Clear the old view and let the incremental updating refill
3591 * the screen. */
3592 werase(view->win);
3593 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3594 report("");
3595 } else if (view_is_displayed(view)) {
3596 redraw_view(view);
3597 report("");
3598 }
3599 }
3601 static void
3602 open_external_viewer(const char *argv[], const char *dir)
3603 {
3604 def_prog_mode(); /* save current tty modes */
3605 endwin(); /* restore original tty modes */
3606 io_run_fg(argv, dir);
3607 fprintf(stderr, "Press Enter to continue");
3608 getc(opt_tty);
3609 reset_prog_mode();
3610 redraw_display(TRUE);
3611 }
3613 static void
3614 open_mergetool(const char *file)
3615 {
3616 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3618 open_external_viewer(mergetool_argv, opt_cdup);
3619 }
3621 static void
3622 open_editor(const char *file)
3623 {
3624 const char *editor_argv[] = { "vi", file, NULL };
3625 const char *editor;
3627 editor = getenv("GIT_EDITOR");
3628 if (!editor && *opt_editor)
3629 editor = opt_editor;
3630 if (!editor)
3631 editor = getenv("VISUAL");
3632 if (!editor)
3633 editor = getenv("EDITOR");
3634 if (!editor)
3635 editor = "vi";
3637 editor_argv[0] = editor;
3638 open_external_viewer(editor_argv, opt_cdup);
3639 }
3641 static void
3642 open_run_request(enum request request)
3643 {
3644 struct run_request *req = get_run_request(request);
3645 const char **argv = NULL;
3647 if (!req) {
3648 report("Unknown run request");
3649 return;
3650 }
3652 if (format_argv(&argv, req->argv, TRUE))
3653 open_external_viewer(argv, NULL);
3654 if (argv)
3655 argv_free(argv);
3656 free(argv);
3657 }
3659 /*
3660 * User request switch noodle
3661 */
3663 static int
3664 view_driver(struct view *view, enum request request)
3665 {
3666 int i;
3668 if (request == REQ_NONE)
3669 return TRUE;
3671 if (request > REQ_NONE) {
3672 open_run_request(request);
3673 view_request(view, REQ_REFRESH);
3674 return TRUE;
3675 }
3677 request = view_request(view, request);
3678 if (request == REQ_NONE)
3679 return TRUE;
3681 switch (request) {
3682 case REQ_MOVE_UP:
3683 case REQ_MOVE_DOWN:
3684 case REQ_MOVE_PAGE_UP:
3685 case REQ_MOVE_PAGE_DOWN:
3686 case REQ_MOVE_FIRST_LINE:
3687 case REQ_MOVE_LAST_LINE:
3688 move_view(view, request);
3689 break;
3691 case REQ_SCROLL_LEFT:
3692 case REQ_SCROLL_RIGHT:
3693 case REQ_SCROLL_LINE_DOWN:
3694 case REQ_SCROLL_LINE_UP:
3695 case REQ_SCROLL_PAGE_DOWN:
3696 case REQ_SCROLL_PAGE_UP:
3697 scroll_view(view, request);
3698 break;
3700 case REQ_VIEW_BLAME:
3701 if (!opt_file[0]) {
3702 report("No file chosen, press %s to open tree view",
3703 get_key(view->keymap, REQ_VIEW_TREE));
3704 break;
3705 }
3706 open_view(view, request, OPEN_DEFAULT);
3707 break;
3709 case REQ_VIEW_BLOB:
3710 if (!ref_blob[0]) {
3711 report("No file chosen, press %s to open tree view",
3712 get_key(view->keymap, REQ_VIEW_TREE));
3713 break;
3714 }
3715 open_view(view, request, OPEN_DEFAULT);
3716 break;
3718 case REQ_VIEW_PAGER:
3719 if (view == NULL) {
3720 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3721 die("Failed to open stdin");
3722 open_view(view, request, OPEN_PREPARED);
3723 break;
3724 }
3726 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3727 report("No pager content, press %s to run command from prompt",
3728 get_key(view->keymap, REQ_PROMPT));
3729 break;
3730 }
3731 open_view(view, request, OPEN_DEFAULT);
3732 break;
3734 case REQ_VIEW_STAGE:
3735 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3736 report("No stage content, press %s to open the status view and choose file",
3737 get_key(view->keymap, REQ_VIEW_STATUS));
3738 break;
3739 }
3740 open_view(view, request, OPEN_DEFAULT);
3741 break;
3743 case REQ_VIEW_STATUS:
3744 if (opt_is_inside_work_tree == FALSE) {
3745 report("The status view requires a working tree");
3746 break;
3747 }
3748 open_view(view, request, OPEN_DEFAULT);
3749 break;
3751 case REQ_VIEW_MAIN:
3752 case REQ_VIEW_DIFF:
3753 case REQ_VIEW_LOG:
3754 case REQ_VIEW_TREE:
3755 case REQ_VIEW_HELP:
3756 case REQ_VIEW_BRANCH:
3757 open_view(view, request, OPEN_DEFAULT);
3758 break;
3760 case REQ_NEXT:
3761 case REQ_PREVIOUS:
3762 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3764 if (view->parent) {
3765 int line;
3767 view = view->parent;
3768 line = view->lineno;
3769 move_view(view, request);
3770 if (view_is_displayed(view))
3771 update_view_title(view);
3772 if (line != view->lineno)
3773 view_request(view, REQ_ENTER);
3774 } else {
3775 move_view(view, request);
3776 }
3777 break;
3779 case REQ_VIEW_NEXT:
3780 {
3781 int nviews = displayed_views();
3782 int next_view = (current_view + 1) % nviews;
3784 if (next_view == current_view) {
3785 report("Only one view is displayed");
3786 break;
3787 }
3789 current_view = next_view;
3790 /* Blur out the title of the previous view. */
3791 update_view_title(view);
3792 report("");
3793 break;
3794 }
3795 case REQ_REFRESH:
3796 report("Refreshing is not yet supported for the %s view", view->name);
3797 break;
3799 case REQ_MAXIMIZE:
3800 if (displayed_views() == 2)
3801 maximize_view(view);
3802 break;
3804 case REQ_OPTIONS:
3805 open_option_menu();
3806 break;
3808 case REQ_TOGGLE_LINENO:
3809 toggle_view_option(&opt_line_number, "line numbers");
3810 break;
3812 case REQ_TOGGLE_DATE:
3813 toggle_date();
3814 break;
3816 case REQ_TOGGLE_AUTHOR:
3817 toggle_author();
3818 break;
3820 case REQ_TOGGLE_REV_GRAPH:
3821 toggle_view_option(&opt_rev_graph, "revision graph display");
3822 break;
3824 case REQ_TOGGLE_REFS:
3825 toggle_view_option(&opt_show_refs, "reference display");
3826 break;
3828 case REQ_TOGGLE_SORT_FIELD:
3829 case REQ_TOGGLE_SORT_ORDER:
3830 report("Sorting is not yet supported for the %s view", view->name);
3831 break;
3833 case REQ_SEARCH:
3834 case REQ_SEARCH_BACK:
3835 search_view(view, request);
3836 break;
3838 case REQ_FIND_NEXT:
3839 case REQ_FIND_PREV:
3840 find_next(view, request);
3841 break;
3843 case REQ_STOP_LOADING:
3844 foreach_view(view, i) {
3845 if (view->pipe)
3846 report("Stopped loading the %s view", view->name),
3847 end_update(view, TRUE);
3848 }
3849 break;
3851 case REQ_SHOW_VERSION:
3852 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3853 return TRUE;
3855 case REQ_SCREEN_REDRAW:
3856 redraw_display(TRUE);
3857 break;
3859 case REQ_EDIT:
3860 report("Nothing to edit");
3861 break;
3863 case REQ_ENTER:
3864 report("Nothing to enter");
3865 break;
3867 case REQ_VIEW_CLOSE:
3868 /* XXX: Mark closed views by letting view->prev point to the
3869 * view itself. Parents to closed view should never be
3870 * followed. */
3871 if (view->prev && view->prev != view) {
3872 maximize_view(view->prev);
3873 view->prev = view;
3874 break;
3875 }
3876 /* Fall-through */
3877 case REQ_QUIT:
3878 return FALSE;
3880 default:
3881 report("Unknown key, press %s for help",
3882 get_key(view->keymap, REQ_VIEW_HELP));
3883 return TRUE;
3884 }
3886 return TRUE;
3887 }
3890 /*
3891 * View backend utilities
3892 */
3894 enum sort_field {
3895 ORDERBY_NAME,
3896 ORDERBY_DATE,
3897 ORDERBY_AUTHOR,
3898 };
3900 struct sort_state {
3901 const enum sort_field *fields;
3902 size_t size, current;
3903 bool reverse;
3904 };
3906 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3907 #define get_sort_field(state) ((state).fields[(state).current])
3908 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3910 static void
3911 sort_view(struct view *view, enum request request, struct sort_state *state,
3912 int (*compare)(const void *, const void *))
3913 {
3914 switch (request) {
3915 case REQ_TOGGLE_SORT_FIELD:
3916 state->current = (state->current + 1) % state->size;
3917 break;
3919 case REQ_TOGGLE_SORT_ORDER:
3920 state->reverse = !state->reverse;
3921 break;
3922 default:
3923 die("Not a sort request");
3924 }
3926 qsort(view->line, view->lines, sizeof(*view->line), compare);
3927 redraw_view(view);
3928 }
3930 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3932 /* Small author cache to reduce memory consumption. It uses binary
3933 * search to lookup or find place to position new entries. No entries
3934 * are ever freed. */
3935 static const char *
3936 get_author(const char *name)
3937 {
3938 static const char **authors;
3939 static size_t authors_size;
3940 int from = 0, to = authors_size - 1;
3942 while (from <= to) {
3943 size_t pos = (to + from) / 2;
3944 int cmp = strcmp(name, authors[pos]);
3946 if (!cmp)
3947 return authors[pos];
3949 if (cmp < 0)
3950 to = pos - 1;
3951 else
3952 from = pos + 1;
3953 }
3955 if (!realloc_authors(&authors, authors_size, 1))
3956 return NULL;
3957 name = strdup(name);
3958 if (!name)
3959 return NULL;
3961 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3962 authors[from] = name;
3963 authors_size++;
3965 return name;
3966 }
3968 static void
3969 parse_timesec(struct time *time, const char *sec)
3970 {
3971 time->sec = (time_t) atol(sec);
3972 }
3974 static void
3975 parse_timezone(struct time *time, const char *zone)
3976 {
3977 long tz;
3979 tz = ('0' - zone[1]) * 60 * 60 * 10;
3980 tz += ('0' - zone[2]) * 60 * 60;
3981 tz += ('0' - zone[3]) * 60 * 10;
3982 tz += ('0' - zone[4]) * 60;
3984 if (zone[0] == '-')
3985 tz = -tz;
3987 time->tz = tz;
3988 time->sec -= tz;
3989 }
3991 /* Parse author lines where the name may be empty:
3992 * author <email@address.tld> 1138474660 +0100
3993 */
3994 static void
3995 parse_author_line(char *ident, const char **author, struct time *time)
3996 {
3997 char *nameend = strchr(ident, '<');
3998 char *emailend = strchr(ident, '>');
4000 if (nameend && emailend)
4001 *nameend = *emailend = 0;
4002 ident = chomp_string(ident);
4003 if (!*ident) {
4004 if (nameend)
4005 ident = chomp_string(nameend + 1);
4006 if (!*ident)
4007 ident = "Unknown";
4008 }
4010 *author = get_author(ident);
4012 /* Parse epoch and timezone */
4013 if (emailend && emailend[1] == ' ') {
4014 char *secs = emailend + 2;
4015 char *zone = strchr(secs, ' ');
4017 parse_timesec(time, secs);
4019 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4020 parse_timezone(time, zone + 1);
4021 }
4022 }
4024 /*
4025 * Pager backend
4026 */
4028 static bool
4029 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4030 {
4031 if (opt_line_number && draw_lineno(view, lineno))
4032 return TRUE;
4034 draw_text(view, line->type, line->data, TRUE);
4035 return TRUE;
4036 }
4038 static bool
4039 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4040 {
4041 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4042 char ref[SIZEOF_STR];
4044 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4045 return TRUE;
4047 /* This is the only fatal call, since it can "corrupt" the buffer. */
4048 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4049 return FALSE;
4051 return TRUE;
4052 }
4054 static void
4055 add_pager_refs(struct view *view, struct line *line)
4056 {
4057 char buf[SIZEOF_STR];
4058 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4059 struct ref_list *list;
4060 size_t bufpos = 0, i;
4061 const char *sep = "Refs: ";
4062 bool is_tag = FALSE;
4064 assert(line->type == LINE_COMMIT);
4066 list = get_ref_list(commit_id);
4067 if (!list) {
4068 if (view->type == VIEW_DIFF)
4069 goto try_add_describe_ref;
4070 return;
4071 }
4073 for (i = 0; i < list->size; i++) {
4074 struct ref *ref = list->refs[i];
4075 const char *fmt = ref->tag ? "%s[%s]" :
4076 ref->remote ? "%s<%s>" : "%s%s";
4078 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4079 return;
4080 sep = ", ";
4081 if (ref->tag)
4082 is_tag = TRUE;
4083 }
4085 if (!is_tag && view->type == VIEW_DIFF) {
4086 try_add_describe_ref:
4087 /* Add <tag>-g<commit_id> "fake" reference. */
4088 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4089 return;
4090 }
4092 if (bufpos == 0)
4093 return;
4095 add_line_text(view, buf, LINE_PP_REFS);
4096 }
4098 static bool
4099 pager_read(struct view *view, char *data)
4100 {
4101 struct line *line;
4103 if (!data)
4104 return TRUE;
4106 line = add_line_text(view, data, get_line_type(data));
4107 if (!line)
4108 return FALSE;
4110 if (line->type == LINE_COMMIT &&
4111 (view->type == VIEW_DIFF ||
4112 view->type == VIEW_LOG))
4113 add_pager_refs(view, line);
4115 return TRUE;
4116 }
4118 static enum request
4119 pager_request(struct view *view, enum request request, struct line *line)
4120 {
4121 int split = 0;
4123 if (request != REQ_ENTER)
4124 return request;
4126 if (line->type == LINE_COMMIT &&
4127 (view->type == VIEW_LOG ||
4128 view->type == VIEW_PAGER)) {
4129 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4130 split = 1;
4131 }
4133 /* Always scroll the view even if it was split. That way
4134 * you can use Enter to scroll through the log view and
4135 * split open each commit diff. */
4136 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4138 /* FIXME: A minor workaround. Scrolling the view will call report("")
4139 * but if we are scrolling a non-current view this won't properly
4140 * update the view title. */
4141 if (split)
4142 update_view_title(view);
4144 return REQ_NONE;
4145 }
4147 static bool
4148 pager_grep(struct view *view, struct line *line)
4149 {
4150 const char *text[] = { line->data, NULL };
4152 return grep_text(view, text);
4153 }
4155 static void
4156 pager_select(struct view *view, struct line *line)
4157 {
4158 if (line->type == LINE_COMMIT) {
4159 char *text = (char *)line->data + STRING_SIZE("commit ");
4161 if (view->type != VIEW_PAGER)
4162 string_copy_rev(view->ref, text);
4163 string_copy_rev(ref_commit, text);
4164 }
4165 }
4167 static struct view_ops pager_ops = {
4168 "line",
4169 NULL,
4170 NULL,
4171 pager_read,
4172 pager_draw,
4173 pager_request,
4174 pager_grep,
4175 pager_select,
4176 };
4178 static const char *log_argv[SIZEOF_ARG] = {
4179 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4180 };
4182 static enum request
4183 log_request(struct view *view, enum request request, struct line *line)
4184 {
4185 switch (request) {
4186 case REQ_REFRESH:
4187 load_refs();
4188 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4189 return REQ_NONE;
4190 default:
4191 return pager_request(view, request, line);
4192 }
4193 }
4195 static struct view_ops log_ops = {
4196 "line",
4197 log_argv,
4198 NULL,
4199 pager_read,
4200 pager_draw,
4201 log_request,
4202 pager_grep,
4203 pager_select,
4204 };
4206 static const char *diff_argv[SIZEOF_ARG] = {
4207 "git", "show", "--pretty=fuller", "--no-color", "--root",
4208 "--patch-with-stat", "--find-copies-harder", "-C",
4209 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4210 };
4212 static bool
4213 diff_read(struct view *view, char *data)
4214 {
4215 if (!data) {
4216 /* Fall back to retry if no diff will be shown. */
4217 if (view->lines == 0 && opt_file_args) {
4218 int pos = argv_size(view->argv)
4219 - argv_size(opt_file_args) - 1;
4221 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4222 for (; view->argv[pos]; pos++) {
4223 free((void *) view->argv[pos]);
4224 view->argv[pos] = NULL;
4225 }
4227 if (view->pipe)
4228 io_done(view->pipe);
4229 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4230 return FALSE;
4231 }
4232 }
4233 return TRUE;
4234 }
4236 return pager_read(view, data);
4237 }
4239 static struct view_ops diff_ops = {
4240 "line",
4241 diff_argv,
4242 NULL,
4243 diff_read,
4244 pager_draw,
4245 pager_request,
4246 pager_grep,
4247 pager_select,
4248 };
4250 /*
4251 * Help backend
4252 */
4254 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4256 static bool
4257 help_open_keymap_title(struct view *view, enum keymap keymap)
4258 {
4259 struct line *line;
4261 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4262 help_keymap_hidden[keymap] ? '+' : '-',
4263 enum_name(keymap_table[keymap]));
4264 if (line)
4265 line->other = keymap;
4267 return help_keymap_hidden[keymap];
4268 }
4270 static void
4271 help_open_keymap(struct view *view, enum keymap keymap)
4272 {
4273 const char *group = NULL;
4274 char buf[SIZEOF_STR];
4275 size_t bufpos;
4276 bool add_title = TRUE;
4277 int i;
4279 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4280 const char *key = NULL;
4282 if (req_info[i].request == REQ_NONE)
4283 continue;
4285 if (!req_info[i].request) {
4286 group = req_info[i].help;
4287 continue;
4288 }
4290 key = get_keys(keymap, req_info[i].request, TRUE);
4291 if (!key || !*key)
4292 continue;
4294 if (add_title && help_open_keymap_title(view, keymap))
4295 return;
4296 add_title = FALSE;
4298 if (group) {
4299 add_line_text(view, group, LINE_HELP_GROUP);
4300 group = NULL;
4301 }
4303 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4304 enum_name(req_info[i]), req_info[i].help);
4305 }
4307 group = "External commands:";
4309 for (i = 0; i < run_requests; i++) {
4310 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4311 const char *key;
4312 int argc;
4314 if (!req || req->keymap != keymap)
4315 continue;
4317 key = get_key_name(req->key);
4318 if (!*key)
4319 key = "(no key defined)";
4321 if (add_title && help_open_keymap_title(view, keymap))
4322 return;
4323 if (group) {
4324 add_line_text(view, group, LINE_HELP_GROUP);
4325 group = NULL;
4326 }
4328 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4329 if (!string_format_from(buf, &bufpos, "%s%s",
4330 argc ? " " : "", req->argv[argc]))
4331 return;
4333 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4334 }
4335 }
4337 static bool
4338 help_open(struct view *view)
4339 {
4340 enum keymap keymap;
4342 reset_view(view);
4343 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4344 add_line_text(view, "", LINE_DEFAULT);
4346 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4347 help_open_keymap(view, keymap);
4349 return TRUE;
4350 }
4352 static enum request
4353 help_request(struct view *view, enum request request, struct line *line)
4354 {
4355 switch (request) {
4356 case REQ_ENTER:
4357 if (line->type == LINE_HELP_KEYMAP) {
4358 help_keymap_hidden[line->other] =
4359 !help_keymap_hidden[line->other];
4360 view->p_restore = TRUE;
4361 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4362 }
4364 return REQ_NONE;
4365 default:
4366 return pager_request(view, request, line);
4367 }
4368 }
4370 static struct view_ops help_ops = {
4371 "line",
4372 NULL,
4373 help_open,
4374 NULL,
4375 pager_draw,
4376 help_request,
4377 pager_grep,
4378 pager_select,
4379 };
4382 /*
4383 * Tree backend
4384 */
4386 struct tree_stack_entry {
4387 struct tree_stack_entry *prev; /* Entry below this in the stack */
4388 unsigned long lineno; /* Line number to restore */
4389 char *name; /* Position of name in opt_path */
4390 };
4392 /* The top of the path stack. */
4393 static struct tree_stack_entry *tree_stack = NULL;
4394 unsigned long tree_lineno = 0;
4396 static void
4397 pop_tree_stack_entry(void)
4398 {
4399 struct tree_stack_entry *entry = tree_stack;
4401 tree_lineno = entry->lineno;
4402 entry->name[0] = 0;
4403 tree_stack = entry->prev;
4404 free(entry);
4405 }
4407 static void
4408 push_tree_stack_entry(const char *name, unsigned long lineno)
4409 {
4410 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4411 size_t pathlen = strlen(opt_path);
4413 if (!entry)
4414 return;
4416 entry->prev = tree_stack;
4417 entry->name = opt_path + pathlen;
4418 tree_stack = entry;
4420 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4421 pop_tree_stack_entry();
4422 return;
4423 }
4425 /* Move the current line to the first tree entry. */
4426 tree_lineno = 1;
4427 entry->lineno = lineno;
4428 }
4430 /* Parse output from git-ls-tree(1):
4431 *
4432 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4433 */
4435 #define SIZEOF_TREE_ATTR \
4436 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4438 #define SIZEOF_TREE_MODE \
4439 STRING_SIZE("100644 ")
4441 #define TREE_ID_OFFSET \
4442 STRING_SIZE("100644 blob ")
4444 struct tree_entry {
4445 char id[SIZEOF_REV];
4446 mode_t mode;
4447 struct time time; /* Date from the author ident. */
4448 const char *author; /* Author of the commit. */
4449 char name[1];
4450 };
4452 static const char *
4453 tree_path(const struct line *line)
4454 {
4455 return ((struct tree_entry *) line->data)->name;
4456 }
4458 static int
4459 tree_compare_entry(const struct line *line1, const struct line *line2)
4460 {
4461 if (line1->type != line2->type)
4462 return line1->type == LINE_TREE_DIR ? -1 : 1;
4463 return strcmp(tree_path(line1), tree_path(line2));
4464 }
4466 static const enum sort_field tree_sort_fields[] = {
4467 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4468 };
4469 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4471 static int
4472 tree_compare(const void *l1, const void *l2)
4473 {
4474 const struct line *line1 = (const struct line *) l1;
4475 const struct line *line2 = (const struct line *) l2;
4476 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4477 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4479 if (line1->type == LINE_TREE_HEAD)
4480 return -1;
4481 if (line2->type == LINE_TREE_HEAD)
4482 return 1;
4484 switch (get_sort_field(tree_sort_state)) {
4485 case ORDERBY_DATE:
4486 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4488 case ORDERBY_AUTHOR:
4489 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4491 case ORDERBY_NAME:
4492 default:
4493 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4494 }
4495 }
4498 static struct line *
4499 tree_entry(struct view *view, enum line_type type, const char *path,
4500 const char *mode, const char *id)
4501 {
4502 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4503 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4505 if (!entry || !line) {
4506 free(entry);
4507 return NULL;
4508 }
4510 strncpy(entry->name, path, strlen(path));
4511 if (mode)
4512 entry->mode = strtoul(mode, NULL, 8);
4513 if (id)
4514 string_copy_rev(entry->id, id);
4516 return line;
4517 }
4519 static bool
4520 tree_read_date(struct view *view, char *text, bool *read_date)
4521 {
4522 static const char *author_name;
4523 static struct time author_time;
4525 if (!text && *read_date) {
4526 *read_date = FALSE;
4527 return TRUE;
4529 } else if (!text) {
4530 char *path = *opt_path ? opt_path : ".";
4531 /* Find next entry to process */
4532 const char *log_file[] = {
4533 "git", "log", "--no-color", "--pretty=raw",
4534 "--cc", "--raw", view->id, "--", path, NULL
4535 };
4537 if (!view->lines) {
4538 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4539 report("Tree is empty");
4540 return TRUE;
4541 }
4543 if (!start_update(view, log_file, opt_cdup)) {
4544 report("Failed to load tree data");
4545 return TRUE;
4546 }
4548 *read_date = TRUE;
4549 return FALSE;
4551 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4552 parse_author_line(text + STRING_SIZE("author "),
4553 &author_name, &author_time);
4555 } else if (*text == ':') {
4556 char *pos;
4557 size_t annotated = 1;
4558 size_t i;
4560 pos = strchr(text, '\t');
4561 if (!pos)
4562 return TRUE;
4563 text = pos + 1;
4564 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4565 text += strlen(opt_path);
4566 pos = strchr(text, '/');
4567 if (pos)
4568 *pos = 0;
4570 for (i = 1; i < view->lines; i++) {
4571 struct line *line = &view->line[i];
4572 struct tree_entry *entry = line->data;
4574 annotated += !!entry->author;
4575 if (entry->author || strcmp(entry->name, text))
4576 continue;
4578 entry->author = author_name;
4579 entry->time = author_time;
4580 line->dirty = 1;
4581 break;
4582 }
4584 if (annotated == view->lines)
4585 io_kill(view->pipe);
4586 }
4587 return TRUE;
4588 }
4590 static bool
4591 tree_read(struct view *view, char *text)
4592 {
4593 static bool read_date = FALSE;
4594 struct tree_entry *data;
4595 struct line *entry, *line;
4596 enum line_type type;
4597 size_t textlen = text ? strlen(text) : 0;
4598 char *path = text + SIZEOF_TREE_ATTR;
4600 if (read_date || !text)
4601 return tree_read_date(view, text, &read_date);
4603 if (textlen <= SIZEOF_TREE_ATTR)
4604 return FALSE;
4605 if (view->lines == 0 &&
4606 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4607 return FALSE;
4609 /* Strip the path part ... */
4610 if (*opt_path) {
4611 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4612 size_t striplen = strlen(opt_path);
4614 if (pathlen > striplen)
4615 memmove(path, path + striplen,
4616 pathlen - striplen + 1);
4618 /* Insert "link" to parent directory. */
4619 if (view->lines == 1 &&
4620 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4621 return FALSE;
4622 }
4624 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4625 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4626 if (!entry)
4627 return FALSE;
4628 data = entry->data;
4630 /* Skip "Directory ..." and ".." line. */
4631 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4632 if (tree_compare_entry(line, entry) <= 0)
4633 continue;
4635 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4637 line->data = data;
4638 line->type = type;
4639 for (; line <= entry; line++)
4640 line->dirty = line->cleareol = 1;
4641 return TRUE;
4642 }
4644 if (tree_lineno > view->lineno) {
4645 view->lineno = tree_lineno;
4646 tree_lineno = 0;
4647 }
4649 return TRUE;
4650 }
4652 static bool
4653 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4654 {
4655 struct tree_entry *entry = line->data;
4657 if (line->type == LINE_TREE_HEAD) {
4658 if (draw_text(view, line->type, "Directory path /", TRUE))
4659 return TRUE;
4660 } else {
4661 if (draw_mode(view, entry->mode))
4662 return TRUE;
4664 if (opt_author && draw_author(view, entry->author))
4665 return TRUE;
4667 if (opt_date && draw_date(view, &entry->time))
4668 return TRUE;
4669 }
4670 if (draw_text(view, line->type, entry->name, TRUE))
4671 return TRUE;
4672 return TRUE;
4673 }
4675 static void
4676 open_blob_editor(const char *id)
4677 {
4678 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4679 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4680 int fd = mkstemp(file);
4682 if (fd == -1)
4683 report("Failed to create temporary file");
4684 else if (!io_run_append(blob_argv, fd))
4685 report("Failed to save blob data to file");
4686 else
4687 open_editor(file);
4688 if (fd != -1)
4689 unlink(file);
4690 }
4692 static enum request
4693 tree_request(struct view *view, enum request request, struct line *line)
4694 {
4695 enum open_flags flags;
4696 struct tree_entry *entry = line->data;
4698 switch (request) {
4699 case REQ_VIEW_BLAME:
4700 if (line->type != LINE_TREE_FILE) {
4701 report("Blame only supported for files");
4702 return REQ_NONE;
4703 }
4705 string_copy(opt_ref, view->vid);
4706 return request;
4708 case REQ_EDIT:
4709 if (line->type != LINE_TREE_FILE) {
4710 report("Edit only supported for files");
4711 } else if (!is_head_commit(view->vid)) {
4712 open_blob_editor(entry->id);
4713 } else {
4714 open_editor(opt_file);
4715 }
4716 return REQ_NONE;
4718 case REQ_TOGGLE_SORT_FIELD:
4719 case REQ_TOGGLE_SORT_ORDER:
4720 sort_view(view, request, &tree_sort_state, tree_compare);
4721 return REQ_NONE;
4723 case REQ_PARENT:
4724 if (!*opt_path) {
4725 /* quit view if at top of tree */
4726 return REQ_VIEW_CLOSE;
4727 }
4728 /* fake 'cd ..' */
4729 line = &view->line[1];
4730 break;
4732 case REQ_ENTER:
4733 break;
4735 default:
4736 return request;
4737 }
4739 /* Cleanup the stack if the tree view is at a different tree. */
4740 while (!*opt_path && tree_stack)
4741 pop_tree_stack_entry();
4743 switch (line->type) {
4744 case LINE_TREE_DIR:
4745 /* Depending on whether it is a subdirectory or parent link
4746 * mangle the path buffer. */
4747 if (line == &view->line[1] && *opt_path) {
4748 pop_tree_stack_entry();
4750 } else {
4751 const char *basename = tree_path(line);
4753 push_tree_stack_entry(basename, view->lineno);
4754 }
4756 /* Trees and subtrees share the same ID, so they are not not
4757 * unique like blobs. */
4758 flags = OPEN_RELOAD;
4759 request = REQ_VIEW_TREE;
4760 break;
4762 case LINE_TREE_FILE:
4763 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4764 request = REQ_VIEW_BLOB;
4765 break;
4767 default:
4768 return REQ_NONE;
4769 }
4771 open_view(view, request, flags);
4772 if (request == REQ_VIEW_TREE)
4773 view->lineno = tree_lineno;
4775 return REQ_NONE;
4776 }
4778 static bool
4779 tree_grep(struct view *view, struct line *line)
4780 {
4781 struct tree_entry *entry = line->data;
4782 const char *text[] = {
4783 entry->name,
4784 opt_author ? entry->author : "",
4785 mkdate(&entry->time, opt_date),
4786 NULL
4787 };
4789 return grep_text(view, text);
4790 }
4792 static void
4793 tree_select(struct view *view, struct line *line)
4794 {
4795 struct tree_entry *entry = line->data;
4797 if (line->type == LINE_TREE_FILE) {
4798 string_copy_rev(ref_blob, entry->id);
4799 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4801 } else if (line->type != LINE_TREE_DIR) {
4802 return;
4803 }
4805 string_copy_rev(view->ref, entry->id);
4806 }
4808 static bool
4809 tree_prepare(struct view *view)
4810 {
4811 if (view->lines == 0 && opt_prefix[0]) {
4812 char *pos = opt_prefix;
4814 while (pos && *pos) {
4815 char *end = strchr(pos, '/');
4817 if (end)
4818 *end = 0;
4819 push_tree_stack_entry(pos, 0);
4820 pos = end;
4821 if (end) {
4822 *end = '/';
4823 pos++;
4824 }
4825 }
4827 } else if (strcmp(view->vid, view->id)) {
4828 opt_path[0] = 0;
4829 }
4831 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4832 }
4834 static const char *tree_argv[SIZEOF_ARG] = {
4835 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4836 };
4838 static struct view_ops tree_ops = {
4839 "file",
4840 tree_argv,
4841 NULL,
4842 tree_read,
4843 tree_draw,
4844 tree_request,
4845 tree_grep,
4846 tree_select,
4847 tree_prepare,
4848 };
4850 static bool
4851 blob_read(struct view *view, char *line)
4852 {
4853 if (!line)
4854 return TRUE;
4855 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4856 }
4858 static enum request
4859 blob_request(struct view *view, enum request request, struct line *line)
4860 {
4861 switch (request) {
4862 case REQ_EDIT:
4863 open_blob_editor(view->vid);
4864 return REQ_NONE;
4865 default:
4866 return pager_request(view, request, line);
4867 }
4868 }
4870 static const char *blob_argv[SIZEOF_ARG] = {
4871 "git", "cat-file", "blob", "%(blob)", NULL
4872 };
4874 static struct view_ops blob_ops = {
4875 "line",
4876 blob_argv,
4877 NULL,
4878 blob_read,
4879 pager_draw,
4880 blob_request,
4881 pager_grep,
4882 pager_select,
4883 };
4885 /*
4886 * Blame backend
4887 *
4888 * Loading the blame view is a two phase job:
4889 *
4890 * 1. File content is read either using opt_file from the
4891 * filesystem or using git-cat-file.
4892 * 2. Then blame information is incrementally added by
4893 * reading output from git-blame.
4894 */
4896 struct blame_commit {
4897 char id[SIZEOF_REV]; /* SHA1 ID. */
4898 char title[128]; /* First line of the commit message. */
4899 const char *author; /* Author of the commit. */
4900 struct time time; /* Date from the author ident. */
4901 char filename[128]; /* Name of file. */
4902 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4903 char parent_filename[128]; /* Parent/previous name of file. */
4904 };
4906 struct blame {
4907 struct blame_commit *commit;
4908 unsigned long lineno;
4909 char text[1];
4910 };
4912 static bool
4913 blame_open(struct view *view)
4914 {
4915 char path[SIZEOF_STR];
4916 size_t i;
4918 if (!view->prev && *opt_prefix) {
4919 string_copy(path, opt_file);
4920 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4921 return FALSE;
4922 }
4924 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4925 const char *blame_cat_file_argv[] = {
4926 "git", "cat-file", "blob", path, NULL
4927 };
4929 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4930 !start_update(view, blame_cat_file_argv, opt_cdup))
4931 return FALSE;
4932 }
4934 /* First pass: remove multiple references to the same commit. */
4935 for (i = 0; i < view->lines; i++) {
4936 struct blame *blame = view->line[i].data;
4938 if (blame->commit && blame->commit->id[0])
4939 blame->commit->id[0] = 0;
4940 else
4941 blame->commit = NULL;
4942 }
4944 /* Second pass: free existing references. */
4945 for (i = 0; i < view->lines; i++) {
4946 struct blame *blame = view->line[i].data;
4948 if (blame->commit)
4949 free(blame->commit);
4950 }
4952 setup_update(view, opt_file);
4953 string_format(view->ref, "%s ...", opt_file);
4955 return TRUE;
4956 }
4958 static struct blame_commit *
4959 get_blame_commit(struct view *view, const char *id)
4960 {
4961 size_t i;
4963 for (i = 0; i < view->lines; i++) {
4964 struct blame *blame = view->line[i].data;
4966 if (!blame->commit)
4967 continue;
4969 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4970 return blame->commit;
4971 }
4973 {
4974 struct blame_commit *commit = calloc(1, sizeof(*commit));
4976 if (commit)
4977 string_ncopy(commit->id, id, SIZEOF_REV);
4978 return commit;
4979 }
4980 }
4982 static bool
4983 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4984 {
4985 const char *pos = *posref;
4987 *posref = NULL;
4988 pos = strchr(pos + 1, ' ');
4989 if (!pos || !isdigit(pos[1]))
4990 return FALSE;
4991 *number = atoi(pos + 1);
4992 if (*number < min || *number > max)
4993 return FALSE;
4995 *posref = pos;
4996 return TRUE;
4997 }
4999 static struct blame_commit *
5000 parse_blame_commit(struct view *view, const char *text, int *blamed)
5001 {
5002 struct blame_commit *commit;
5003 struct blame *blame;
5004 const char *pos = text + SIZEOF_REV - 2;
5005 size_t orig_lineno = 0;
5006 size_t lineno;
5007 size_t group;
5009 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5010 return NULL;
5012 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5013 !parse_number(&pos, &lineno, 1, view->lines) ||
5014 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5015 return NULL;
5017 commit = get_blame_commit(view, text);
5018 if (!commit)
5019 return NULL;
5021 *blamed += group;
5022 while (group--) {
5023 struct line *line = &view->line[lineno + group - 1];
5025 blame = line->data;
5026 blame->commit = commit;
5027 blame->lineno = orig_lineno + group - 1;
5028 line->dirty = 1;
5029 }
5031 return commit;
5032 }
5034 static bool
5035 blame_read_file(struct view *view, const char *line, bool *read_file)
5036 {
5037 if (!line) {
5038 const char *blame_argv[] = {
5039 "git", "blame", "--incremental",
5040 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5041 };
5043 if (view->lines == 0 && !view->prev)
5044 die("No blame exist for %s", view->vid);
5046 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5047 report("Failed to load blame data");
5048 return TRUE;
5049 }
5051 *read_file = FALSE;
5052 return FALSE;
5054 } else {
5055 size_t linelen = strlen(line);
5056 struct blame *blame = malloc(sizeof(*blame) + linelen);
5058 if (!blame)
5059 return FALSE;
5061 blame->commit = NULL;
5062 strncpy(blame->text, line, linelen);
5063 blame->text[linelen] = 0;
5064 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5065 }
5066 }
5068 static bool
5069 match_blame_header(const char *name, char **line)
5070 {
5071 size_t namelen = strlen(name);
5072 bool matched = !strncmp(name, *line, namelen);
5074 if (matched)
5075 *line += namelen;
5077 return matched;
5078 }
5080 static bool
5081 blame_read(struct view *view, char *line)
5082 {
5083 static struct blame_commit *commit = NULL;
5084 static int blamed = 0;
5085 static bool read_file = TRUE;
5087 if (read_file)
5088 return blame_read_file(view, line, &read_file);
5090 if (!line) {
5091 /* Reset all! */
5092 commit = NULL;
5093 blamed = 0;
5094 read_file = TRUE;
5095 string_format(view->ref, "%s", view->vid);
5096 if (view_is_displayed(view)) {
5097 update_view_title(view);
5098 redraw_view_from(view, 0);
5099 }
5100 return TRUE;
5101 }
5103 if (!commit) {
5104 commit = parse_blame_commit(view, line, &blamed);
5105 string_format(view->ref, "%s %2d%%", view->vid,
5106 view->lines ? blamed * 100 / view->lines : 0);
5108 } else if (match_blame_header("author ", &line)) {
5109 commit->author = get_author(line);
5111 } else if (match_blame_header("author-time ", &line)) {
5112 parse_timesec(&commit->time, line);
5114 } else if (match_blame_header("author-tz ", &line)) {
5115 parse_timezone(&commit->time, line);
5117 } else if (match_blame_header("summary ", &line)) {
5118 string_ncopy(commit->title, line, strlen(line));
5120 } else if (match_blame_header("previous ", &line)) {
5121 if (strlen(line) <= SIZEOF_REV)
5122 return FALSE;
5123 string_copy_rev(commit->parent_id, line);
5124 line += SIZEOF_REV;
5125 string_ncopy(commit->parent_filename, line, strlen(line));
5127 } else if (match_blame_header("filename ", &line)) {
5128 string_ncopy(commit->filename, line, strlen(line));
5129 commit = NULL;
5130 }
5132 return TRUE;
5133 }
5135 static bool
5136 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5137 {
5138 struct blame *blame = line->data;
5139 struct time *time = NULL;
5140 const char *id = NULL, *author = NULL;
5142 if (blame->commit && *blame->commit->filename) {
5143 id = blame->commit->id;
5144 author = blame->commit->author;
5145 time = &blame->commit->time;
5146 }
5148 if (opt_date && draw_date(view, time))
5149 return TRUE;
5151 if (opt_author && draw_author(view, author))
5152 return TRUE;
5154 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5155 return TRUE;
5157 if (draw_lineno(view, lineno))
5158 return TRUE;
5160 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5161 return TRUE;
5162 }
5164 static bool
5165 check_blame_commit(struct blame *blame, bool check_null_id)
5166 {
5167 if (!blame->commit)
5168 report("Commit data not loaded yet");
5169 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5170 report("No commit exist for the selected line");
5171 else
5172 return TRUE;
5173 return FALSE;
5174 }
5176 static void
5177 setup_blame_parent_line(struct view *view, struct blame *blame)
5178 {
5179 char from[SIZEOF_REF + SIZEOF_STR];
5180 char to[SIZEOF_REF + SIZEOF_STR];
5181 const char *diff_tree_argv[] = {
5182 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5183 "-U0", from, to, "--", NULL
5184 };
5185 struct io io;
5186 int parent_lineno = -1;
5187 int blamed_lineno = -1;
5188 char *line;
5190 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5191 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5192 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5193 return;
5195 while ((line = io_get(&io, '\n', TRUE))) {
5196 if (*line == '@') {
5197 char *pos = strchr(line, '+');
5199 parent_lineno = atoi(line + 4);
5200 if (pos)
5201 blamed_lineno = atoi(pos + 1);
5203 } else if (*line == '+' && parent_lineno != -1) {
5204 if (blame->lineno == blamed_lineno - 1 &&
5205 !strcmp(blame->text, line + 1)) {
5206 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5207 break;
5208 }
5209 blamed_lineno++;
5210 }
5211 }
5213 io_done(&io);
5214 }
5216 static enum request
5217 blame_request(struct view *view, enum request request, struct line *line)
5218 {
5219 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5220 struct blame *blame = line->data;
5222 switch (request) {
5223 case REQ_VIEW_BLAME:
5224 if (check_blame_commit(blame, TRUE)) {
5225 string_copy(opt_ref, blame->commit->id);
5226 string_copy(opt_file, blame->commit->filename);
5227 if (blame->lineno)
5228 view->lineno = blame->lineno;
5229 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5230 }
5231 break;
5233 case REQ_PARENT:
5234 if (!check_blame_commit(blame, TRUE))
5235 break;
5236 if (!*blame->commit->parent_id) {
5237 report("The selected commit has no parents");
5238 } else {
5239 string_copy_rev(opt_ref, blame->commit->parent_id);
5240 string_copy(opt_file, blame->commit->parent_filename);
5241 setup_blame_parent_line(view, blame);
5242 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5243 }
5244 break;
5246 case REQ_ENTER:
5247 if (!check_blame_commit(blame, FALSE))
5248 break;
5250 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5251 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5252 break;
5254 if (!strcmp(blame->commit->id, NULL_ID)) {
5255 struct view *diff = VIEW(REQ_VIEW_DIFF);
5256 const char *diff_index_argv[] = {
5257 "git", "diff-index", "--root", "--patch-with-stat",
5258 "-C", "-M", "HEAD", "--", view->vid, NULL
5259 };
5261 if (!*blame->commit->parent_id) {
5262 diff_index_argv[1] = "diff";
5263 diff_index_argv[2] = "--no-color";
5264 diff_index_argv[6] = "--";
5265 diff_index_argv[7] = "/dev/null";
5266 }
5268 if (!prepare_update(diff, diff_index_argv, NULL)) {
5269 report("Failed to allocate diff command");
5270 break;
5271 }
5272 flags |= OPEN_PREPARED;
5273 }
5275 open_view(view, REQ_VIEW_DIFF, flags);
5276 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5277 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5278 break;
5280 default:
5281 return request;
5282 }
5284 return REQ_NONE;
5285 }
5287 static bool
5288 blame_grep(struct view *view, struct line *line)
5289 {
5290 struct blame *blame = line->data;
5291 struct blame_commit *commit = blame->commit;
5292 const char *text[] = {
5293 blame->text,
5294 commit ? commit->title : "",
5295 commit ? commit->id : "",
5296 commit && opt_author ? commit->author : "",
5297 commit ? mkdate(&commit->time, opt_date) : "",
5298 NULL
5299 };
5301 return grep_text(view, text);
5302 }
5304 static void
5305 blame_select(struct view *view, struct line *line)
5306 {
5307 struct blame *blame = line->data;
5308 struct blame_commit *commit = blame->commit;
5310 if (!commit)
5311 return;
5313 if (!strcmp(commit->id, NULL_ID))
5314 string_ncopy(ref_commit, "HEAD", 4);
5315 else
5316 string_copy_rev(ref_commit, commit->id);
5317 }
5319 static struct view_ops blame_ops = {
5320 "line",
5321 NULL,
5322 blame_open,
5323 blame_read,
5324 blame_draw,
5325 blame_request,
5326 blame_grep,
5327 blame_select,
5328 };
5330 /*
5331 * Branch backend
5332 */
5334 struct branch {
5335 const char *author; /* Author of the last commit. */
5336 struct time time; /* Date of the last activity. */
5337 const struct ref *ref; /* Name and commit ID information. */
5338 };
5340 static const struct ref branch_all;
5342 static const enum sort_field branch_sort_fields[] = {
5343 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5344 };
5345 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5347 static int
5348 branch_compare(const void *l1, const void *l2)
5349 {
5350 const struct branch *branch1 = ((const struct line *) l1)->data;
5351 const struct branch *branch2 = ((const struct line *) l2)->data;
5353 switch (get_sort_field(branch_sort_state)) {
5354 case ORDERBY_DATE:
5355 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5357 case ORDERBY_AUTHOR:
5358 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5360 case ORDERBY_NAME:
5361 default:
5362 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5363 }
5364 }
5366 static bool
5367 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5368 {
5369 struct branch *branch = line->data;
5370 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5372 if (opt_date && draw_date(view, &branch->time))
5373 return TRUE;
5375 if (opt_author && draw_author(view, branch->author))
5376 return TRUE;
5378 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5379 return TRUE;
5380 }
5382 static enum request
5383 branch_request(struct view *view, enum request request, struct line *line)
5384 {
5385 struct branch *branch = line->data;
5387 switch (request) {
5388 case REQ_REFRESH:
5389 load_refs();
5390 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5391 return REQ_NONE;
5393 case REQ_TOGGLE_SORT_FIELD:
5394 case REQ_TOGGLE_SORT_ORDER:
5395 sort_view(view, request, &branch_sort_state, branch_compare);
5396 return REQ_NONE;
5398 case REQ_ENTER:
5399 {
5400 const struct ref *ref = branch->ref;
5401 const char *all_branches_argv[] = {
5402 "git", "log", "--no-color", "--pretty=raw", "--parents",
5403 "--topo-order",
5404 ref == &branch_all ? "--all" : ref->name, NULL
5405 };
5406 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5408 if (!prepare_update(main_view, all_branches_argv, NULL))
5409 report("Failed to load view of all branches");
5410 else
5411 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5412 return REQ_NONE;
5413 }
5414 default:
5415 return request;
5416 }
5417 }
5419 static bool
5420 branch_read(struct view *view, char *line)
5421 {
5422 static char id[SIZEOF_REV];
5423 struct branch *reference;
5424 size_t i;
5426 if (!line)
5427 return TRUE;
5429 switch (get_line_type(line)) {
5430 case LINE_COMMIT:
5431 string_copy_rev(id, line + STRING_SIZE("commit "));
5432 return TRUE;
5434 case LINE_AUTHOR:
5435 for (i = 0, reference = NULL; i < view->lines; i++) {
5436 struct branch *branch = view->line[i].data;
5438 if (strcmp(branch->ref->id, id))
5439 continue;
5441 view->line[i].dirty = TRUE;
5442 if (reference) {
5443 branch->author = reference->author;
5444 branch->time = reference->time;
5445 continue;
5446 }
5448 parse_author_line(line + STRING_SIZE("author "),
5449 &branch->author, &branch->time);
5450 reference = branch;
5451 }
5452 return TRUE;
5454 default:
5455 return TRUE;
5456 }
5458 }
5460 static bool
5461 branch_open_visitor(void *data, const struct ref *ref)
5462 {
5463 struct view *view = data;
5464 struct branch *branch;
5466 if (ref->tag || ref->ltag || ref->remote)
5467 return TRUE;
5469 branch = calloc(1, sizeof(*branch));
5470 if (!branch)
5471 return FALSE;
5473 branch->ref = ref;
5474 return !!add_line_data(view, branch, LINE_DEFAULT);
5475 }
5477 static bool
5478 branch_open(struct view *view)
5479 {
5480 const char *branch_log[] = {
5481 "git", "log", "--no-color", "--pretty=raw",
5482 "--simplify-by-decoration", "--all", NULL
5483 };
5485 if (!start_update(view, branch_log, NULL)) {
5486 report("Failed to load branch data");
5487 return TRUE;
5488 }
5490 setup_update(view, view->id);
5491 branch_open_visitor(view, &branch_all);
5492 foreach_ref(branch_open_visitor, view);
5493 view->p_restore = TRUE;
5495 return TRUE;
5496 }
5498 static bool
5499 branch_grep(struct view *view, struct line *line)
5500 {
5501 struct branch *branch = line->data;
5502 const char *text[] = {
5503 branch->ref->name,
5504 branch->author,
5505 NULL
5506 };
5508 return grep_text(view, text);
5509 }
5511 static void
5512 branch_select(struct view *view, struct line *line)
5513 {
5514 struct branch *branch = line->data;
5516 string_copy_rev(view->ref, branch->ref->id);
5517 string_copy_rev(ref_commit, branch->ref->id);
5518 string_copy_rev(ref_head, branch->ref->id);
5519 string_copy_rev(ref_branch, branch->ref->name);
5520 }
5522 static struct view_ops branch_ops = {
5523 "branch",
5524 NULL,
5525 branch_open,
5526 branch_read,
5527 branch_draw,
5528 branch_request,
5529 branch_grep,
5530 branch_select,
5531 };
5533 /*
5534 * Status backend
5535 */
5537 struct status {
5538 char status;
5539 struct {
5540 mode_t mode;
5541 char rev[SIZEOF_REV];
5542 char name[SIZEOF_STR];
5543 } old;
5544 struct {
5545 mode_t mode;
5546 char rev[SIZEOF_REV];
5547 char name[SIZEOF_STR];
5548 } new;
5549 };
5551 static char status_onbranch[SIZEOF_STR];
5552 static struct status stage_status;
5553 static enum line_type stage_line_type;
5554 static size_t stage_chunks;
5555 static int *stage_chunk;
5557 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5559 /* This should work even for the "On branch" line. */
5560 static inline bool
5561 status_has_none(struct view *view, struct line *line)
5562 {
5563 return line < view->line + view->lines && !line[1].data;
5564 }
5566 /* Get fields from the diff line:
5567 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5568 */
5569 static inline bool
5570 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5571 {
5572 const char *old_mode = buf + 1;
5573 const char *new_mode = buf + 8;
5574 const char *old_rev = buf + 15;
5575 const char *new_rev = buf + 56;
5576 const char *status = buf + 97;
5578 if (bufsize < 98 ||
5579 old_mode[-1] != ':' ||
5580 new_mode[-1] != ' ' ||
5581 old_rev[-1] != ' ' ||
5582 new_rev[-1] != ' ' ||
5583 status[-1] != ' ')
5584 return FALSE;
5586 file->status = *status;
5588 string_copy_rev(file->old.rev, old_rev);
5589 string_copy_rev(file->new.rev, new_rev);
5591 file->old.mode = strtoul(old_mode, NULL, 8);
5592 file->new.mode = strtoul(new_mode, NULL, 8);
5594 file->old.name[0] = file->new.name[0] = 0;
5596 return TRUE;
5597 }
5599 static bool
5600 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5601 {
5602 struct status *unmerged = NULL;
5603 char *buf;
5604 struct io io;
5606 if (!io_run(&io, IO_RD, opt_cdup, argv))
5607 return FALSE;
5609 add_line_data(view, NULL, type);
5611 while ((buf = io_get(&io, 0, TRUE))) {
5612 struct status *file = unmerged;
5614 if (!file) {
5615 file = calloc(1, sizeof(*file));
5616 if (!file || !add_line_data(view, file, type))
5617 goto error_out;
5618 }
5620 /* Parse diff info part. */
5621 if (status) {
5622 file->status = status;
5623 if (status == 'A')
5624 string_copy(file->old.rev, NULL_ID);
5626 } else if (!file->status || file == unmerged) {
5627 if (!status_get_diff(file, buf, strlen(buf)))
5628 goto error_out;
5630 buf = io_get(&io, 0, TRUE);
5631 if (!buf)
5632 break;
5634 /* Collapse all modified entries that follow an
5635 * associated unmerged entry. */
5636 if (unmerged == file) {
5637 unmerged->status = 'U';
5638 unmerged = NULL;
5639 } else if (file->status == 'U') {
5640 unmerged = file;
5641 }
5642 }
5644 /* Grab the old name for rename/copy. */
5645 if (!*file->old.name &&
5646 (file->status == 'R' || file->status == 'C')) {
5647 string_ncopy(file->old.name, buf, strlen(buf));
5649 buf = io_get(&io, 0, TRUE);
5650 if (!buf)
5651 break;
5652 }
5654 /* git-ls-files just delivers a NUL separated list of
5655 * file names similar to the second half of the
5656 * git-diff-* output. */
5657 string_ncopy(file->new.name, buf, strlen(buf));
5658 if (!*file->old.name)
5659 string_copy(file->old.name, file->new.name);
5660 file = NULL;
5661 }
5663 if (io_error(&io)) {
5664 error_out:
5665 io_done(&io);
5666 return FALSE;
5667 }
5669 if (!view->line[view->lines - 1].data)
5670 add_line_data(view, NULL, LINE_STAT_NONE);
5672 io_done(&io);
5673 return TRUE;
5674 }
5676 /* Don't show unmerged entries in the staged section. */
5677 static const char *status_diff_index_argv[] = {
5678 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5679 "--cached", "-M", "HEAD", NULL
5680 };
5682 static const char *status_diff_files_argv[] = {
5683 "git", "diff-files", "-z", NULL
5684 };
5686 static const char *status_list_other_argv[] = {
5687 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5688 };
5690 static const char *status_list_no_head_argv[] = {
5691 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5692 };
5694 static const char *update_index_argv[] = {
5695 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5696 };
5698 /* Restore the previous line number to stay in the context or select a
5699 * line with something that can be updated. */
5700 static void
5701 status_restore(struct view *view)
5702 {
5703 if (view->p_lineno >= view->lines)
5704 view->p_lineno = view->lines - 1;
5705 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5706 view->p_lineno++;
5707 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5708 view->p_lineno--;
5710 /* If the above fails, always skip the "On branch" line. */
5711 if (view->p_lineno < view->lines)
5712 view->lineno = view->p_lineno;
5713 else
5714 view->lineno = 1;
5716 if (view->lineno < view->offset)
5717 view->offset = view->lineno;
5718 else if (view->offset + view->height <= view->lineno)
5719 view->offset = view->lineno - view->height + 1;
5721 view->p_restore = FALSE;
5722 }
5724 static void
5725 status_update_onbranch(void)
5726 {
5727 static const char *paths[][2] = {
5728 { "rebase-apply/rebasing", "Rebasing" },
5729 { "rebase-apply/applying", "Applying mailbox" },
5730 { "rebase-apply/", "Rebasing mailbox" },
5731 { "rebase-merge/interactive", "Interactive rebase" },
5732 { "rebase-merge/", "Rebase merge" },
5733 { "MERGE_HEAD", "Merging" },
5734 { "BISECT_LOG", "Bisecting" },
5735 { "HEAD", "On branch" },
5736 };
5737 char buf[SIZEOF_STR];
5738 struct stat stat;
5739 int i;
5741 if (is_initial_commit()) {
5742 string_copy(status_onbranch, "Initial commit");
5743 return;
5744 }
5746 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5747 char *head = opt_head;
5749 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5750 lstat(buf, &stat) < 0)
5751 continue;
5753 if (!*opt_head) {
5754 struct io io;
5756 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5757 io_read_buf(&io, buf, sizeof(buf))) {
5758 head = buf;
5759 if (!prefixcmp(head, "refs/heads/"))
5760 head += STRING_SIZE("refs/heads/");
5761 }
5762 }
5764 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5765 string_copy(status_onbranch, opt_head);
5766 return;
5767 }
5769 string_copy(status_onbranch, "Not currently on any branch");
5770 }
5772 /* First parse staged info using git-diff-index(1), then parse unstaged
5773 * info using git-diff-files(1), and finally untracked files using
5774 * git-ls-files(1). */
5775 static bool
5776 status_open(struct view *view)
5777 {
5778 reset_view(view);
5780 add_line_data(view, NULL, LINE_STAT_HEAD);
5781 status_update_onbranch();
5783 io_run_bg(update_index_argv);
5785 if (is_initial_commit()) {
5786 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5787 return FALSE;
5788 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5789 return FALSE;
5790 }
5792 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5793 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5794 return FALSE;
5796 /* Restore the exact position or use the specialized restore
5797 * mode? */
5798 if (!view->p_restore)
5799 status_restore(view);
5800 return TRUE;
5801 }
5803 static bool
5804 status_draw(struct view *view, struct line *line, unsigned int lineno)
5805 {
5806 struct status *status = line->data;
5807 enum line_type type;
5808 const char *text;
5810 if (!status) {
5811 switch (line->type) {
5812 case LINE_STAT_STAGED:
5813 type = LINE_STAT_SECTION;
5814 text = "Changes to be committed:";
5815 break;
5817 case LINE_STAT_UNSTAGED:
5818 type = LINE_STAT_SECTION;
5819 text = "Changed but not updated:";
5820 break;
5822 case LINE_STAT_UNTRACKED:
5823 type = LINE_STAT_SECTION;
5824 text = "Untracked files:";
5825 break;
5827 case LINE_STAT_NONE:
5828 type = LINE_DEFAULT;
5829 text = " (no files)";
5830 break;
5832 case LINE_STAT_HEAD:
5833 type = LINE_STAT_HEAD;
5834 text = status_onbranch;
5835 break;
5837 default:
5838 return FALSE;
5839 }
5840 } else {
5841 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5843 buf[0] = status->status;
5844 if (draw_text(view, line->type, buf, TRUE))
5845 return TRUE;
5846 type = LINE_DEFAULT;
5847 text = status->new.name;
5848 }
5850 draw_text(view, type, text, TRUE);
5851 return TRUE;
5852 }
5854 static enum request
5855 status_load_error(struct view *view, struct view *stage, const char *path)
5856 {
5857 if (displayed_views() == 2 || display[current_view] != view)
5858 maximize_view(view);
5859 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5860 return REQ_NONE;
5861 }
5863 static enum request
5864 status_enter(struct view *view, struct line *line)
5865 {
5866 struct status *status = line->data;
5867 const char *oldpath = status ? status->old.name : NULL;
5868 /* Diffs for unmerged entries are empty when passing the new
5869 * path, so leave it empty. */
5870 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5871 const char *info;
5872 enum open_flags split;
5873 struct view *stage = VIEW(REQ_VIEW_STAGE);
5875 if (line->type == LINE_STAT_NONE ||
5876 (!status && line[1].type == LINE_STAT_NONE)) {
5877 report("No file to diff");
5878 return REQ_NONE;
5879 }
5881 switch (line->type) {
5882 case LINE_STAT_STAGED:
5883 if (is_initial_commit()) {
5884 const char *no_head_diff_argv[] = {
5885 "git", "diff", "--no-color", "--patch-with-stat",
5886 "--", "/dev/null", newpath, NULL
5887 };
5889 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5890 return status_load_error(view, stage, newpath);
5891 } else {
5892 const char *index_show_argv[] = {
5893 "git", "diff-index", "--root", "--patch-with-stat",
5894 "-C", "-M", "--cached", "HEAD", "--",
5895 oldpath, newpath, NULL
5896 };
5898 if (!prepare_update(stage, index_show_argv, opt_cdup))
5899 return status_load_error(view, stage, newpath);
5900 }
5902 if (status)
5903 info = "Staged changes to %s";
5904 else
5905 info = "Staged changes";
5906 break;
5908 case LINE_STAT_UNSTAGED:
5909 {
5910 const char *files_show_argv[] = {
5911 "git", "diff-files", "--root", "--patch-with-stat",
5912 "-C", "-M", "--", oldpath, newpath, NULL
5913 };
5915 if (!prepare_update(stage, files_show_argv, opt_cdup))
5916 return status_load_error(view, stage, newpath);
5917 if (status)
5918 info = "Unstaged changes to %s";
5919 else
5920 info = "Unstaged changes";
5921 break;
5922 }
5923 case LINE_STAT_UNTRACKED:
5924 if (!newpath) {
5925 report("No file to show");
5926 return REQ_NONE;
5927 }
5929 if (!suffixcmp(status->new.name, -1, "/")) {
5930 report("Cannot display a directory");
5931 return REQ_NONE;
5932 }
5934 if (!prepare_update_file(stage, newpath))
5935 return status_load_error(view, stage, newpath);
5936 info = "Untracked file %s";
5937 break;
5939 case LINE_STAT_HEAD:
5940 return REQ_NONE;
5942 default:
5943 die("line type %d not handled in switch", line->type);
5944 }
5946 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5947 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5948 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5949 if (status) {
5950 stage_status = *status;
5951 } else {
5952 memset(&stage_status, 0, sizeof(stage_status));
5953 }
5955 stage_line_type = line->type;
5956 stage_chunks = 0;
5957 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5958 }
5960 return REQ_NONE;
5961 }
5963 static bool
5964 status_exists(struct status *status, enum line_type type)
5965 {
5966 struct view *view = VIEW(REQ_VIEW_STATUS);
5967 unsigned long lineno;
5969 for (lineno = 0; lineno < view->lines; lineno++) {
5970 struct line *line = &view->line[lineno];
5971 struct status *pos = line->data;
5973 if (line->type != type)
5974 continue;
5975 if (!pos && (!status || !status->status) && line[1].data) {
5976 select_view_line(view, lineno);
5977 return TRUE;
5978 }
5979 if (pos && !strcmp(status->new.name, pos->new.name)) {
5980 select_view_line(view, lineno);
5981 return TRUE;
5982 }
5983 }
5985 return FALSE;
5986 }
5989 static bool
5990 status_update_prepare(struct io *io, enum line_type type)
5991 {
5992 const char *staged_argv[] = {
5993 "git", "update-index", "-z", "--index-info", NULL
5994 };
5995 const char *others_argv[] = {
5996 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5997 };
5999 switch (type) {
6000 case LINE_STAT_STAGED:
6001 return io_run(io, IO_WR, opt_cdup, staged_argv);
6003 case LINE_STAT_UNSTAGED:
6004 case LINE_STAT_UNTRACKED:
6005 return io_run(io, IO_WR, opt_cdup, others_argv);
6007 default:
6008 die("line type %d not handled in switch", type);
6009 return FALSE;
6010 }
6011 }
6013 static bool
6014 status_update_write(struct io *io, struct status *status, enum line_type type)
6015 {
6016 char buf[SIZEOF_STR];
6017 size_t bufsize = 0;
6019 switch (type) {
6020 case LINE_STAT_STAGED:
6021 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6022 status->old.mode,
6023 status->old.rev,
6024 status->old.name, 0))
6025 return FALSE;
6026 break;
6028 case LINE_STAT_UNSTAGED:
6029 case LINE_STAT_UNTRACKED:
6030 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6031 return FALSE;
6032 break;
6034 default:
6035 die("line type %d not handled in switch", type);
6036 }
6038 return io_write(io, buf, bufsize);
6039 }
6041 static bool
6042 status_update_file(struct status *status, enum line_type type)
6043 {
6044 struct io io;
6045 bool result;
6047 if (!status_update_prepare(&io, type))
6048 return FALSE;
6050 result = status_update_write(&io, status, type);
6051 return io_done(&io) && result;
6052 }
6054 static bool
6055 status_update_files(struct view *view, struct line *line)
6056 {
6057 char buf[sizeof(view->ref)];
6058 struct io io;
6059 bool result = TRUE;
6060 struct line *pos = view->line + view->lines;
6061 int files = 0;
6062 int file, done;
6063 int cursor_y = -1, cursor_x = -1;
6065 if (!status_update_prepare(&io, line->type))
6066 return FALSE;
6068 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6069 files++;
6071 string_copy(buf, view->ref);
6072 getsyx(cursor_y, cursor_x);
6073 for (file = 0, done = 5; result && file < files; line++, file++) {
6074 int almost_done = file * 100 / files;
6076 if (almost_done > done) {
6077 done = almost_done;
6078 string_format(view->ref, "updating file %u of %u (%d%% done)",
6079 file, files, done);
6080 update_view_title(view);
6081 setsyx(cursor_y, cursor_x);
6082 doupdate();
6083 }
6084 result = status_update_write(&io, line->data, line->type);
6085 }
6086 string_copy(view->ref, buf);
6088 return io_done(&io) && result;
6089 }
6091 static bool
6092 status_update(struct view *view)
6093 {
6094 struct line *line = &view->line[view->lineno];
6096 assert(view->lines);
6098 if (!line->data) {
6099 /* This should work even for the "On branch" line. */
6100 if (line < view->line + view->lines && !line[1].data) {
6101 report("Nothing to update");
6102 return FALSE;
6103 }
6105 if (!status_update_files(view, line + 1)) {
6106 report("Failed to update file status");
6107 return FALSE;
6108 }
6110 } else if (!status_update_file(line->data, line->type)) {
6111 report("Failed to update file status");
6112 return FALSE;
6113 }
6115 return TRUE;
6116 }
6118 static bool
6119 status_revert(struct status *status, enum line_type type, bool has_none)
6120 {
6121 if (!status || type != LINE_STAT_UNSTAGED) {
6122 if (type == LINE_STAT_STAGED) {
6123 report("Cannot revert changes to staged files");
6124 } else if (type == LINE_STAT_UNTRACKED) {
6125 report("Cannot revert changes to untracked files");
6126 } else if (has_none) {
6127 report("Nothing to revert");
6128 } else {
6129 report("Cannot revert changes to multiple files");
6130 }
6132 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6133 char mode[10] = "100644";
6134 const char *reset_argv[] = {
6135 "git", "update-index", "--cacheinfo", mode,
6136 status->old.rev, status->old.name, NULL
6137 };
6138 const char *checkout_argv[] = {
6139 "git", "checkout", "--", status->old.name, NULL
6140 };
6142 if (status->status == 'U') {
6143 string_format(mode, "%5o", status->old.mode);
6145 if (status->old.mode == 0 && status->new.mode == 0) {
6146 reset_argv[2] = "--force-remove";
6147 reset_argv[3] = status->old.name;
6148 reset_argv[4] = NULL;
6149 }
6151 if (!io_run_fg(reset_argv, opt_cdup))
6152 return FALSE;
6153 if (status->old.mode == 0 && status->new.mode == 0)
6154 return TRUE;
6155 }
6157 return io_run_fg(checkout_argv, opt_cdup);
6158 }
6160 return FALSE;
6161 }
6163 static enum request
6164 status_request(struct view *view, enum request request, struct line *line)
6165 {
6166 struct status *status = line->data;
6168 switch (request) {
6169 case REQ_STATUS_UPDATE:
6170 if (!status_update(view))
6171 return REQ_NONE;
6172 break;
6174 case REQ_STATUS_REVERT:
6175 if (!status_revert(status, line->type, status_has_none(view, line)))
6176 return REQ_NONE;
6177 break;
6179 case REQ_STATUS_MERGE:
6180 if (!status || status->status != 'U') {
6181 report("Merging only possible for files with unmerged status ('U').");
6182 return REQ_NONE;
6183 }
6184 open_mergetool(status->new.name);
6185 break;
6187 case REQ_EDIT:
6188 if (!status)
6189 return request;
6190 if (status->status == 'D') {
6191 report("File has been deleted.");
6192 return REQ_NONE;
6193 }
6195 open_editor(status->new.name);
6196 break;
6198 case REQ_VIEW_BLAME:
6199 if (status)
6200 opt_ref[0] = 0;
6201 return request;
6203 case REQ_ENTER:
6204 /* After returning the status view has been split to
6205 * show the stage view. No further reloading is
6206 * necessary. */
6207 return status_enter(view, line);
6209 case REQ_REFRESH:
6210 /* Simply reload the view. */
6211 break;
6213 default:
6214 return request;
6215 }
6217 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6219 return REQ_NONE;
6220 }
6222 static void
6223 status_select(struct view *view, struct line *line)
6224 {
6225 struct status *status = line->data;
6226 char file[SIZEOF_STR] = "all files";
6227 const char *text;
6228 const char *key;
6230 if (status && !string_format(file, "'%s'", status->new.name))
6231 return;
6233 if (!status && line[1].type == LINE_STAT_NONE)
6234 line++;
6236 switch (line->type) {
6237 case LINE_STAT_STAGED:
6238 text = "Press %s to unstage %s for commit";
6239 break;
6241 case LINE_STAT_UNSTAGED:
6242 text = "Press %s to stage %s for commit";
6243 break;
6245 case LINE_STAT_UNTRACKED:
6246 text = "Press %s to stage %s for addition";
6247 break;
6249 case LINE_STAT_HEAD:
6250 case LINE_STAT_NONE:
6251 text = "Nothing to update";
6252 break;
6254 default:
6255 die("line type %d not handled in switch", line->type);
6256 }
6258 if (status && status->status == 'U') {
6259 text = "Press %s to resolve conflict in %s";
6260 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6262 } else {
6263 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6264 }
6266 string_format(view->ref, text, key, file);
6267 if (status)
6268 string_copy(opt_file, status->new.name);
6269 }
6271 static bool
6272 status_grep(struct view *view, struct line *line)
6273 {
6274 struct status *status = line->data;
6276 if (status) {
6277 const char buf[2] = { status->status, 0 };
6278 const char *text[] = { status->new.name, buf, NULL };
6280 return grep_text(view, text);
6281 }
6283 return FALSE;
6284 }
6286 static struct view_ops status_ops = {
6287 "file",
6288 NULL,
6289 status_open,
6290 NULL,
6291 status_draw,
6292 status_request,
6293 status_grep,
6294 status_select,
6295 };
6298 static bool
6299 stage_diff_write(struct io *io, struct line *line, struct line *end)
6300 {
6301 while (line < end) {
6302 if (!io_write(io, line->data, strlen(line->data)) ||
6303 !io_write(io, "\n", 1))
6304 return FALSE;
6305 line++;
6306 if (line->type == LINE_DIFF_CHUNK ||
6307 line->type == LINE_DIFF_HEADER)
6308 break;
6309 }
6311 return TRUE;
6312 }
6314 static struct line *
6315 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6316 {
6317 for (; view->line < line; line--)
6318 if (line->type == type)
6319 return line;
6321 return NULL;
6322 }
6324 static bool
6325 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6326 {
6327 const char *apply_argv[SIZEOF_ARG] = {
6328 "git", "apply", "--whitespace=nowarn", NULL
6329 };
6330 struct line *diff_hdr;
6331 struct io io;
6332 int argc = 3;
6334 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6335 if (!diff_hdr)
6336 return FALSE;
6338 if (!revert)
6339 apply_argv[argc++] = "--cached";
6340 if (revert || stage_line_type == LINE_STAT_STAGED)
6341 apply_argv[argc++] = "-R";
6342 apply_argv[argc++] = "-";
6343 apply_argv[argc++] = NULL;
6344 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6345 return FALSE;
6347 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6348 !stage_diff_write(&io, chunk, view->line + view->lines))
6349 chunk = NULL;
6351 io_done(&io);
6352 io_run_bg(update_index_argv);
6354 return chunk ? TRUE : FALSE;
6355 }
6357 static bool
6358 stage_update(struct view *view, struct line *line)
6359 {
6360 struct line *chunk = NULL;
6362 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6363 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6365 if (chunk) {
6366 if (!stage_apply_chunk(view, chunk, FALSE)) {
6367 report("Failed to apply chunk");
6368 return FALSE;
6369 }
6371 } else if (!stage_status.status) {
6372 view = VIEW(REQ_VIEW_STATUS);
6374 for (line = view->line; line < view->line + view->lines; line++)
6375 if (line->type == stage_line_type)
6376 break;
6378 if (!status_update_files(view, line + 1)) {
6379 report("Failed to update files");
6380 return FALSE;
6381 }
6383 } else if (!status_update_file(&stage_status, stage_line_type)) {
6384 report("Failed to update file");
6385 return FALSE;
6386 }
6388 return TRUE;
6389 }
6391 static bool
6392 stage_revert(struct view *view, struct line *line)
6393 {
6394 struct line *chunk = NULL;
6396 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6397 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6399 if (chunk) {
6400 if (!prompt_yesno("Are you sure you want to revert changes?"))
6401 return FALSE;
6403 if (!stage_apply_chunk(view, chunk, TRUE)) {
6404 report("Failed to revert chunk");
6405 return FALSE;
6406 }
6407 return TRUE;
6409 } else {
6410 return status_revert(stage_status.status ? &stage_status : NULL,
6411 stage_line_type, FALSE);
6412 }
6413 }
6416 static void
6417 stage_next(struct view *view, struct line *line)
6418 {
6419 int i;
6421 if (!stage_chunks) {
6422 for (line = view->line; line < view->line + view->lines; line++) {
6423 if (line->type != LINE_DIFF_CHUNK)
6424 continue;
6426 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6427 report("Allocation failure");
6428 return;
6429 }
6431 stage_chunk[stage_chunks++] = line - view->line;
6432 }
6433 }
6435 for (i = 0; i < stage_chunks; i++) {
6436 if (stage_chunk[i] > view->lineno) {
6437 do_scroll_view(view, stage_chunk[i] - view->lineno);
6438 report("Chunk %d of %d", i + 1, stage_chunks);
6439 return;
6440 }
6441 }
6443 report("No next chunk found");
6444 }
6446 static enum request
6447 stage_request(struct view *view, enum request request, struct line *line)
6448 {
6449 switch (request) {
6450 case REQ_STATUS_UPDATE:
6451 if (!stage_update(view, line))
6452 return REQ_NONE;
6453 break;
6455 case REQ_STATUS_REVERT:
6456 if (!stage_revert(view, line))
6457 return REQ_NONE;
6458 break;
6460 case REQ_STAGE_NEXT:
6461 if (stage_line_type == LINE_STAT_UNTRACKED) {
6462 report("File is untracked; press %s to add",
6463 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6464 return REQ_NONE;
6465 }
6466 stage_next(view, line);
6467 return REQ_NONE;
6469 case REQ_EDIT:
6470 if (!stage_status.new.name[0])
6471 return request;
6472 if (stage_status.status == 'D') {
6473 report("File has been deleted.");
6474 return REQ_NONE;
6475 }
6477 open_editor(stage_status.new.name);
6478 break;
6480 case REQ_REFRESH:
6481 /* Reload everything ... */
6482 break;
6484 case REQ_VIEW_BLAME:
6485 if (stage_status.new.name[0]) {
6486 string_copy(opt_file, stage_status.new.name);
6487 opt_ref[0] = 0;
6488 }
6489 return request;
6491 case REQ_ENTER:
6492 return pager_request(view, request, line);
6494 default:
6495 return request;
6496 }
6498 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6499 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6501 /* Check whether the staged entry still exists, and close the
6502 * stage view if it doesn't. */
6503 if (!status_exists(&stage_status, stage_line_type)) {
6504 status_restore(VIEW(REQ_VIEW_STATUS));
6505 return REQ_VIEW_CLOSE;
6506 }
6508 if (stage_line_type == LINE_STAT_UNTRACKED) {
6509 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6510 report("Cannot display a directory");
6511 return REQ_NONE;
6512 }
6514 if (!prepare_update_file(view, stage_status.new.name)) {
6515 report("Failed to open file: %s", strerror(errno));
6516 return REQ_NONE;
6517 }
6518 }
6519 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6521 return REQ_NONE;
6522 }
6524 static struct view_ops stage_ops = {
6525 "line",
6526 NULL,
6527 NULL,
6528 pager_read,
6529 pager_draw,
6530 stage_request,
6531 pager_grep,
6532 pager_select,
6533 };
6536 /*
6537 * Revision graph
6538 */
6540 struct commit {
6541 char id[SIZEOF_REV]; /* SHA1 ID. */
6542 char title[128]; /* First line of the commit message. */
6543 const char *author; /* Author of the commit. */
6544 struct time time; /* Date from the author ident. */
6545 struct ref_list *refs; /* Repository references. */
6546 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6547 size_t graph_size; /* The width of the graph array. */
6548 bool has_parents; /* Rewritten --parents seen. */
6549 };
6551 /* Size of rev graph with no "padding" columns */
6552 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6554 struct rev_graph {
6555 struct rev_graph *prev, *next, *parents;
6556 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6557 size_t size;
6558 struct commit *commit;
6559 size_t pos;
6560 unsigned int boundary:1;
6561 };
6563 /* Parents of the commit being visualized. */
6564 static struct rev_graph graph_parents[4];
6566 /* The current stack of revisions on the graph. */
6567 static struct rev_graph graph_stacks[4] = {
6568 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6569 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6570 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6571 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6572 };
6574 static inline bool
6575 graph_parent_is_merge(struct rev_graph *graph)
6576 {
6577 return graph->parents->size > 1;
6578 }
6580 static inline void
6581 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6582 {
6583 struct commit *commit = graph->commit;
6585 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6586 commit->graph[commit->graph_size++] = symbol;
6587 }
6589 static void
6590 clear_rev_graph(struct rev_graph *graph)
6591 {
6592 graph->boundary = 0;
6593 graph->size = graph->pos = 0;
6594 graph->commit = NULL;
6595 memset(graph->parents, 0, sizeof(*graph->parents));
6596 }
6598 static void
6599 done_rev_graph(struct rev_graph *graph)
6600 {
6601 if (graph_parent_is_merge(graph) &&
6602 graph->pos < graph->size - 1 &&
6603 graph->next->size == graph->size + graph->parents->size - 1) {
6604 size_t i = graph->pos + graph->parents->size - 1;
6606 graph->commit->graph_size = i * 2;
6607 while (i < graph->next->size - 1) {
6608 append_to_rev_graph(graph, ' ');
6609 append_to_rev_graph(graph, '\\');
6610 i++;
6611 }
6612 }
6614 clear_rev_graph(graph);
6615 }
6617 static void
6618 push_rev_graph(struct rev_graph *graph, const char *parent)
6619 {
6620 int i;
6622 /* "Collapse" duplicate parents lines.
6623 *
6624 * FIXME: This needs to also update update the drawn graph but
6625 * for now it just serves as a method for pruning graph lines. */
6626 for (i = 0; i < graph->size; i++)
6627 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6628 return;
6630 if (graph->size < SIZEOF_REVITEMS) {
6631 string_copy_rev(graph->rev[graph->size++], parent);
6632 }
6633 }
6635 static chtype
6636 get_rev_graph_symbol(struct rev_graph *graph)
6637 {
6638 chtype symbol;
6640 if (graph->boundary)
6641 symbol = REVGRAPH_BOUND;
6642 else if (graph->parents->size == 0)
6643 symbol = REVGRAPH_INIT;
6644 else if (graph_parent_is_merge(graph))
6645 symbol = REVGRAPH_MERGE;
6646 else if (graph->pos >= graph->size)
6647 symbol = REVGRAPH_BRANCH;
6648 else
6649 symbol = REVGRAPH_COMMIT;
6651 return symbol;
6652 }
6654 static void
6655 draw_rev_graph(struct rev_graph *graph)
6656 {
6657 struct rev_filler {
6658 chtype separator, line;
6659 };
6660 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6661 static struct rev_filler fillers[] = {
6662 { ' ', '|' },
6663 { '`', '.' },
6664 { '\'', ' ' },
6665 { '/', ' ' },
6666 };
6667 chtype symbol = get_rev_graph_symbol(graph);
6668 struct rev_filler *filler;
6669 size_t i;
6671 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6672 filler = &fillers[DEFAULT];
6674 for (i = 0; i < graph->pos; i++) {
6675 append_to_rev_graph(graph, filler->line);
6676 if (graph_parent_is_merge(graph->prev) &&
6677 graph->prev->pos == i)
6678 filler = &fillers[RSHARP];
6680 append_to_rev_graph(graph, filler->separator);
6681 }
6683 /* Place the symbol for this revision. */
6684 append_to_rev_graph(graph, symbol);
6686 if (graph->prev->size > graph->size)
6687 filler = &fillers[RDIAG];
6688 else
6689 filler = &fillers[DEFAULT];
6691 i++;
6693 for (; i < graph->size; i++) {
6694 append_to_rev_graph(graph, filler->separator);
6695 append_to_rev_graph(graph, filler->line);
6696 if (graph_parent_is_merge(graph->prev) &&
6697 i < graph->prev->pos + graph->parents->size)
6698 filler = &fillers[RSHARP];
6699 if (graph->prev->size > graph->size)
6700 filler = &fillers[LDIAG];
6701 }
6703 if (graph->prev->size > graph->size) {
6704 append_to_rev_graph(graph, filler->separator);
6705 if (filler->line != ' ')
6706 append_to_rev_graph(graph, filler->line);
6707 }
6708 }
6710 /* Prepare the next rev graph */
6711 static void
6712 prepare_rev_graph(struct rev_graph *graph)
6713 {
6714 size_t i;
6716 /* First, traverse all lines of revisions up to the active one. */
6717 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6718 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6719 break;
6721 push_rev_graph(graph->next, graph->rev[graph->pos]);
6722 }
6724 /* Interleave the new revision parent(s). */
6725 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6726 push_rev_graph(graph->next, graph->parents->rev[i]);
6728 /* Lastly, put any remaining revisions. */
6729 for (i = graph->pos + 1; i < graph->size; i++)
6730 push_rev_graph(graph->next, graph->rev[i]);
6731 }
6733 static void
6734 update_rev_graph(struct view *view, struct rev_graph *graph)
6735 {
6736 /* If this is the finalizing update ... */
6737 if (graph->commit)
6738 prepare_rev_graph(graph);
6740 /* Graph visualization needs a one rev look-ahead,
6741 * so the first update doesn't visualize anything. */
6742 if (!graph->prev->commit)
6743 return;
6745 if (view->lines > 2)
6746 view->line[view->lines - 3].dirty = 1;
6747 if (view->lines > 1)
6748 view->line[view->lines - 2].dirty = 1;
6749 draw_rev_graph(graph->prev);
6750 done_rev_graph(graph->prev->prev);
6751 }
6754 /*
6755 * Main view backend
6756 */
6758 static const char *main_argv[SIZEOF_ARG] = {
6759 "git", "log", "--no-color", "--pretty=raw", "--parents",
6760 "--topo-order", "%(diffargs)", "%(revargs)",
6761 "--", "%(fileargs)", NULL
6762 };
6764 static bool
6765 main_draw(struct view *view, struct line *line, unsigned int lineno)
6766 {
6767 struct commit *commit = line->data;
6769 if (!commit->author)
6770 return FALSE;
6772 if (opt_date && draw_date(view, &commit->time))
6773 return TRUE;
6775 if (opt_author && draw_author(view, commit->author))
6776 return TRUE;
6778 if (opt_rev_graph && commit->graph_size &&
6779 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6780 return TRUE;
6782 if (opt_show_refs && commit->refs) {
6783 size_t i;
6785 for (i = 0; i < commit->refs->size; i++) {
6786 struct ref *ref = commit->refs->refs[i];
6787 enum line_type type;
6789 if (ref->head)
6790 type = LINE_MAIN_HEAD;
6791 else if (ref->ltag)
6792 type = LINE_MAIN_LOCAL_TAG;
6793 else if (ref->tag)
6794 type = LINE_MAIN_TAG;
6795 else if (ref->tracked)
6796 type = LINE_MAIN_TRACKED;
6797 else if (ref->remote)
6798 type = LINE_MAIN_REMOTE;
6799 else
6800 type = LINE_MAIN_REF;
6802 if (draw_text(view, type, "[", TRUE) ||
6803 draw_text(view, type, ref->name, TRUE) ||
6804 draw_text(view, type, "]", TRUE))
6805 return TRUE;
6807 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6808 return TRUE;
6809 }
6810 }
6812 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6813 return TRUE;
6814 }
6816 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6817 static bool
6818 main_read(struct view *view, char *line)
6819 {
6820 static struct rev_graph *graph = graph_stacks;
6821 enum line_type type;
6822 struct commit *commit;
6824 if (!line) {
6825 int i;
6827 if (!view->lines && !view->prev)
6828 die("No revisions match the given arguments.");
6829 if (view->lines > 0) {
6830 commit = view->line[view->lines - 1].data;
6831 view->line[view->lines - 1].dirty = 1;
6832 if (!commit->author) {
6833 view->lines--;
6834 free(commit);
6835 graph->commit = NULL;
6836 }
6837 }
6838 update_rev_graph(view, graph);
6840 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6841 clear_rev_graph(&graph_stacks[i]);
6842 return TRUE;
6843 }
6845 type = get_line_type(line);
6846 if (type == LINE_COMMIT) {
6847 commit = calloc(1, sizeof(struct commit));
6848 if (!commit)
6849 return FALSE;
6851 line += STRING_SIZE("commit ");
6852 if (*line == '-') {
6853 graph->boundary = 1;
6854 line++;
6855 }
6857 string_copy_rev(commit->id, line);
6858 commit->refs = get_ref_list(commit->id);
6859 graph->commit = commit;
6860 add_line_data(view, commit, LINE_MAIN_COMMIT);
6862 while ((line = strchr(line, ' '))) {
6863 line++;
6864 push_rev_graph(graph->parents, line);
6865 commit->has_parents = TRUE;
6866 }
6867 return TRUE;
6868 }
6870 if (!view->lines)
6871 return TRUE;
6872 commit = view->line[view->lines - 1].data;
6874 switch (type) {
6875 case LINE_PARENT:
6876 if (commit->has_parents)
6877 break;
6878 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6879 break;
6881 case LINE_AUTHOR:
6882 parse_author_line(line + STRING_SIZE("author "),
6883 &commit->author, &commit->time);
6884 update_rev_graph(view, graph);
6885 graph = graph->next;
6886 break;
6888 default:
6889 /* Fill in the commit title if it has not already been set. */
6890 if (commit->title[0])
6891 break;
6893 /* Require titles to start with a non-space character at the
6894 * offset used by git log. */
6895 if (strncmp(line, " ", 4))
6896 break;
6897 line += 4;
6898 /* Well, if the title starts with a whitespace character,
6899 * try to be forgiving. Otherwise we end up with no title. */
6900 while (isspace(*line))
6901 line++;
6902 if (*line == '\0')
6903 break;
6904 /* FIXME: More graceful handling of titles; append "..." to
6905 * shortened titles, etc. */
6907 string_expand(commit->title, sizeof(commit->title), line, 1);
6908 view->line[view->lines - 1].dirty = 1;
6909 }
6911 return TRUE;
6912 }
6914 static enum request
6915 main_request(struct view *view, enum request request, struct line *line)
6916 {
6917 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6919 switch (request) {
6920 case REQ_ENTER:
6921 open_view(view, REQ_VIEW_DIFF, flags);
6922 break;
6923 case REQ_REFRESH:
6924 load_refs();
6925 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6926 break;
6927 default:
6928 return request;
6929 }
6931 return REQ_NONE;
6932 }
6934 static bool
6935 grep_refs(struct ref_list *list, regex_t *regex)
6936 {
6937 regmatch_t pmatch;
6938 size_t i;
6940 if (!opt_show_refs || !list)
6941 return FALSE;
6943 for (i = 0; i < list->size; i++) {
6944 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6945 return TRUE;
6946 }
6948 return FALSE;
6949 }
6951 static bool
6952 main_grep(struct view *view, struct line *line)
6953 {
6954 struct commit *commit = line->data;
6955 const char *text[] = {
6956 commit->title,
6957 opt_author ? commit->author : "",
6958 mkdate(&commit->time, opt_date),
6959 NULL
6960 };
6962 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6963 }
6965 static void
6966 main_select(struct view *view, struct line *line)
6967 {
6968 struct commit *commit = line->data;
6970 string_copy_rev(view->ref, commit->id);
6971 string_copy_rev(ref_commit, view->ref);
6972 }
6974 static struct view_ops main_ops = {
6975 "commit",
6976 main_argv,
6977 NULL,
6978 main_read,
6979 main_draw,
6980 main_request,
6981 main_grep,
6982 main_select,
6983 };
6986 /*
6987 * Status management
6988 */
6990 /* Whether or not the curses interface has been initialized. */
6991 static bool cursed = FALSE;
6993 /* Terminal hacks and workarounds. */
6994 static bool use_scroll_redrawwin;
6995 static bool use_scroll_status_wclear;
6997 /* The status window is used for polling keystrokes. */
6998 static WINDOW *status_win;
7000 /* Reading from the prompt? */
7001 static bool input_mode = FALSE;
7003 static bool status_empty = FALSE;
7005 /* Update status and title window. */
7006 static void
7007 report(const char *msg, ...)
7008 {
7009 struct view *view = display[current_view];
7011 if (input_mode)
7012 return;
7014 if (!view) {
7015 char buf[SIZEOF_STR];
7016 va_list args;
7018 va_start(args, msg);
7019 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7020 buf[sizeof(buf) - 1] = 0;
7021 buf[sizeof(buf) - 2] = '.';
7022 buf[sizeof(buf) - 3] = '.';
7023 buf[sizeof(buf) - 4] = '.';
7024 }
7025 va_end(args);
7026 die("%s", buf);
7027 }
7029 if (!status_empty || *msg) {
7030 va_list args;
7032 va_start(args, msg);
7034 wmove(status_win, 0, 0);
7035 if (view->has_scrolled && use_scroll_status_wclear)
7036 wclear(status_win);
7037 if (*msg) {
7038 vwprintw(status_win, msg, args);
7039 status_empty = FALSE;
7040 } else {
7041 status_empty = TRUE;
7042 }
7043 wclrtoeol(status_win);
7044 wnoutrefresh(status_win);
7046 va_end(args);
7047 }
7049 update_view_title(view);
7050 }
7052 static void
7053 init_display(void)
7054 {
7055 const char *term;
7056 int x, y;
7058 /* Initialize the curses library */
7059 if (isatty(STDIN_FILENO)) {
7060 cursed = !!initscr();
7061 opt_tty = stdin;
7062 } else {
7063 /* Leave stdin and stdout alone when acting as a pager. */
7064 opt_tty = fopen("/dev/tty", "r+");
7065 if (!opt_tty)
7066 die("Failed to open /dev/tty");
7067 cursed = !!newterm(NULL, opt_tty, opt_tty);
7068 }
7070 if (!cursed)
7071 die("Failed to initialize curses");
7073 nonl(); /* Disable conversion and detect newlines from input. */
7074 cbreak(); /* Take input chars one at a time, no wait for \n */
7075 noecho(); /* Don't echo input */
7076 leaveok(stdscr, FALSE);
7078 if (has_colors())
7079 init_colors();
7081 getmaxyx(stdscr, y, x);
7082 status_win = newwin(1, 0, y - 1, 0);
7083 if (!status_win)
7084 die("Failed to create status window");
7086 /* Enable keyboard mapping */
7087 keypad(status_win, TRUE);
7088 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7090 TABSIZE = opt_tab_size;
7092 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7093 if (term && !strcmp(term, "gnome-terminal")) {
7094 /* In the gnome-terminal-emulator, the message from
7095 * scrolling up one line when impossible followed by
7096 * scrolling down one line causes corruption of the
7097 * status line. This is fixed by calling wclear. */
7098 use_scroll_status_wclear = TRUE;
7099 use_scroll_redrawwin = FALSE;
7101 } else if (term && !strcmp(term, "xrvt-xpm")) {
7102 /* No problems with full optimizations in xrvt-(unicode)
7103 * and aterm. */
7104 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7106 } else {
7107 /* When scrolling in (u)xterm the last line in the
7108 * scrolling direction will update slowly. */
7109 use_scroll_redrawwin = TRUE;
7110 use_scroll_status_wclear = FALSE;
7111 }
7112 }
7114 static int
7115 get_input(int prompt_position)
7116 {
7117 struct view *view;
7118 int i, key, cursor_y, cursor_x;
7120 if (prompt_position)
7121 input_mode = TRUE;
7123 while (TRUE) {
7124 bool loading = FALSE;
7126 foreach_view (view, i) {
7127 update_view(view);
7128 if (view_is_displayed(view) && view->has_scrolled &&
7129 use_scroll_redrawwin)
7130 redrawwin(view->win);
7131 view->has_scrolled = FALSE;
7132 if (view->pipe)
7133 loading = TRUE;
7134 }
7136 /* Update the cursor position. */
7137 if (prompt_position) {
7138 getbegyx(status_win, cursor_y, cursor_x);
7139 cursor_x = prompt_position;
7140 } else {
7141 view = display[current_view];
7142 getbegyx(view->win, cursor_y, cursor_x);
7143 cursor_x = view->width - 1;
7144 cursor_y += view->lineno - view->offset;
7145 }
7146 setsyx(cursor_y, cursor_x);
7148 /* Refresh, accept single keystroke of input */
7149 doupdate();
7150 nodelay(status_win, loading);
7151 key = wgetch(status_win);
7153 /* wgetch() with nodelay() enabled returns ERR when
7154 * there's no input. */
7155 if (key == ERR) {
7157 } else if (key == KEY_RESIZE) {
7158 int height, width;
7160 getmaxyx(stdscr, height, width);
7162 wresize(status_win, 1, width);
7163 mvwin(status_win, height - 1, 0);
7164 wnoutrefresh(status_win);
7165 resize_display();
7166 redraw_display(TRUE);
7168 } else {
7169 input_mode = FALSE;
7170 return key;
7171 }
7172 }
7173 }
7175 static char *
7176 prompt_input(const char *prompt, input_handler handler, void *data)
7177 {
7178 enum input_status status = INPUT_OK;
7179 static char buf[SIZEOF_STR];
7180 size_t pos = 0;
7182 buf[pos] = 0;
7184 while (status == INPUT_OK || status == INPUT_SKIP) {
7185 int key;
7187 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7188 wclrtoeol(status_win);
7190 key = get_input(pos + 1);
7191 switch (key) {
7192 case KEY_RETURN:
7193 case KEY_ENTER:
7194 case '\n':
7195 status = pos ? INPUT_STOP : INPUT_CANCEL;
7196 break;
7198 case KEY_BACKSPACE:
7199 if (pos > 0)
7200 buf[--pos] = 0;
7201 else
7202 status = INPUT_CANCEL;
7203 break;
7205 case KEY_ESC:
7206 status = INPUT_CANCEL;
7207 break;
7209 default:
7210 if (pos >= sizeof(buf)) {
7211 report("Input string too long");
7212 return NULL;
7213 }
7215 status = handler(data, buf, key);
7216 if (status == INPUT_OK)
7217 buf[pos++] = (char) key;
7218 }
7219 }
7221 /* Clear the status window */
7222 status_empty = FALSE;
7223 report("");
7225 if (status == INPUT_CANCEL)
7226 return NULL;
7228 buf[pos++] = 0;
7230 return buf;
7231 }
7233 static enum input_status
7234 prompt_yesno_handler(void *data, char *buf, int c)
7235 {
7236 if (c == 'y' || c == 'Y')
7237 return INPUT_STOP;
7238 if (c == 'n' || c == 'N')
7239 return INPUT_CANCEL;
7240 return INPUT_SKIP;
7241 }
7243 static bool
7244 prompt_yesno(const char *prompt)
7245 {
7246 char prompt2[SIZEOF_STR];
7248 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7249 return FALSE;
7251 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7252 }
7254 static enum input_status
7255 read_prompt_handler(void *data, char *buf, int c)
7256 {
7257 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7258 }
7260 static char *
7261 read_prompt(const char *prompt)
7262 {
7263 return prompt_input(prompt, read_prompt_handler, NULL);
7264 }
7266 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7267 {
7268 enum input_status status = INPUT_OK;
7269 int size = 0;
7271 while (items[size].text)
7272 size++;
7274 while (status == INPUT_OK) {
7275 const struct menu_item *item = &items[*selected];
7276 int key;
7277 int i;
7279 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7280 prompt, *selected + 1, size);
7281 if (item->hotkey)
7282 wprintw(status_win, "[%c] ", (char) item->hotkey);
7283 wprintw(status_win, "%s", item->text);
7284 wclrtoeol(status_win);
7286 key = get_input(COLS - 1);
7287 switch (key) {
7288 case KEY_RETURN:
7289 case KEY_ENTER:
7290 case '\n':
7291 status = INPUT_STOP;
7292 break;
7294 case KEY_LEFT:
7295 case KEY_UP:
7296 *selected = *selected - 1;
7297 if (*selected < 0)
7298 *selected = size - 1;
7299 break;
7301 case KEY_RIGHT:
7302 case KEY_DOWN:
7303 *selected = (*selected + 1) % size;
7304 break;
7306 case KEY_ESC:
7307 status = INPUT_CANCEL;
7308 break;
7310 default:
7311 for (i = 0; items[i].text; i++)
7312 if (items[i].hotkey == key) {
7313 *selected = i;
7314 status = INPUT_STOP;
7315 break;
7316 }
7317 }
7318 }
7320 /* Clear the status window */
7321 status_empty = FALSE;
7322 report("");
7324 return status != INPUT_CANCEL;
7325 }
7327 /*
7328 * Repository properties
7329 */
7331 static struct ref **refs = NULL;
7332 static size_t refs_size = 0;
7333 static struct ref *refs_head = NULL;
7335 static struct ref_list **ref_lists = NULL;
7336 static size_t ref_lists_size = 0;
7338 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7339 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7340 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7342 static int
7343 compare_refs(const void *ref1_, const void *ref2_)
7344 {
7345 const struct ref *ref1 = *(const struct ref **)ref1_;
7346 const struct ref *ref2 = *(const struct ref **)ref2_;
7348 if (ref1->tag != ref2->tag)
7349 return ref2->tag - ref1->tag;
7350 if (ref1->ltag != ref2->ltag)
7351 return ref2->ltag - ref2->ltag;
7352 if (ref1->head != ref2->head)
7353 return ref2->head - ref1->head;
7354 if (ref1->tracked != ref2->tracked)
7355 return ref2->tracked - ref1->tracked;
7356 if (ref1->remote != ref2->remote)
7357 return ref2->remote - ref1->remote;
7358 return strcmp(ref1->name, ref2->name);
7359 }
7361 static void
7362 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7363 {
7364 size_t i;
7366 for (i = 0; i < refs_size; i++)
7367 if (!visitor(data, refs[i]))
7368 break;
7369 }
7371 static struct ref *
7372 get_ref_head()
7373 {
7374 return refs_head;
7375 }
7377 static struct ref_list *
7378 get_ref_list(const char *id)
7379 {
7380 struct ref_list *list;
7381 size_t i;
7383 for (i = 0; i < ref_lists_size; i++)
7384 if (!strcmp(id, ref_lists[i]->id))
7385 return ref_lists[i];
7387 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7388 return NULL;
7389 list = calloc(1, sizeof(*list));
7390 if (!list)
7391 return NULL;
7393 for (i = 0; i < refs_size; i++) {
7394 if (!strcmp(id, refs[i]->id) &&
7395 realloc_refs_list(&list->refs, list->size, 1))
7396 list->refs[list->size++] = refs[i];
7397 }
7399 if (!list->refs) {
7400 free(list);
7401 return NULL;
7402 }
7404 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7405 ref_lists[ref_lists_size++] = list;
7406 return list;
7407 }
7409 static int
7410 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7411 {
7412 struct ref *ref = NULL;
7413 bool tag = FALSE;
7414 bool ltag = FALSE;
7415 bool remote = FALSE;
7416 bool tracked = FALSE;
7417 bool head = FALSE;
7418 int from = 0, to = refs_size - 1;
7420 if (!prefixcmp(name, "refs/tags/")) {
7421 if (!suffixcmp(name, namelen, "^{}")) {
7422 namelen -= 3;
7423 name[namelen] = 0;
7424 } else {
7425 ltag = TRUE;
7426 }
7428 tag = TRUE;
7429 namelen -= STRING_SIZE("refs/tags/");
7430 name += STRING_SIZE("refs/tags/");
7432 } else if (!prefixcmp(name, "refs/remotes/")) {
7433 remote = TRUE;
7434 namelen -= STRING_SIZE("refs/remotes/");
7435 name += STRING_SIZE("refs/remotes/");
7436 tracked = !strcmp(opt_remote, name);
7438 } else if (!prefixcmp(name, "refs/heads/")) {
7439 namelen -= STRING_SIZE("refs/heads/");
7440 name += STRING_SIZE("refs/heads/");
7441 if (!strncmp(opt_head, name, namelen))
7442 return OK;
7444 } else if (!strcmp(name, "HEAD")) {
7445 head = TRUE;
7446 if (*opt_head) {
7447 namelen = strlen(opt_head);
7448 name = opt_head;
7449 }
7450 }
7452 /* If we are reloading or it's an annotated tag, replace the
7453 * previous SHA1 with the resolved commit id; relies on the fact
7454 * git-ls-remote lists the commit id of an annotated tag right
7455 * before the commit id it points to. */
7456 while (from <= to) {
7457 size_t pos = (to + from) / 2;
7458 int cmp = strcmp(name, refs[pos]->name);
7460 if (!cmp) {
7461 ref = refs[pos];
7462 break;
7463 }
7465 if (cmp < 0)
7466 to = pos - 1;
7467 else
7468 from = pos + 1;
7469 }
7471 if (!ref) {
7472 if (!realloc_refs(&refs, refs_size, 1))
7473 return ERR;
7474 ref = calloc(1, sizeof(*ref) + namelen);
7475 if (!ref)
7476 return ERR;
7477 memmove(refs + from + 1, refs + from,
7478 (refs_size - from) * sizeof(*refs));
7479 refs[from] = ref;
7480 strncpy(ref->name, name, namelen);
7481 refs_size++;
7482 }
7484 ref->head = head;
7485 ref->tag = tag;
7486 ref->ltag = ltag;
7487 ref->remote = remote;
7488 ref->tracked = tracked;
7489 string_copy_rev(ref->id, id);
7491 if (head)
7492 refs_head = ref;
7493 return OK;
7494 }
7496 static int
7497 load_refs(void)
7498 {
7499 const char *head_argv[] = {
7500 "git", "symbolic-ref", "HEAD", NULL
7501 };
7502 static const char *ls_remote_argv[SIZEOF_ARG] = {
7503 "git", "ls-remote", opt_git_dir, NULL
7504 };
7505 static bool init = FALSE;
7506 size_t i;
7508 if (!init) {
7509 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7510 die("TIG_LS_REMOTE contains too many arguments");
7511 init = TRUE;
7512 }
7514 if (!*opt_git_dir)
7515 return OK;
7517 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7518 !prefixcmp(opt_head, "refs/heads/")) {
7519 char *offset = opt_head + STRING_SIZE("refs/heads/");
7521 memmove(opt_head, offset, strlen(offset) + 1);
7522 }
7524 refs_head = NULL;
7525 for (i = 0; i < refs_size; i++)
7526 refs[i]->id[0] = 0;
7528 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7529 return ERR;
7531 /* Update the ref lists to reflect changes. */
7532 for (i = 0; i < ref_lists_size; i++) {
7533 struct ref_list *list = ref_lists[i];
7534 size_t old, new;
7536 for (old = new = 0; old < list->size; old++)
7537 if (!strcmp(list->id, list->refs[old]->id))
7538 list->refs[new++] = list->refs[old];
7539 list->size = new;
7540 }
7542 return OK;
7543 }
7545 static void
7546 set_remote_branch(const char *name, const char *value, size_t valuelen)
7547 {
7548 if (!strcmp(name, ".remote")) {
7549 string_ncopy(opt_remote, value, valuelen);
7551 } else if (*opt_remote && !strcmp(name, ".merge")) {
7552 size_t from = strlen(opt_remote);
7554 if (!prefixcmp(value, "refs/heads/"))
7555 value += STRING_SIZE("refs/heads/");
7557 if (!string_format_from(opt_remote, &from, "/%s", value))
7558 opt_remote[0] = 0;
7559 }
7560 }
7562 static void
7563 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7564 {
7565 const char *argv[SIZEOF_ARG] = { name, "=" };
7566 int argc = 1 + (cmd == option_set_command);
7567 int error = ERR;
7569 if (!argv_from_string(argv, &argc, value))
7570 config_msg = "Too many option arguments";
7571 else
7572 error = cmd(argc, argv);
7574 if (error == ERR)
7575 warn("Option 'tig.%s': %s", name, config_msg);
7576 }
7578 static bool
7579 set_environment_variable(const char *name, const char *value)
7580 {
7581 size_t len = strlen(name) + 1 + strlen(value) + 1;
7582 char *env = malloc(len);
7584 if (env &&
7585 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7586 putenv(env) == 0)
7587 return TRUE;
7588 free(env);
7589 return FALSE;
7590 }
7592 static void
7593 set_work_tree(const char *value)
7594 {
7595 char cwd[SIZEOF_STR];
7597 if (!getcwd(cwd, sizeof(cwd)))
7598 die("Failed to get cwd path: %s", strerror(errno));
7599 if (chdir(opt_git_dir) < 0)
7600 die("Failed to chdir(%s): %s", strerror(errno));
7601 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7602 die("Failed to get git path: %s", strerror(errno));
7603 if (chdir(cwd) < 0)
7604 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7605 if (chdir(value) < 0)
7606 die("Failed to chdir(%s): %s", value, strerror(errno));
7607 if (!getcwd(cwd, sizeof(cwd)))
7608 die("Failed to get cwd path: %s", strerror(errno));
7609 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7610 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7611 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7612 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7613 opt_is_inside_work_tree = TRUE;
7614 }
7616 static int
7617 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7618 {
7619 if (!strcmp(name, "i18n.commitencoding"))
7620 string_ncopy(opt_encoding, value, valuelen);
7622 else if (!strcmp(name, "core.editor"))
7623 string_ncopy(opt_editor, value, valuelen);
7625 else if (!strcmp(name, "core.worktree"))
7626 set_work_tree(value);
7628 else if (!prefixcmp(name, "tig.color."))
7629 set_repo_config_option(name + 10, value, option_color_command);
7631 else if (!prefixcmp(name, "tig.bind."))
7632 set_repo_config_option(name + 9, value, option_bind_command);
7634 else if (!prefixcmp(name, "tig."))
7635 set_repo_config_option(name + 4, value, option_set_command);
7637 else if (*opt_head && !prefixcmp(name, "branch.") &&
7638 !strncmp(name + 7, opt_head, strlen(opt_head)))
7639 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7641 return OK;
7642 }
7644 static int
7645 load_git_config(void)
7646 {
7647 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7649 return io_run_load(config_list_argv, "=", read_repo_config_option);
7650 }
7652 static int
7653 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7654 {
7655 if (!opt_git_dir[0]) {
7656 string_ncopy(opt_git_dir, name, namelen);
7658 } else if (opt_is_inside_work_tree == -1) {
7659 /* This can be 3 different values depending on the
7660 * version of git being used. If git-rev-parse does not
7661 * understand --is-inside-work-tree it will simply echo
7662 * the option else either "true" or "false" is printed.
7663 * Default to true for the unknown case. */
7664 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7666 } else if (*name == '.') {
7667 string_ncopy(opt_cdup, name, namelen);
7669 } else {
7670 string_ncopy(opt_prefix, name, namelen);
7671 }
7673 return OK;
7674 }
7676 static int
7677 load_repo_info(void)
7678 {
7679 const char *rev_parse_argv[] = {
7680 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7681 "--show-cdup", "--show-prefix", NULL
7682 };
7684 return io_run_load(rev_parse_argv, "=", read_repo_info);
7685 }
7688 /*
7689 * Main
7690 */
7692 static const char usage[] =
7693 "tig " TIG_VERSION " (" __DATE__ ")\n"
7694 "\n"
7695 "Usage: tig [options] [revs] [--] [paths]\n"
7696 " or: tig show [options] [revs] [--] [paths]\n"
7697 " or: tig blame [rev] path\n"
7698 " or: tig status\n"
7699 " or: tig < [git command output]\n"
7700 "\n"
7701 "Options:\n"
7702 " -v, --version Show version and exit\n"
7703 " -h, --help Show help message and exit";
7705 static void __NORETURN
7706 quit(int sig)
7707 {
7708 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7709 if (cursed)
7710 endwin();
7711 exit(0);
7712 }
7714 static void __NORETURN
7715 die(const char *err, ...)
7716 {
7717 va_list args;
7719 endwin();
7721 va_start(args, err);
7722 fputs("tig: ", stderr);
7723 vfprintf(stderr, err, args);
7724 fputs("\n", stderr);
7725 va_end(args);
7727 exit(1);
7728 }
7730 static void
7731 warn(const char *msg, ...)
7732 {
7733 va_list args;
7735 va_start(args, msg);
7736 fputs("tig warning: ", stderr);
7737 vfprintf(stderr, msg, args);
7738 fputs("\n", stderr);
7739 va_end(args);
7740 }
7742 static const char ***filter_args;
7744 static int
7745 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7746 {
7747 return argv_append(filter_args, name) ? OK : ERR;
7748 }
7750 static void
7751 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7752 {
7753 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7754 const char **all_argv = NULL;
7756 filter_args = args;
7757 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7758 !argv_append_array(&all_argv, argv) ||
7759 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7760 die("Failed to split arguments");
7761 argv_free(all_argv);
7762 free(all_argv);
7763 }
7765 static void
7766 filter_options(const char *argv[])
7767 {
7768 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7769 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7770 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7771 }
7773 static enum request
7774 parse_options(int argc, const char *argv[])
7775 {
7776 enum request request = REQ_VIEW_MAIN;
7777 const char *subcommand;
7778 bool seen_dashdash = FALSE;
7779 const char **filter_argv = NULL;
7780 int i;
7782 if (!isatty(STDIN_FILENO))
7783 return REQ_VIEW_PAGER;
7785 if (argc <= 1)
7786 return REQ_VIEW_MAIN;
7788 subcommand = argv[1];
7789 if (!strcmp(subcommand, "status")) {
7790 if (argc > 2)
7791 warn("ignoring arguments after `%s'", subcommand);
7792 return REQ_VIEW_STATUS;
7794 } else if (!strcmp(subcommand, "blame")) {
7795 if (argc <= 2 || argc > 4)
7796 die("invalid number of options to blame\n\n%s", usage);
7798 i = 2;
7799 if (argc == 4) {
7800 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7801 i++;
7802 }
7804 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7805 return REQ_VIEW_BLAME;
7807 } else if (!strcmp(subcommand, "show")) {
7808 request = REQ_VIEW_DIFF;
7810 } else {
7811 subcommand = NULL;
7812 }
7814 for (i = 1 + !!subcommand; i < argc; i++) {
7815 const char *opt = argv[i];
7817 if (seen_dashdash) {
7818 argv_append(&opt_file_args, opt);
7819 continue;
7821 } else if (!strcmp(opt, "--")) {
7822 seen_dashdash = TRUE;
7823 continue;
7825 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7826 printf("tig version %s\n", TIG_VERSION);
7827 quit(0);
7829 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7830 printf("%s\n", usage);
7831 quit(0);
7833 } else if (!strcmp(opt, "--all")) {
7834 argv_append(&opt_rev_args, opt);
7835 continue;
7836 }
7838 if (!argv_append(&filter_argv, opt))
7839 die("command too long");
7840 }
7842 if (filter_argv)
7843 filter_options(filter_argv);
7845 return request;
7846 }
7848 int
7849 main(int argc, const char *argv[])
7850 {
7851 const char *codeset = "UTF-8";
7852 enum request request = parse_options(argc, argv);
7853 struct view *view;
7854 size_t i;
7856 signal(SIGINT, quit);
7857 signal(SIGPIPE, SIG_IGN);
7859 if (setlocale(LC_ALL, "")) {
7860 codeset = nl_langinfo(CODESET);
7861 }
7863 if (load_repo_info() == ERR)
7864 die("Failed to load repo info.");
7866 if (load_options() == ERR)
7867 die("Failed to load user config.");
7869 if (load_git_config() == ERR)
7870 die("Failed to load repo config.");
7872 /* Require a git repository unless when running in pager mode. */
7873 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7874 die("Not a git repository");
7876 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7877 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7878 if (opt_iconv_in == ICONV_NONE)
7879 die("Failed to initialize character set conversion");
7880 }
7882 if (codeset && strcmp(codeset, "UTF-8")) {
7883 opt_iconv_out = iconv_open(codeset, "UTF-8");
7884 if (opt_iconv_out == ICONV_NONE)
7885 die("Failed to initialize character set conversion");
7886 }
7888 if (load_refs() == ERR)
7889 die("Failed to load refs.");
7891 foreach_view (view, i) {
7892 if (getenv(view->cmd_env))
7893 warn("Use of the %s environment variable is deprecated,"
7894 " use options or TIG_DIFF_ARGS instead",
7895 view->cmd_env);
7896 if (!argv_from_env(view->ops->argv, view->cmd_env))
7897 die("Too many arguments in the `%s` environment variable",
7898 view->cmd_env);
7899 }
7901 init_display();
7903 while (view_driver(display[current_view], request)) {
7904 int key = get_input(0);
7906 view = display[current_view];
7907 request = get_keybinding(view->keymap, key);
7909 /* Some low-level request handling. This keeps access to
7910 * status_win restricted. */
7911 switch (request) {
7912 case REQ_NONE:
7913 report("Unknown key, press %s for help",
7914 get_key(view->keymap, REQ_VIEW_HELP));
7915 break;
7916 case REQ_PROMPT:
7917 {
7918 char *cmd = read_prompt(":");
7920 if (cmd && isdigit(*cmd)) {
7921 int lineno = view->lineno + 1;
7923 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7924 select_view_line(view, lineno - 1);
7925 report("");
7926 } else {
7927 report("Unable to parse '%s' as a line number", cmd);
7928 }
7930 } else if (cmd) {
7931 struct view *next = VIEW(REQ_VIEW_PAGER);
7932 const char *argv[SIZEOF_ARG] = { "git" };
7933 int argc = 1;
7935 /* When running random commands, initially show the
7936 * command in the title. However, it maybe later be
7937 * overwritten if a commit line is selected. */
7938 string_ncopy(next->ref, cmd, strlen(cmd));
7940 if (!argv_from_string(argv, &argc, cmd)) {
7941 report("Too many arguments");
7942 } else if (!prepare_update(next, argv, NULL)) {
7943 report("Failed to format command");
7944 } else {
7945 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7946 }
7947 }
7949 request = REQ_NONE;
7950 break;
7951 }
7952 case REQ_SEARCH:
7953 case REQ_SEARCH_BACK:
7954 {
7955 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7956 char *search = read_prompt(prompt);
7958 if (search)
7959 string_ncopy(opt_search, search, strlen(search));
7960 else if (*opt_search)
7961 request = request == REQ_SEARCH ?
7962 REQ_FIND_NEXT :
7963 REQ_FIND_PREV;
7964 else
7965 request = REQ_NONE;
7966 break;
7967 }
7968 default:
7969 break;
7970 }
7971 }
7973 quit(0);
7975 return 0;
7976 }