1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
137 };
139 static struct ref *get_ref_head();
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum input_status {
145 INPUT_OK,
146 INPUT_SKIP,
147 INPUT_STOP,
148 INPUT_CANCEL
149 };
151 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
153 static char *prompt_input(const char *prompt, input_handler handler, void *data);
154 static bool prompt_yesno(const char *prompt);
156 struct menu_item {
157 int hotkey;
158 const char *text;
159 void *data;
160 };
162 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
164 /*
165 * Allocation helpers ... Entering macro hell to never be seen again.
166 */
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 static type * \
170 name(type **mem, size_t size, size_t increase) \
171 { \
172 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
173 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174 type *tmp = *mem; \
175 \
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178 if (tmp) \
179 *mem = tmp; \
180 } \
181 \
182 return tmp; \
183 }
185 /*
186 * String helpers
187 */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191 {
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
197 }
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static size_t
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215 {
216 size_t size, pos;
218 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219 if (src[pos] == '\t') {
220 size_t expanded = tabsize - (size % tabsize);
222 if (expanded + size >= dstlen - 1)
223 expanded = dstlen - size - 1;
224 memcpy(dst + size, " ", expanded);
225 size += expanded;
226 } else {
227 dst[size++] = src[pos];
228 }
229 }
231 dst[size] = 0;
232 return pos;
233 }
235 static char *
236 chomp_string(char *name)
237 {
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
248 }
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252 {
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
264 }
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
274 {
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
289 }
291 return 0;
292 }
294 #define enum_equals(entry, str, len) \
295 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
297 struct enum_map {
298 const char *name;
299 int namelen;
300 int value;
301 };
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 static char *
306 enum_map_name(const char *name, size_t namelen)
307 {
308 static char buf[SIZEOF_STR];
309 int bufpos;
311 for (bufpos = 0; bufpos <= namelen; bufpos++) {
312 buf[bufpos] = tolower(name[bufpos]);
313 if (buf[bufpos] == '_')
314 buf[bufpos] = '-';
315 }
317 buf[bufpos] = 0;
318 return buf;
319 }
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 static bool
324 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
325 {
326 size_t namelen = strlen(name);
327 int i;
329 for (i = 0; i < map_size; i++)
330 if (enum_equals(map[i], name, namelen)) {
331 *value = map[i].value;
332 return TRUE;
333 }
335 return FALSE;
336 }
338 #define map_enum(attr, map, name) \
339 map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342 strncmp(str1, str2, STRING_SIZE(str2))
344 static inline int
345 suffixcmp(const char *str, int slen, const char *suffix)
346 {
347 size_t len = slen >= 0 ? slen : strlen(str);
348 size_t suffixlen = strlen(suffix);
350 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
351 }
354 /*
355 * Unicode / UTF-8 handling
356 *
357 * NOTE: Much of the following code for dealing with Unicode is derived from
358 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
360 */
362 static inline int
363 unicode_width(unsigned long c, int tab_size)
364 {
365 if (c >= 0x1100 &&
366 (c <= 0x115f /* Hangul Jamo */
367 || c == 0x2329
368 || c == 0x232a
369 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
370 /* CJK ... Yi */
371 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
372 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
373 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
374 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
375 || (c >= 0xffe0 && c <= 0xffe6)
376 || (c >= 0x20000 && c <= 0x2fffd)
377 || (c >= 0x30000 && c <= 0x3fffd)))
378 return 2;
380 if (c == '\t')
381 return tab_size;
383 return 1;
384 }
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387 * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes[256] = {
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
396 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
397 };
399 static inline unsigned char
400 utf8_char_length(const char *string, const char *end)
401 {
402 int c = *(unsigned char *) string;
404 return utf8_bytes[c];
405 }
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string, size_t length)
410 {
411 unsigned long unicode;
413 switch (length) {
414 case 1:
415 unicode = string[0];
416 break;
417 case 2:
418 unicode = (string[0] & 0x1f) << 6;
419 unicode += (string[1] & 0x3f);
420 break;
421 case 3:
422 unicode = (string[0] & 0x0f) << 12;
423 unicode += ((string[1] & 0x3f) << 6);
424 unicode += (string[2] & 0x3f);
425 break;
426 case 4:
427 unicode = (string[0] & 0x0f) << 18;
428 unicode += ((string[1] & 0x3f) << 12);
429 unicode += ((string[2] & 0x3f) << 6);
430 unicode += (string[3] & 0x3f);
431 break;
432 case 5:
433 unicode = (string[0] & 0x0f) << 24;
434 unicode += ((string[1] & 0x3f) << 18);
435 unicode += ((string[2] & 0x3f) << 12);
436 unicode += ((string[3] & 0x3f) << 6);
437 unicode += (string[4] & 0x3f);
438 break;
439 case 6:
440 unicode = (string[0] & 0x01) << 30;
441 unicode += ((string[1] & 0x3f) << 24);
442 unicode += ((string[2] & 0x3f) << 18);
443 unicode += ((string[3] & 0x3f) << 12);
444 unicode += ((string[4] & 0x3f) << 6);
445 unicode += (string[5] & 0x3f);
446 break;
447 default:
448 return 0;
449 }
451 /* Invalid characters could return the special 0xfffd value but NUL
452 * should be just as good. */
453 return unicode > 0xffff ? 0 : unicode;
454 }
456 /* Calculates how much of string can be shown within the given maximum width
457 * and sets trimmed parameter to non-zero value if all of string could not be
458 * shown. If the reserve flag is TRUE, it will reserve at least one
459 * trailing character, which can be useful when drawing a delimiter.
460 *
461 * Returns the number of bytes to output from string to satisfy max_width. */
462 static size_t
463 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
464 {
465 const char *string = *start;
466 const char *end = strchr(string, '\0');
467 unsigned char last_bytes = 0;
468 size_t last_ucwidth = 0;
470 *width = 0;
471 *trimmed = 0;
473 while (string < end) {
474 unsigned char bytes = utf8_char_length(string, end);
475 size_t ucwidth;
476 unsigned long unicode;
478 if (string + bytes > end)
479 break;
481 /* Change representation to figure out whether
482 * it is a single- or double-width character. */
484 unicode = utf8_to_unicode(string, bytes);
485 /* FIXME: Graceful handling of invalid Unicode character. */
486 if (!unicode)
487 break;
489 ucwidth = unicode_width(unicode, tab_size);
490 if (skip > 0) {
491 skip -= ucwidth <= skip ? ucwidth : skip;
492 *start += bytes;
493 }
494 *width += ucwidth;
495 if (*width > max_width) {
496 *trimmed = 1;
497 *width -= ucwidth;
498 if (reserve && *width == max_width) {
499 string -= last_bytes;
500 *width -= last_ucwidth;
501 }
502 break;
503 }
505 string += bytes;
506 last_bytes = ucwidth ? bytes : 0;
507 last_ucwidth = ucwidth;
508 }
510 return string - *start;
511 }
514 #define DATE_INFO \
515 DATE_(NO), \
516 DATE_(DEFAULT), \
517 DATE_(LOCAL), \
518 DATE_(RELATIVE), \
519 DATE_(SHORT)
521 enum date {
522 #define DATE_(name) DATE_##name
523 DATE_INFO
524 #undef DATE_
525 };
527 static const struct enum_map date_map[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
529 DATE_INFO
530 #undef DATE_
531 };
533 struct time {
534 time_t sec;
535 int tz;
536 };
538 static inline int timecmp(const struct time *t1, const struct time *t2)
539 {
540 return t1->sec - t2->sec;
541 }
543 static const char *
544 mkdate(const struct time *time, enum date date)
545 {
546 static char buf[DATE_COLS + 1];
547 static const struct enum_map reldate[] = {
548 { "second", 1, 60 * 2 },
549 { "minute", 60, 60 * 60 * 2 },
550 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
551 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
552 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
553 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
554 };
555 struct tm tm;
557 if (!date || !time || !time->sec)
558 return "";
560 if (date == DATE_RELATIVE) {
561 struct timeval now;
562 time_t date = time->sec + time->tz;
563 time_t seconds;
564 int i;
566 gettimeofday(&now, NULL);
567 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
568 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
569 if (seconds >= reldate[i].value)
570 continue;
572 seconds /= reldate[i].namelen;
573 if (!string_format(buf, "%ld %s%s %s",
574 seconds, reldate[i].name,
575 seconds > 1 ? "s" : "",
576 now.tv_sec >= date ? "ago" : "ahead"))
577 break;
578 return buf;
579 }
580 }
582 if (date == DATE_LOCAL) {
583 time_t date = time->sec + time->tz;
584 localtime_r(&date, &tm);
585 }
586 else {
587 gmtime_r(&time->sec, &tm);
588 }
589 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
590 }
593 #define AUTHOR_VALUES \
594 AUTHOR_(NO), \
595 AUTHOR_(FULL), \
596 AUTHOR_(ABBREVIATED)
598 enum author {
599 #define AUTHOR_(name) AUTHOR_##name
600 AUTHOR_VALUES,
601 #undef AUTHOR_
602 AUTHOR_DEFAULT = AUTHOR_FULL
603 };
605 static const struct enum_map author_map[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
607 AUTHOR_VALUES
608 #undef AUTHOR_
609 };
611 static const char *
612 get_author_initials(const char *author)
613 {
614 static char initials[AUTHOR_COLS * 6 + 1];
615 size_t pos = 0;
616 const char *end = strchr(author, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620 memset(initials, 0, sizeof(initials));
621 while (author < end) {
622 unsigned char bytes;
623 size_t i;
625 while (is_initial_sep(*author))
626 author++;
628 bytes = utf8_char_length(author, end);
629 if (bytes < sizeof(initials) - 1 - pos) {
630 while (bytes--) {
631 initials[pos++] = *author++;
632 }
633 }
635 for (i = pos; author < end && !is_initial_sep(*author); author++) {
636 if (i < sizeof(initials) - 1)
637 initials[i++] = *author;
638 }
640 initials[i++] = 0;
641 }
643 return initials;
644 }
647 static bool
648 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649 {
650 int valuelen;
652 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
653 bool advance = cmd[valuelen] != 0;
655 cmd[valuelen] = 0;
656 argv[(*argc)++] = chomp_string(cmd);
657 cmd = chomp_string(cmd + valuelen + advance);
658 }
660 if (*argc < SIZEOF_ARG)
661 argv[*argc] = NULL;
662 return *argc < SIZEOF_ARG;
663 }
665 static bool
666 argv_from_env(const char **argv, const char *name)
667 {
668 char *env = argv ? getenv(name) : NULL;
669 int argc = 0;
671 if (env && *env)
672 env = strdup(env);
673 return !env || argv_from_string(argv, &argc, env);
674 }
676 static void
677 argv_free(const char *argv[])
678 {
679 int argc;
681 if (!argv)
682 return;
683 for (argc = 0; argv[argc]; argc++)
684 free((void *) argv[argc]);
685 argv[0] = NULL;
686 }
688 static size_t
689 argv_size(const char **argv)
690 {
691 int argc = 0;
693 while (argv && argv[argc])
694 argc++;
696 return argc;
697 }
699 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
701 static bool
702 argv_append(const char ***argv, const char *arg)
703 {
704 size_t argc = argv_size(*argv);
706 if (!argv_realloc(argv, argc, 2))
707 return FALSE;
709 (*argv)[argc++] = strdup(arg);
710 (*argv)[argc] = NULL;
711 return TRUE;
712 }
714 static bool
715 argv_append_array(const char ***dst_argv, const char *src_argv[])
716 {
717 int i;
719 for (i = 0; src_argv && src_argv[i]; i++)
720 if (!argv_append(dst_argv, src_argv[i]))
721 return FALSE;
722 return TRUE;
723 }
725 static bool
726 argv_copy(const char ***dst, const char *src[])
727 {
728 int argc;
730 for (argc = 0; src[argc]; argc++)
731 if (!argv_append(dst, src[argc]))
732 return FALSE;
733 return TRUE;
734 }
737 /*
738 * Executing external commands.
739 */
741 enum io_type {
742 IO_FD, /* File descriptor based IO. */
743 IO_BG, /* Execute command in the background. */
744 IO_FG, /* Execute command with same std{in,out,err}. */
745 IO_RD, /* Read only fork+exec IO. */
746 IO_WR, /* Write only fork+exec IO. */
747 IO_AP, /* Append fork+exec output to file. */
748 };
750 struct io {
751 int pipe; /* Pipe end for reading or writing. */
752 pid_t pid; /* PID of spawned process. */
753 int error; /* Error status. */
754 char *buf; /* Read buffer. */
755 size_t bufalloc; /* Allocated buffer size. */
756 size_t bufsize; /* Buffer content size. */
757 char *bufpos; /* Current buffer position. */
758 unsigned int eof:1; /* Has end of file been reached. */
759 };
761 static void
762 io_init(struct io *io)
763 {
764 memset(io, 0, sizeof(*io));
765 io->pipe = -1;
766 }
768 static bool
769 io_open(struct io *io, const char *fmt, ...)
770 {
771 char name[SIZEOF_STR] = "";
772 bool fits;
773 va_list args;
775 io_init(io);
777 va_start(args, fmt);
778 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
779 va_end(args);
781 if (!fits) {
782 io->error = ENAMETOOLONG;
783 return FALSE;
784 }
785 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
786 if (io->pipe == -1)
787 io->error = errno;
788 return io->pipe != -1;
789 }
791 static bool
792 io_kill(struct io *io)
793 {
794 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
795 }
797 static bool
798 io_done(struct io *io)
799 {
800 pid_t pid = io->pid;
802 if (io->pipe != -1)
803 close(io->pipe);
804 free(io->buf);
805 io_init(io);
807 while (pid > 0) {
808 int status;
809 pid_t waiting = waitpid(pid, &status, 0);
811 if (waiting < 0) {
812 if (errno == EINTR)
813 continue;
814 io->error = errno;
815 return FALSE;
816 }
818 return waiting == pid &&
819 !WIFSIGNALED(status) &&
820 WIFEXITED(status) &&
821 !WEXITSTATUS(status);
822 }
824 return TRUE;
825 }
827 static bool
828 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
829 {
830 int pipefds[2] = { -1, -1 };
831 va_list args;
833 io_init(io);
835 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
836 io->error = errno;
837 return FALSE;
838 } else if (type == IO_AP) {
839 va_start(args, argv);
840 pipefds[1] = va_arg(args, int);
841 va_end(args);
842 }
844 if ((io->pid = fork())) {
845 if (io->pid == -1)
846 io->error = errno;
847 if (pipefds[!(type == IO_WR)] != -1)
848 close(pipefds[!(type == IO_WR)]);
849 if (io->pid != -1) {
850 io->pipe = pipefds[!!(type == IO_WR)];
851 return TRUE;
852 }
854 } else {
855 if (type != IO_FG) {
856 int devnull = open("/dev/null", O_RDWR);
857 int readfd = type == IO_WR ? pipefds[0] : devnull;
858 int writefd = (type == IO_RD || type == IO_AP)
859 ? pipefds[1] : devnull;
861 dup2(readfd, STDIN_FILENO);
862 dup2(writefd, STDOUT_FILENO);
863 dup2(devnull, STDERR_FILENO);
865 close(devnull);
866 if (pipefds[0] != -1)
867 close(pipefds[0]);
868 if (pipefds[1] != -1)
869 close(pipefds[1]);
870 }
872 if (dir && *dir && chdir(dir) == -1)
873 exit(errno);
875 execvp(argv[0], (char *const*) argv);
876 exit(errno);
877 }
879 if (pipefds[!!(type == IO_WR)] != -1)
880 close(pipefds[!!(type == IO_WR)]);
881 return FALSE;
882 }
884 static bool
885 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
886 {
887 struct io io;
889 return io_run(&io, type, dir, argv, fd) && io_done(&io);
890 }
892 static bool
893 io_run_bg(const char **argv)
894 {
895 return io_complete(IO_BG, argv, NULL, -1);
896 }
898 static bool
899 io_run_fg(const char **argv, const char *dir)
900 {
901 return io_complete(IO_FG, argv, dir, -1);
902 }
904 static bool
905 io_run_append(const char **argv, int fd)
906 {
907 return io_complete(IO_AP, argv, NULL, fd);
908 }
910 static bool
911 io_eof(struct io *io)
912 {
913 return io->eof;
914 }
916 static int
917 io_error(struct io *io)
918 {
919 return io->error;
920 }
922 static char *
923 io_strerror(struct io *io)
924 {
925 return strerror(io->error);
926 }
928 static bool
929 io_can_read(struct io *io)
930 {
931 struct timeval tv = { 0, 500 };
932 fd_set fds;
934 FD_ZERO(&fds);
935 FD_SET(io->pipe, &fds);
937 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
938 }
940 static ssize_t
941 io_read(struct io *io, void *buf, size_t bufsize)
942 {
943 do {
944 ssize_t readsize = read(io->pipe, buf, bufsize);
946 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
947 continue;
948 else if (readsize == -1)
949 io->error = errno;
950 else if (readsize == 0)
951 io->eof = 1;
952 return readsize;
953 } while (1);
954 }
956 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
958 static char *
959 io_get(struct io *io, int c, bool can_read)
960 {
961 char *eol;
962 ssize_t readsize;
964 while (TRUE) {
965 if (io->bufsize > 0) {
966 eol = memchr(io->bufpos, c, io->bufsize);
967 if (eol) {
968 char *line = io->bufpos;
970 *eol = 0;
971 io->bufpos = eol + 1;
972 io->bufsize -= io->bufpos - line;
973 return line;
974 }
975 }
977 if (io_eof(io)) {
978 if (io->bufsize) {
979 io->bufpos[io->bufsize] = 0;
980 io->bufsize = 0;
981 return io->bufpos;
982 }
983 return NULL;
984 }
986 if (!can_read)
987 return NULL;
989 if (io->bufsize > 0 && io->bufpos > io->buf)
990 memmove(io->buf, io->bufpos, io->bufsize);
992 if (io->bufalloc == io->bufsize) {
993 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
994 return NULL;
995 io->bufalloc += BUFSIZ;
996 }
998 io->bufpos = io->buf;
999 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1000 if (io_error(io))
1001 return NULL;
1002 io->bufsize += readsize;
1003 }
1004 }
1006 static bool
1007 io_write(struct io *io, const void *buf, size_t bufsize)
1008 {
1009 size_t written = 0;
1011 while (!io_error(io) && written < bufsize) {
1012 ssize_t size;
1014 size = write(io->pipe, buf + written, bufsize - written);
1015 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1016 continue;
1017 else if (size == -1)
1018 io->error = errno;
1019 else
1020 written += size;
1021 }
1023 return written == bufsize;
1024 }
1026 static bool
1027 io_read_buf(struct io *io, char buf[], size_t bufsize)
1028 {
1029 char *result = io_get(io, '\n', TRUE);
1031 if (result) {
1032 result = chomp_string(result);
1033 string_ncopy_do(buf, bufsize, result, strlen(result));
1034 }
1036 return io_done(io) && result;
1037 }
1039 static bool
1040 io_run_buf(const char **argv, char buf[], size_t bufsize)
1041 {
1042 struct io io;
1044 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1045 }
1047 static int
1048 io_load(struct io *io, const char *separators,
1049 int (*read_property)(char *, size_t, char *, size_t))
1050 {
1051 char *name;
1052 int state = OK;
1054 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1055 char *value;
1056 size_t namelen;
1057 size_t valuelen;
1059 name = chomp_string(name);
1060 namelen = strcspn(name, separators);
1062 if (name[namelen]) {
1063 name[namelen] = 0;
1064 value = chomp_string(name + namelen + 1);
1065 valuelen = strlen(value);
1067 } else {
1068 value = "";
1069 valuelen = 0;
1070 }
1072 state = read_property(name, namelen, value, valuelen);
1073 }
1075 if (state != ERR && io_error(io))
1076 state = ERR;
1077 io_done(io);
1079 return state;
1080 }
1082 static int
1083 io_run_load(const char **argv, const char *separators,
1084 int (*read_property)(char *, size_t, char *, size_t))
1085 {
1086 struct io io;
1088 if (!io_run(&io, IO_RD, NULL, argv))
1089 return ERR;
1090 return io_load(&io, separators, read_property);
1091 }
1094 /*
1095 * User requests
1096 */
1098 #define REQ_INFO \
1099 /* XXX: Keep the view request first and in sync with views[]. */ \
1100 REQ_GROUP("View switching") \
1101 REQ_(VIEW_MAIN, "Show main view"), \
1102 REQ_(VIEW_DIFF, "Show diff view"), \
1103 REQ_(VIEW_LOG, "Show log view"), \
1104 REQ_(VIEW_TREE, "Show tree view"), \
1105 REQ_(VIEW_BLOB, "Show blob view"), \
1106 REQ_(VIEW_BLAME, "Show blame view"), \
1107 REQ_(VIEW_BRANCH, "Show branch view"), \
1108 REQ_(VIEW_HELP, "Show help page"), \
1109 REQ_(VIEW_PAGER, "Show pager view"), \
1110 REQ_(VIEW_STATUS, "Show status view"), \
1111 REQ_(VIEW_STAGE, "Show stage view"), \
1112 \
1113 REQ_GROUP("View manipulation") \
1114 REQ_(ENTER, "Enter current line and scroll"), \
1115 REQ_(NEXT, "Move to next"), \
1116 REQ_(PREVIOUS, "Move to previous"), \
1117 REQ_(PARENT, "Move to parent"), \
1118 REQ_(VIEW_NEXT, "Move focus to next view"), \
1119 REQ_(REFRESH, "Reload and refresh"), \
1120 REQ_(MAXIMIZE, "Maximize the current view"), \
1121 REQ_(VIEW_CLOSE, "Close the current view"), \
1122 REQ_(QUIT, "Close all views and quit"), \
1123 \
1124 REQ_GROUP("View specific requests") \
1125 REQ_(STATUS_UPDATE, "Update file status"), \
1126 REQ_(STATUS_REVERT, "Revert file changes"), \
1127 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1128 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1129 \
1130 REQ_GROUP("Cursor navigation") \
1131 REQ_(MOVE_UP, "Move cursor one line up"), \
1132 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1133 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1134 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1135 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1136 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1137 \
1138 REQ_GROUP("Scrolling") \
1139 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1140 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1141 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1142 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1143 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1144 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1145 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1146 \
1147 REQ_GROUP("Searching") \
1148 REQ_(SEARCH, "Search the view"), \
1149 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1150 REQ_(FIND_NEXT, "Find next search match"), \
1151 REQ_(FIND_PREV, "Find previous search match"), \
1152 \
1153 REQ_GROUP("Option manipulation") \
1154 REQ_(OPTIONS, "Open option menu"), \
1155 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1156 REQ_(TOGGLE_DATE, "Toggle date display"), \
1157 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1158 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1159 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1160 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1161 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1162 \
1163 REQ_GROUP("Misc") \
1164 REQ_(PROMPT, "Bring up the prompt"), \
1165 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1166 REQ_(SHOW_VERSION, "Show version information"), \
1167 REQ_(STOP_LOADING, "Stop all loading views"), \
1168 REQ_(EDIT, "Open in editor"), \
1169 REQ_(NONE, "Do nothing")
1172 /* User action requests. */
1173 enum request {
1174 #define REQ_GROUP(help)
1175 #define REQ_(req, help) REQ_##req
1177 /* Offset all requests to avoid conflicts with ncurses getch values. */
1178 REQ_UNKNOWN = KEY_MAX + 1,
1179 REQ_OFFSET,
1180 REQ_INFO
1182 #undef REQ_GROUP
1183 #undef REQ_
1184 };
1186 struct request_info {
1187 enum request request;
1188 const char *name;
1189 int namelen;
1190 const char *help;
1191 };
1193 static const struct request_info req_info[] = {
1194 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1195 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1196 REQ_INFO
1197 #undef REQ_GROUP
1198 #undef REQ_
1199 };
1201 static enum request
1202 get_request(const char *name)
1203 {
1204 int namelen = strlen(name);
1205 int i;
1207 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1208 if (enum_equals(req_info[i], name, namelen))
1209 return req_info[i].request;
1211 return REQ_UNKNOWN;
1212 }
1215 /*
1216 * Options
1217 */
1219 /* Option and state variables. */
1220 static enum date opt_date = DATE_DEFAULT;
1221 static enum author opt_author = AUTHOR_DEFAULT;
1222 static bool opt_line_number = FALSE;
1223 static bool opt_line_graphics = TRUE;
1224 static bool opt_rev_graph = FALSE;
1225 static bool opt_show_refs = TRUE;
1226 static bool opt_untracked_dirs_content = TRUE;
1227 static int opt_num_interval = 5;
1228 static double opt_hscroll = 0.50;
1229 static double opt_scale_split_view = 2.0 / 3.0;
1230 static int opt_tab_size = 8;
1231 static int opt_author_cols = AUTHOR_COLS;
1232 static char opt_path[SIZEOF_STR] = "";
1233 static char opt_file[SIZEOF_STR] = "";
1234 static char opt_ref[SIZEOF_REF] = "";
1235 static char opt_head[SIZEOF_REF] = "";
1236 static char opt_remote[SIZEOF_REF] = "";
1237 static char opt_encoding[20] = "UTF-8";
1238 static iconv_t opt_iconv_in = ICONV_NONE;
1239 static iconv_t opt_iconv_out = ICONV_NONE;
1240 static char opt_search[SIZEOF_STR] = "";
1241 static char opt_cdup[SIZEOF_STR] = "";
1242 static char opt_prefix[SIZEOF_STR] = "";
1243 static char opt_git_dir[SIZEOF_STR] = "";
1244 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1245 static char opt_editor[SIZEOF_STR] = "";
1246 static FILE *opt_tty = NULL;
1247 static const char **opt_diff_args = NULL;
1248 static const char **opt_rev_args = NULL;
1249 static const char **opt_file_args = NULL;
1251 #define is_initial_commit() (!get_ref_head())
1252 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1255 /*
1256 * Line-oriented content detection.
1257 */
1259 #define LINE_INFO \
1260 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1274 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1275 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1276 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1281 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1283 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1285 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1291 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1292 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1293 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1294 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1295 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1298 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1299 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1301 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1307 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1308 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1309 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1310 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1311 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1312 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1313 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1315 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1317 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1318 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1320 enum line_type {
1321 #define LINE(type, line, fg, bg, attr) \
1322 LINE_##type
1323 LINE_INFO,
1324 LINE_NONE
1325 #undef LINE
1326 };
1328 struct line_info {
1329 const char *name; /* Option name. */
1330 int namelen; /* Size of option name. */
1331 const char *line; /* The start of line to match. */
1332 int linelen; /* Size of string to match. */
1333 int fg, bg, attr; /* Color and text attributes for the lines. */
1334 };
1336 static struct line_info line_info[] = {
1337 #define LINE(type, line, fg, bg, attr) \
1338 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1339 LINE_INFO
1340 #undef LINE
1341 };
1343 static enum line_type
1344 get_line_type(const char *line)
1345 {
1346 int linelen = strlen(line);
1347 enum line_type type;
1349 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1350 /* Case insensitive search matches Signed-off-by lines better. */
1351 if (linelen >= line_info[type].linelen &&
1352 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1353 return type;
1355 return LINE_DEFAULT;
1356 }
1358 static inline int
1359 get_line_attr(enum line_type type)
1360 {
1361 assert(type < ARRAY_SIZE(line_info));
1362 return COLOR_PAIR(type) | line_info[type].attr;
1363 }
1365 static struct line_info *
1366 get_line_info(const char *name)
1367 {
1368 size_t namelen = strlen(name);
1369 enum line_type type;
1371 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1372 if (enum_equals(line_info[type], name, namelen))
1373 return &line_info[type];
1375 return NULL;
1376 }
1378 static void
1379 init_colors(void)
1380 {
1381 int default_bg = line_info[LINE_DEFAULT].bg;
1382 int default_fg = line_info[LINE_DEFAULT].fg;
1383 enum line_type type;
1385 start_color();
1387 if (assume_default_colors(default_fg, default_bg) == ERR) {
1388 default_bg = COLOR_BLACK;
1389 default_fg = COLOR_WHITE;
1390 }
1392 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1393 struct line_info *info = &line_info[type];
1394 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1395 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1397 init_pair(type, fg, bg);
1398 }
1399 }
1401 struct line {
1402 enum line_type type;
1404 /* State flags */
1405 unsigned int selected:1;
1406 unsigned int dirty:1;
1407 unsigned int cleareol:1;
1408 unsigned int other:16;
1410 void *data; /* User data */
1411 };
1414 /*
1415 * Keys
1416 */
1418 struct keybinding {
1419 int alias;
1420 enum request request;
1421 };
1423 static struct keybinding default_keybindings[] = {
1424 /* View switching */
1425 { 'm', REQ_VIEW_MAIN },
1426 { 'd', REQ_VIEW_DIFF },
1427 { 'l', REQ_VIEW_LOG },
1428 { 't', REQ_VIEW_TREE },
1429 { 'f', REQ_VIEW_BLOB },
1430 { 'B', REQ_VIEW_BLAME },
1431 { 'H', REQ_VIEW_BRANCH },
1432 { 'p', REQ_VIEW_PAGER },
1433 { 'h', REQ_VIEW_HELP },
1434 { 'S', REQ_VIEW_STATUS },
1435 { 'c', REQ_VIEW_STAGE },
1437 /* View manipulation */
1438 { 'q', REQ_VIEW_CLOSE },
1439 { KEY_TAB, REQ_VIEW_NEXT },
1440 { KEY_RETURN, REQ_ENTER },
1441 { KEY_UP, REQ_PREVIOUS },
1442 { KEY_CTL('P'), REQ_PREVIOUS },
1443 { KEY_DOWN, REQ_NEXT },
1444 { KEY_CTL('N'), REQ_NEXT },
1445 { 'R', REQ_REFRESH },
1446 { KEY_F(5), REQ_REFRESH },
1447 { 'O', REQ_MAXIMIZE },
1449 /* Cursor navigation */
1450 { 'k', REQ_MOVE_UP },
1451 { 'j', REQ_MOVE_DOWN },
1452 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1453 { KEY_END, REQ_MOVE_LAST_LINE },
1454 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1455 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1456 { ' ', REQ_MOVE_PAGE_DOWN },
1457 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1458 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1459 { 'b', REQ_MOVE_PAGE_UP },
1460 { '-', REQ_MOVE_PAGE_UP },
1462 /* Scrolling */
1463 { '|', REQ_SCROLL_FIRST_COL },
1464 { KEY_LEFT, REQ_SCROLL_LEFT },
1465 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1466 { KEY_IC, REQ_SCROLL_LINE_UP },
1467 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1468 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1469 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1470 { 'w', REQ_SCROLL_PAGE_UP },
1471 { 's', REQ_SCROLL_PAGE_DOWN },
1473 /* Searching */
1474 { '/', REQ_SEARCH },
1475 { '?', REQ_SEARCH_BACK },
1476 { 'n', REQ_FIND_NEXT },
1477 { 'N', REQ_FIND_PREV },
1479 /* Misc */
1480 { 'Q', REQ_QUIT },
1481 { 'z', REQ_STOP_LOADING },
1482 { 'v', REQ_SHOW_VERSION },
1483 { 'r', REQ_SCREEN_REDRAW },
1484 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1485 { 'o', REQ_OPTIONS },
1486 { '.', REQ_TOGGLE_LINENO },
1487 { 'D', REQ_TOGGLE_DATE },
1488 { 'A', REQ_TOGGLE_AUTHOR },
1489 { 'g', REQ_TOGGLE_REV_GRAPH },
1490 { 'F', REQ_TOGGLE_REFS },
1491 { 'I', REQ_TOGGLE_SORT_ORDER },
1492 { 'i', REQ_TOGGLE_SORT_FIELD },
1493 { ':', REQ_PROMPT },
1494 { 'u', REQ_STATUS_UPDATE },
1495 { '!', REQ_STATUS_REVERT },
1496 { 'M', REQ_STATUS_MERGE },
1497 { '@', REQ_STAGE_NEXT },
1498 { ',', REQ_PARENT },
1499 { 'e', REQ_EDIT },
1500 };
1502 #define KEYMAP_INFO \
1503 KEYMAP_(GENERIC), \
1504 KEYMAP_(MAIN), \
1505 KEYMAP_(DIFF), \
1506 KEYMAP_(LOG), \
1507 KEYMAP_(TREE), \
1508 KEYMAP_(BLOB), \
1509 KEYMAP_(BLAME), \
1510 KEYMAP_(BRANCH), \
1511 KEYMAP_(PAGER), \
1512 KEYMAP_(HELP), \
1513 KEYMAP_(STATUS), \
1514 KEYMAP_(STAGE)
1516 enum keymap {
1517 #define KEYMAP_(name) KEYMAP_##name
1518 KEYMAP_INFO
1519 #undef KEYMAP_
1520 };
1522 static const struct enum_map keymap_table[] = {
1523 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1524 KEYMAP_INFO
1525 #undef KEYMAP_
1526 };
1528 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1530 struct keybinding_table {
1531 struct keybinding *data;
1532 size_t size;
1533 };
1535 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1537 static void
1538 add_keybinding(enum keymap keymap, enum request request, int key)
1539 {
1540 struct keybinding_table *table = &keybindings[keymap];
1541 size_t i;
1543 for (i = 0; i < keybindings[keymap].size; i++) {
1544 if (keybindings[keymap].data[i].alias == key) {
1545 keybindings[keymap].data[i].request = request;
1546 return;
1547 }
1548 }
1550 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1551 if (!table->data)
1552 die("Failed to allocate keybinding");
1553 table->data[table->size].alias = key;
1554 table->data[table->size++].request = request;
1556 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1557 int i;
1559 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1560 if (default_keybindings[i].alias == key)
1561 default_keybindings[i].request = REQ_NONE;
1562 }
1563 }
1565 /* Looks for a key binding first in the given map, then in the generic map, and
1566 * lastly in the default keybindings. */
1567 static enum request
1568 get_keybinding(enum keymap keymap, int key)
1569 {
1570 size_t i;
1572 for (i = 0; i < keybindings[keymap].size; i++)
1573 if (keybindings[keymap].data[i].alias == key)
1574 return keybindings[keymap].data[i].request;
1576 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1577 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1578 return keybindings[KEYMAP_GENERIC].data[i].request;
1580 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1581 if (default_keybindings[i].alias == key)
1582 return default_keybindings[i].request;
1584 return (enum request) key;
1585 }
1588 struct key {
1589 const char *name;
1590 int value;
1591 };
1593 static const struct key key_table[] = {
1594 { "Enter", KEY_RETURN },
1595 { "Space", ' ' },
1596 { "Backspace", KEY_BACKSPACE },
1597 { "Tab", KEY_TAB },
1598 { "Escape", KEY_ESC },
1599 { "Left", KEY_LEFT },
1600 { "Right", KEY_RIGHT },
1601 { "Up", KEY_UP },
1602 { "Down", KEY_DOWN },
1603 { "Insert", KEY_IC },
1604 { "Delete", KEY_DC },
1605 { "Hash", '#' },
1606 { "Home", KEY_HOME },
1607 { "End", KEY_END },
1608 { "PageUp", KEY_PPAGE },
1609 { "PageDown", KEY_NPAGE },
1610 { "F1", KEY_F(1) },
1611 { "F2", KEY_F(2) },
1612 { "F3", KEY_F(3) },
1613 { "F4", KEY_F(4) },
1614 { "F5", KEY_F(5) },
1615 { "F6", KEY_F(6) },
1616 { "F7", KEY_F(7) },
1617 { "F8", KEY_F(8) },
1618 { "F9", KEY_F(9) },
1619 { "F10", KEY_F(10) },
1620 { "F11", KEY_F(11) },
1621 { "F12", KEY_F(12) },
1622 };
1624 static int
1625 get_key_value(const char *name)
1626 {
1627 int i;
1629 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1630 if (!strcasecmp(key_table[i].name, name))
1631 return key_table[i].value;
1633 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1634 return (int)name[1] & 0x1f;
1635 if (strlen(name) == 1 && isprint(*name))
1636 return (int) *name;
1637 return ERR;
1638 }
1640 static const char *
1641 get_key_name(int key_value)
1642 {
1643 static char key_char[] = "'X'\0";
1644 const char *seq = NULL;
1645 int key;
1647 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1648 if (key_table[key].value == key_value)
1649 seq = key_table[key].name;
1651 if (seq == NULL && key_value < 0x7f) {
1652 char *s = key_char + 1;
1654 if (key_value >= 0x20) {
1655 *s++ = key_value;
1656 } else {
1657 *s++ = '^';
1658 *s++ = 0x40 | (key_value & 0x1f);
1659 }
1660 *s++ = '\'';
1661 *s++ = '\0';
1662 seq = key_char;
1663 }
1665 return seq ? seq : "(no key)";
1666 }
1668 static bool
1669 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1670 {
1671 const char *sep = *pos > 0 ? ", " : "";
1672 const char *keyname = get_key_name(keybinding->alias);
1674 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1675 }
1677 static bool
1678 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1679 enum keymap keymap, bool all)
1680 {
1681 int i;
1683 for (i = 0; i < keybindings[keymap].size; i++) {
1684 if (keybindings[keymap].data[i].request == request) {
1685 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1686 return FALSE;
1687 if (!all)
1688 break;
1689 }
1690 }
1692 return TRUE;
1693 }
1695 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1697 static const char *
1698 get_keys(enum keymap keymap, enum request request, bool all)
1699 {
1700 static char buf[BUFSIZ];
1701 size_t pos = 0;
1702 int i;
1704 buf[pos] = 0;
1706 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1707 return "Too many keybindings!";
1708 if (pos > 0 && !all)
1709 return buf;
1711 if (keymap != KEYMAP_GENERIC) {
1712 /* Only the generic keymap includes the default keybindings when
1713 * listing all keys. */
1714 if (all)
1715 return buf;
1717 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1718 return "Too many keybindings!";
1719 if (pos)
1720 return buf;
1721 }
1723 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1724 if (default_keybindings[i].request == request) {
1725 if (!append_key(buf, &pos, &default_keybindings[i]))
1726 return "Too many keybindings!";
1727 if (!all)
1728 return buf;
1729 }
1730 }
1732 return buf;
1733 }
1735 struct run_request {
1736 enum keymap keymap;
1737 int key;
1738 const char **argv;
1739 };
1741 static struct run_request *run_request;
1742 static size_t run_requests;
1744 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1746 static enum request
1747 add_run_request(enum keymap keymap, int key, const char **argv)
1748 {
1749 struct run_request *req;
1751 if (!realloc_run_requests(&run_request, run_requests, 1))
1752 return REQ_NONE;
1754 req = &run_request[run_requests];
1755 req->keymap = keymap;
1756 req->key = key;
1757 req->argv = NULL;
1759 if (!argv_copy(&req->argv, argv))
1760 return REQ_NONE;
1762 return REQ_NONE + ++run_requests;
1763 }
1765 static struct run_request *
1766 get_run_request(enum request request)
1767 {
1768 if (request <= REQ_NONE)
1769 return NULL;
1770 return &run_request[request - REQ_NONE - 1];
1771 }
1773 static void
1774 add_builtin_run_requests(void)
1775 {
1776 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1777 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1778 const char *commit[] = { "git", "commit", NULL };
1779 const char *gc[] = { "git", "gc", NULL };
1780 struct run_request reqs[] = {
1781 { KEYMAP_MAIN, 'C', cherry_pick },
1782 { KEYMAP_STATUS, 'C', commit },
1783 { KEYMAP_BRANCH, 'C', checkout },
1784 { KEYMAP_GENERIC, 'G', gc },
1785 };
1786 int i;
1788 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1789 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1791 if (req != reqs[i].key)
1792 continue;
1793 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1794 if (req != REQ_NONE)
1795 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1796 }
1797 }
1799 /*
1800 * User config file handling.
1801 */
1803 static int config_lineno;
1804 static bool config_errors;
1805 static const char *config_msg;
1807 static const struct enum_map color_map[] = {
1808 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1809 COLOR_MAP(DEFAULT),
1810 COLOR_MAP(BLACK),
1811 COLOR_MAP(BLUE),
1812 COLOR_MAP(CYAN),
1813 COLOR_MAP(GREEN),
1814 COLOR_MAP(MAGENTA),
1815 COLOR_MAP(RED),
1816 COLOR_MAP(WHITE),
1817 COLOR_MAP(YELLOW),
1818 };
1820 static const struct enum_map attr_map[] = {
1821 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1822 ATTR_MAP(NORMAL),
1823 ATTR_MAP(BLINK),
1824 ATTR_MAP(BOLD),
1825 ATTR_MAP(DIM),
1826 ATTR_MAP(REVERSE),
1827 ATTR_MAP(STANDOUT),
1828 ATTR_MAP(UNDERLINE),
1829 };
1831 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1833 static int parse_step(double *opt, const char *arg)
1834 {
1835 *opt = atoi(arg);
1836 if (!strchr(arg, '%'))
1837 return OK;
1839 /* "Shift down" so 100% and 1 does not conflict. */
1840 *opt = (*opt - 1) / 100;
1841 if (*opt >= 1.0) {
1842 *opt = 0.99;
1843 config_msg = "Step value larger than 100%";
1844 return ERR;
1845 }
1846 if (*opt < 0.0) {
1847 *opt = 1;
1848 config_msg = "Invalid step value";
1849 return ERR;
1850 }
1851 return OK;
1852 }
1854 static int
1855 parse_int(int *opt, const char *arg, int min, int max)
1856 {
1857 int value = atoi(arg);
1859 if (min <= value && value <= max) {
1860 *opt = value;
1861 return OK;
1862 }
1864 config_msg = "Integer value out of bound";
1865 return ERR;
1866 }
1868 static bool
1869 set_color(int *color, const char *name)
1870 {
1871 if (map_enum(color, color_map, name))
1872 return TRUE;
1873 if (!prefixcmp(name, "color"))
1874 return parse_int(color, name + 5, 0, 255) == OK;
1875 return FALSE;
1876 }
1878 /* Wants: object fgcolor bgcolor [attribute] */
1879 static int
1880 option_color_command(int argc, const char *argv[])
1881 {
1882 struct line_info *info;
1884 if (argc < 3) {
1885 config_msg = "Wrong number of arguments given to color command";
1886 return ERR;
1887 }
1889 info = get_line_info(argv[0]);
1890 if (!info) {
1891 static const struct enum_map obsolete[] = {
1892 ENUM_MAP("main-delim", LINE_DELIMITER),
1893 ENUM_MAP("main-date", LINE_DATE),
1894 ENUM_MAP("main-author", LINE_AUTHOR),
1895 };
1896 int index;
1898 if (!map_enum(&index, obsolete, argv[0])) {
1899 config_msg = "Unknown color name";
1900 return ERR;
1901 }
1902 info = &line_info[index];
1903 }
1905 if (!set_color(&info->fg, argv[1]) ||
1906 !set_color(&info->bg, argv[2])) {
1907 config_msg = "Unknown color";
1908 return ERR;
1909 }
1911 info->attr = 0;
1912 while (argc-- > 3) {
1913 int attr;
1915 if (!set_attribute(&attr, argv[argc])) {
1916 config_msg = "Unknown attribute";
1917 return ERR;
1918 }
1919 info->attr |= attr;
1920 }
1922 return OK;
1923 }
1925 static int parse_bool(bool *opt, const char *arg)
1926 {
1927 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1928 ? TRUE : FALSE;
1929 return OK;
1930 }
1932 static int parse_enum_do(unsigned int *opt, const char *arg,
1933 const struct enum_map *map, size_t map_size)
1934 {
1935 bool is_true;
1937 assert(map_size > 1);
1939 if (map_enum_do(map, map_size, (int *) opt, arg))
1940 return OK;
1942 if (parse_bool(&is_true, arg) != OK)
1943 return ERR;
1945 *opt = is_true ? map[1].value : map[0].value;
1946 return OK;
1947 }
1949 #define parse_enum(opt, arg, map) \
1950 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1952 static int
1953 parse_string(char *opt, const char *arg, size_t optsize)
1954 {
1955 int arglen = strlen(arg);
1957 switch (arg[0]) {
1958 case '\"':
1959 case '\'':
1960 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1961 config_msg = "Unmatched quotation";
1962 return ERR;
1963 }
1964 arg += 1; arglen -= 2;
1965 default:
1966 string_ncopy_do(opt, optsize, arg, arglen);
1967 return OK;
1968 }
1969 }
1971 /* Wants: name = value */
1972 static int
1973 option_set_command(int argc, const char *argv[])
1974 {
1975 if (argc != 3) {
1976 config_msg = "Wrong number of arguments given to set command";
1977 return ERR;
1978 }
1980 if (strcmp(argv[1], "=")) {
1981 config_msg = "No value assigned";
1982 return ERR;
1983 }
1985 if (!strcmp(argv[0], "show-author"))
1986 return parse_enum(&opt_author, argv[2], author_map);
1988 if (!strcmp(argv[0], "show-date"))
1989 return parse_enum(&opt_date, argv[2], date_map);
1991 if (!strcmp(argv[0], "show-rev-graph"))
1992 return parse_bool(&opt_rev_graph, argv[2]);
1994 if (!strcmp(argv[0], "show-refs"))
1995 return parse_bool(&opt_show_refs, argv[2]);
1997 if (!strcmp(argv[0], "show-line-numbers"))
1998 return parse_bool(&opt_line_number, argv[2]);
2000 if (!strcmp(argv[0], "line-graphics"))
2001 return parse_bool(&opt_line_graphics, argv[2]);
2003 if (!strcmp(argv[0], "line-number-interval"))
2004 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2006 if (!strcmp(argv[0], "author-width"))
2007 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2009 if (!strcmp(argv[0], "horizontal-scroll"))
2010 return parse_step(&opt_hscroll, argv[2]);
2012 if (!strcmp(argv[0], "split-view-height"))
2013 return parse_step(&opt_scale_split_view, argv[2]);
2015 if (!strcmp(argv[0], "tab-size"))
2016 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2018 if (!strcmp(argv[0], "commit-encoding"))
2019 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2021 if (!strcmp(argv[0], "status-untracked-dirs"))
2022 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2024 config_msg = "Unknown variable name";
2025 return ERR;
2026 }
2028 /* Wants: mode request key */
2029 static int
2030 option_bind_command(int argc, const char *argv[])
2031 {
2032 enum request request;
2033 int keymap = -1;
2034 int key;
2036 if (argc < 3) {
2037 config_msg = "Wrong number of arguments given to bind command";
2038 return ERR;
2039 }
2041 if (!set_keymap(&keymap, argv[0])) {
2042 config_msg = "Unknown key map";
2043 return ERR;
2044 }
2046 key = get_key_value(argv[1]);
2047 if (key == ERR) {
2048 config_msg = "Unknown key";
2049 return ERR;
2050 }
2052 request = get_request(argv[2]);
2053 if (request == REQ_UNKNOWN) {
2054 static const struct enum_map obsolete[] = {
2055 ENUM_MAP("cherry-pick", REQ_NONE),
2056 ENUM_MAP("screen-resize", REQ_NONE),
2057 ENUM_MAP("tree-parent", REQ_PARENT),
2058 };
2059 int alias;
2061 if (map_enum(&alias, obsolete, argv[2])) {
2062 if (alias != REQ_NONE)
2063 add_keybinding(keymap, alias, key);
2064 config_msg = "Obsolete request name";
2065 return ERR;
2066 }
2067 }
2068 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2069 request = add_run_request(keymap, key, argv + 2);
2070 if (request == REQ_UNKNOWN) {
2071 config_msg = "Unknown request name";
2072 return ERR;
2073 }
2075 add_keybinding(keymap, request, key);
2077 return OK;
2078 }
2080 static int
2081 set_option(const char *opt, char *value)
2082 {
2083 const char *argv[SIZEOF_ARG];
2084 int argc = 0;
2086 if (!argv_from_string(argv, &argc, value)) {
2087 config_msg = "Too many option arguments";
2088 return ERR;
2089 }
2091 if (!strcmp(opt, "color"))
2092 return option_color_command(argc, argv);
2094 if (!strcmp(opt, "set"))
2095 return option_set_command(argc, argv);
2097 if (!strcmp(opt, "bind"))
2098 return option_bind_command(argc, argv);
2100 config_msg = "Unknown option command";
2101 return ERR;
2102 }
2104 static int
2105 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2106 {
2107 int status = OK;
2109 config_lineno++;
2110 config_msg = "Internal error";
2112 /* Check for comment markers, since read_properties() will
2113 * only ensure opt and value are split at first " \t". */
2114 optlen = strcspn(opt, "#");
2115 if (optlen == 0)
2116 return OK;
2118 if (opt[optlen] != 0) {
2119 config_msg = "No option value";
2120 status = ERR;
2122 } else {
2123 /* Look for comment endings in the value. */
2124 size_t len = strcspn(value, "#");
2126 if (len < valuelen) {
2127 valuelen = len;
2128 value[valuelen] = 0;
2129 }
2131 status = set_option(opt, value);
2132 }
2134 if (status == ERR) {
2135 warn("Error on line %d, near '%.*s': %s",
2136 config_lineno, (int) optlen, opt, config_msg);
2137 config_errors = TRUE;
2138 }
2140 /* Always keep going if errors are encountered. */
2141 return OK;
2142 }
2144 static void
2145 load_option_file(const char *path)
2146 {
2147 struct io io;
2149 /* It's OK that the file doesn't exist. */
2150 if (!io_open(&io, "%s", path))
2151 return;
2153 config_lineno = 0;
2154 config_errors = FALSE;
2156 if (io_load(&io, " \t", read_option) == ERR ||
2157 config_errors == TRUE)
2158 warn("Errors while loading %s.", path);
2159 }
2161 static int
2162 load_options(void)
2163 {
2164 const char *home = getenv("HOME");
2165 const char *tigrc_user = getenv("TIGRC_USER");
2166 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2167 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2168 char buf[SIZEOF_STR];
2170 if (!tigrc_system)
2171 tigrc_system = SYSCONFDIR "/tigrc";
2172 load_option_file(tigrc_system);
2174 if (!tigrc_user) {
2175 if (!home || !string_format(buf, "%s/.tigrc", home))
2176 return ERR;
2177 tigrc_user = buf;
2178 }
2179 load_option_file(tigrc_user);
2181 /* Add _after_ loading config files to avoid adding run requests
2182 * that conflict with keybindings. */
2183 add_builtin_run_requests();
2185 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2186 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2187 int argc = 0;
2189 if (!string_format(buf, "%s", tig_diff_opts) ||
2190 !argv_from_string(diff_opts, &argc, buf))
2191 die("TIG_DIFF_OPTS contains too many arguments");
2192 else if (!argv_copy(&opt_diff_args, diff_opts))
2193 die("Failed to format TIG_DIFF_OPTS arguments");
2194 }
2196 return OK;
2197 }
2200 /*
2201 * The viewer
2202 */
2204 struct view;
2205 struct view_ops;
2207 /* The display array of active views and the index of the current view. */
2208 static struct view *display[2];
2209 static unsigned int current_view;
2211 #define foreach_displayed_view(view, i) \
2212 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2214 #define displayed_views() (display[1] != NULL ? 2 : 1)
2216 /* Current head and commit ID */
2217 static char ref_blob[SIZEOF_REF] = "";
2218 static char ref_commit[SIZEOF_REF] = "HEAD";
2219 static char ref_head[SIZEOF_REF] = "HEAD";
2220 static char ref_branch[SIZEOF_REF] = "";
2222 enum view_type {
2223 VIEW_MAIN,
2224 VIEW_DIFF,
2225 VIEW_LOG,
2226 VIEW_TREE,
2227 VIEW_BLOB,
2228 VIEW_BLAME,
2229 VIEW_BRANCH,
2230 VIEW_HELP,
2231 VIEW_PAGER,
2232 VIEW_STATUS,
2233 VIEW_STAGE,
2234 };
2236 struct view {
2237 enum view_type type; /* View type */
2238 const char *name; /* View name */
2239 const char *cmd_env; /* Command line set via environment */
2240 const char *id; /* Points to either of ref_{head,commit,blob} */
2242 struct view_ops *ops; /* View operations */
2244 enum keymap keymap; /* What keymap does this view have */
2245 bool git_dir; /* Whether the view requires a git directory. */
2247 char ref[SIZEOF_REF]; /* Hovered commit reference */
2248 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2250 int height, width; /* The width and height of the main window */
2251 WINDOW *win; /* The main window */
2252 WINDOW *title; /* The title window living below the main window */
2254 /* Navigation */
2255 unsigned long offset; /* Offset of the window top */
2256 unsigned long yoffset; /* Offset from the window side. */
2257 unsigned long lineno; /* Current line number */
2258 unsigned long p_offset; /* Previous offset of the window top */
2259 unsigned long p_yoffset;/* Previous offset from the window side */
2260 unsigned long p_lineno; /* Previous current line number */
2261 bool p_restore; /* Should the previous position be restored. */
2263 /* Searching */
2264 char grep[SIZEOF_STR]; /* Search string */
2265 regex_t *regex; /* Pre-compiled regexp */
2267 /* If non-NULL, points to the view that opened this view. If this view
2268 * is closed tig will switch back to the parent view. */
2269 struct view *parent;
2270 struct view *prev;
2272 /* Buffering */
2273 size_t lines; /* Total number of lines */
2274 struct line *line; /* Line index */
2275 unsigned int digits; /* Number of digits in the lines member. */
2277 /* Drawing */
2278 struct line *curline; /* Line currently being drawn. */
2279 enum line_type curtype; /* Attribute currently used for drawing. */
2280 unsigned long col; /* Column when drawing. */
2281 bool has_scrolled; /* View was scrolled. */
2283 /* Loading */
2284 const char **argv; /* Shell command arguments. */
2285 const char *dir; /* Directory from which to execute. */
2286 struct io io;
2287 struct io *pipe;
2288 time_t start_time;
2289 time_t update_secs;
2290 };
2292 struct view_ops {
2293 /* What type of content being displayed. Used in the title bar. */
2294 const char *type;
2295 /* Default command arguments. */
2296 const char **argv;
2297 /* Open and reads in all view content. */
2298 bool (*open)(struct view *view);
2299 /* Read one line; updates view->line. */
2300 bool (*read)(struct view *view, char *data);
2301 /* Draw one line; @lineno must be < view->height. */
2302 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2303 /* Depending on view handle a special requests. */
2304 enum request (*request)(struct view *view, enum request request, struct line *line);
2305 /* Search for regexp in a line. */
2306 bool (*grep)(struct view *view, struct line *line);
2307 /* Select line */
2308 void (*select)(struct view *view, struct line *line);
2309 /* Prepare view for loading */
2310 bool (*prepare)(struct view *view);
2311 };
2313 static struct view_ops blame_ops;
2314 static struct view_ops blob_ops;
2315 static struct view_ops diff_ops;
2316 static struct view_ops help_ops;
2317 static struct view_ops log_ops;
2318 static struct view_ops main_ops;
2319 static struct view_ops pager_ops;
2320 static struct view_ops stage_ops;
2321 static struct view_ops status_ops;
2322 static struct view_ops tree_ops;
2323 static struct view_ops branch_ops;
2325 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2326 { type, name, #env, ref, ops, map, git }
2328 #define VIEW_(id, name, ops, git, ref) \
2329 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2331 static struct view views[] = {
2332 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2333 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2334 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2335 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2336 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2337 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2338 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2339 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2340 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2341 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2342 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2343 };
2345 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2347 #define foreach_view(view, i) \
2348 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2350 #define view_is_displayed(view) \
2351 (view == display[0] || view == display[1])
2353 static enum request
2354 view_request(struct view *view, enum request request)
2355 {
2356 if (!view || !view->lines)
2357 return request;
2358 return view->ops->request(view, request, &view->line[view->lineno]);
2359 }
2362 /*
2363 * View drawing.
2364 */
2366 static inline void
2367 set_view_attr(struct view *view, enum line_type type)
2368 {
2369 if (!view->curline->selected && view->curtype != type) {
2370 (void) wattrset(view->win, get_line_attr(type));
2371 wchgat(view->win, -1, 0, type, NULL);
2372 view->curtype = type;
2373 }
2374 }
2376 static int
2377 draw_chars(struct view *view, enum line_type type, const char *string,
2378 int max_len, bool use_tilde)
2379 {
2380 static char out_buffer[BUFSIZ * 2];
2381 int len = 0;
2382 int col = 0;
2383 int trimmed = FALSE;
2384 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2386 if (max_len <= 0)
2387 return 0;
2389 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2391 set_view_attr(view, type);
2392 if (len > 0) {
2393 if (opt_iconv_out != ICONV_NONE) {
2394 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2395 size_t inlen = len + 1;
2397 char *outbuf = out_buffer;
2398 size_t outlen = sizeof(out_buffer);
2400 size_t ret;
2402 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2403 if (ret != (size_t) -1) {
2404 string = out_buffer;
2405 len = sizeof(out_buffer) - outlen;
2406 }
2407 }
2409 waddnstr(view->win, string, len);
2411 if (trimmed && use_tilde) {
2412 set_view_attr(view, LINE_DELIMITER);
2413 waddch(view->win, '~');
2414 col++;
2415 }
2416 }
2418 return col;
2419 }
2421 static int
2422 draw_space(struct view *view, enum line_type type, int max, int spaces)
2423 {
2424 static char space[] = " ";
2425 int col = 0;
2427 spaces = MIN(max, spaces);
2429 while (spaces > 0) {
2430 int len = MIN(spaces, sizeof(space) - 1);
2432 col += draw_chars(view, type, space, len, FALSE);
2433 spaces -= len;
2434 }
2436 return col;
2437 }
2439 static bool
2440 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2441 {
2442 char text[SIZEOF_STR];
2444 do {
2445 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2447 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2448 string += pos;
2449 } while (*string && view->width + view->yoffset > view->col);
2451 return view->width + view->yoffset <= view->col;
2452 }
2454 static bool
2455 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2456 {
2457 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2458 int max = view->width + view->yoffset - view->col;
2459 int i;
2461 if (max < size)
2462 size = max;
2464 set_view_attr(view, type);
2465 /* Using waddch() instead of waddnstr() ensures that
2466 * they'll be rendered correctly for the cursor line. */
2467 for (i = skip; i < size; i++)
2468 waddch(view->win, graphic[i]);
2470 view->col += size;
2471 if (size < max && skip <= size)
2472 waddch(view->win, ' ');
2473 view->col++;
2475 return view->width + view->yoffset <= view->col;
2476 }
2478 static bool
2479 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2480 {
2481 int max = MIN(view->width + view->yoffset - view->col, len);
2482 int col;
2484 if (text)
2485 col = draw_chars(view, type, text, max - 1, trim);
2486 else
2487 col = draw_space(view, type, max - 1, max - 1);
2489 view->col += col;
2490 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2491 return view->width + view->yoffset <= view->col;
2492 }
2494 static bool
2495 draw_date(struct view *view, struct time *time)
2496 {
2497 const char *date = mkdate(time, opt_date);
2498 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2500 return draw_field(view, LINE_DATE, date, cols, FALSE);
2501 }
2503 static bool
2504 draw_author(struct view *view, const char *author)
2505 {
2506 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2507 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2509 if (abbreviate && author)
2510 author = get_author_initials(author);
2512 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2513 }
2515 static bool
2516 draw_mode(struct view *view, mode_t mode)
2517 {
2518 const char *str;
2520 if (S_ISDIR(mode))
2521 str = "drwxr-xr-x";
2522 else if (S_ISLNK(mode))
2523 str = "lrwxrwxrwx";
2524 else if (S_ISGITLINK(mode))
2525 str = "m---------";
2526 else if (S_ISREG(mode) && mode & S_IXUSR)
2527 str = "-rwxr-xr-x";
2528 else if (S_ISREG(mode))
2529 str = "-rw-r--r--";
2530 else
2531 str = "----------";
2533 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2534 }
2536 static bool
2537 draw_lineno(struct view *view, unsigned int lineno)
2538 {
2539 char number[10];
2540 int digits3 = view->digits < 3 ? 3 : view->digits;
2541 int max = MIN(view->width + view->yoffset - view->col, digits3);
2542 char *text = NULL;
2543 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2545 lineno += view->offset + 1;
2546 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2547 static char fmt[] = "%1ld";
2549 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2550 if (string_format(number, fmt, lineno))
2551 text = number;
2552 }
2553 if (text)
2554 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2555 else
2556 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2557 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2558 }
2560 static bool
2561 draw_view_line(struct view *view, unsigned int lineno)
2562 {
2563 struct line *line;
2564 bool selected = (view->offset + lineno == view->lineno);
2566 assert(view_is_displayed(view));
2568 if (view->offset + lineno >= view->lines)
2569 return FALSE;
2571 line = &view->line[view->offset + lineno];
2573 wmove(view->win, lineno, 0);
2574 if (line->cleareol)
2575 wclrtoeol(view->win);
2576 view->col = 0;
2577 view->curline = line;
2578 view->curtype = LINE_NONE;
2579 line->selected = FALSE;
2580 line->dirty = line->cleareol = 0;
2582 if (selected) {
2583 set_view_attr(view, LINE_CURSOR);
2584 line->selected = TRUE;
2585 view->ops->select(view, line);
2586 }
2588 return view->ops->draw(view, line, lineno);
2589 }
2591 static void
2592 redraw_view_dirty(struct view *view)
2593 {
2594 bool dirty = FALSE;
2595 int lineno;
2597 for (lineno = 0; lineno < view->height; lineno++) {
2598 if (view->offset + lineno >= view->lines)
2599 break;
2600 if (!view->line[view->offset + lineno].dirty)
2601 continue;
2602 dirty = TRUE;
2603 if (!draw_view_line(view, lineno))
2604 break;
2605 }
2607 if (!dirty)
2608 return;
2609 wnoutrefresh(view->win);
2610 }
2612 static void
2613 redraw_view_from(struct view *view, int lineno)
2614 {
2615 assert(0 <= lineno && lineno < view->height);
2617 for (; lineno < view->height; lineno++) {
2618 if (!draw_view_line(view, lineno))
2619 break;
2620 }
2622 wnoutrefresh(view->win);
2623 }
2625 static void
2626 redraw_view(struct view *view)
2627 {
2628 werase(view->win);
2629 redraw_view_from(view, 0);
2630 }
2633 static void
2634 update_view_title(struct view *view)
2635 {
2636 char buf[SIZEOF_STR];
2637 char state[SIZEOF_STR];
2638 size_t bufpos = 0, statelen = 0;
2640 assert(view_is_displayed(view));
2642 if (view->type != VIEW_STATUS && view->lines) {
2643 unsigned int view_lines = view->offset + view->height;
2644 unsigned int lines = view->lines
2645 ? MIN(view_lines, view->lines) * 100 / view->lines
2646 : 0;
2648 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2649 view->ops->type,
2650 view->lineno + 1,
2651 view->lines,
2652 lines);
2654 }
2656 if (view->pipe) {
2657 time_t secs = time(NULL) - view->start_time;
2659 /* Three git seconds are a long time ... */
2660 if (secs > 2)
2661 string_format_from(state, &statelen, " loading %lds", secs);
2662 }
2664 string_format_from(buf, &bufpos, "[%s]", view->name);
2665 if (*view->ref && bufpos < view->width) {
2666 size_t refsize = strlen(view->ref);
2667 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2669 if (minsize < view->width)
2670 refsize = view->width - minsize + 7;
2671 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2672 }
2674 if (statelen && bufpos < view->width) {
2675 string_format_from(buf, &bufpos, "%s", state);
2676 }
2678 if (view == display[current_view])
2679 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2680 else
2681 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2683 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2684 wclrtoeol(view->title);
2685 wnoutrefresh(view->title);
2686 }
2688 static int
2689 apply_step(double step, int value)
2690 {
2691 if (step >= 1)
2692 return (int) step;
2693 value *= step + 0.01;
2694 return value ? value : 1;
2695 }
2697 static void
2698 resize_display(void)
2699 {
2700 int offset, i;
2701 struct view *base = display[0];
2702 struct view *view = display[1] ? display[1] : display[0];
2704 /* Setup window dimensions */
2706 getmaxyx(stdscr, base->height, base->width);
2708 /* Make room for the status window. */
2709 base->height -= 1;
2711 if (view != base) {
2712 /* Horizontal split. */
2713 view->width = base->width;
2714 view->height = apply_step(opt_scale_split_view, base->height);
2715 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2716 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2717 base->height -= view->height;
2719 /* Make room for the title bar. */
2720 view->height -= 1;
2721 }
2723 /* Make room for the title bar. */
2724 base->height -= 1;
2726 offset = 0;
2728 foreach_displayed_view (view, i) {
2729 if (!view->win) {
2730 view->win = newwin(view->height, 0, offset, 0);
2731 if (!view->win)
2732 die("Failed to create %s view", view->name);
2734 scrollok(view->win, FALSE);
2736 view->title = newwin(1, 0, offset + view->height, 0);
2737 if (!view->title)
2738 die("Failed to create title window");
2740 } else {
2741 wresize(view->win, view->height, view->width);
2742 mvwin(view->win, offset, 0);
2743 mvwin(view->title, offset + view->height, 0);
2744 }
2746 offset += view->height + 1;
2747 }
2748 }
2750 static void
2751 redraw_display(bool clear)
2752 {
2753 struct view *view;
2754 int i;
2756 foreach_displayed_view (view, i) {
2757 if (clear)
2758 wclear(view->win);
2759 redraw_view(view);
2760 update_view_title(view);
2761 }
2762 }
2765 /*
2766 * Option management
2767 */
2769 static void
2770 toggle_enum_option_do(unsigned int *opt, const char *help,
2771 const struct enum_map *map, size_t size)
2772 {
2773 *opt = (*opt + 1) % size;
2774 redraw_display(FALSE);
2775 report("Displaying %s %s", enum_name(map[*opt]), help);
2776 }
2778 #define toggle_enum_option(opt, help, map) \
2779 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2781 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2782 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2784 static void
2785 toggle_view_option(bool *option, const char *help)
2786 {
2787 *option = !*option;
2788 redraw_display(FALSE);
2789 report("%sabling %s", *option ? "En" : "Dis", help);
2790 }
2792 static void
2793 open_option_menu(void)
2794 {
2795 const struct menu_item menu[] = {
2796 { '.', "line numbers", &opt_line_number },
2797 { 'D', "date display", &opt_date },
2798 { 'A', "author display", &opt_author },
2799 { 'g', "revision graph display", &opt_rev_graph },
2800 { 'F', "reference display", &opt_show_refs },
2801 { 0 }
2802 };
2803 int selected = 0;
2805 if (prompt_menu("Toggle option", menu, &selected)) {
2806 if (menu[selected].data == &opt_date)
2807 toggle_date();
2808 else if (menu[selected].data == &opt_author)
2809 toggle_author();
2810 else
2811 toggle_view_option(menu[selected].data, menu[selected].text);
2812 }
2813 }
2815 static void
2816 maximize_view(struct view *view)
2817 {
2818 memset(display, 0, sizeof(display));
2819 current_view = 0;
2820 display[current_view] = view;
2821 resize_display();
2822 redraw_display(FALSE);
2823 report("");
2824 }
2827 /*
2828 * Navigation
2829 */
2831 static bool
2832 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2833 {
2834 if (lineno >= view->lines)
2835 lineno = view->lines > 0 ? view->lines - 1 : 0;
2837 if (offset > lineno || offset + view->height <= lineno) {
2838 unsigned long half = view->height / 2;
2840 if (lineno > half)
2841 offset = lineno - half;
2842 else
2843 offset = 0;
2844 }
2846 if (offset != view->offset || lineno != view->lineno) {
2847 view->offset = offset;
2848 view->lineno = lineno;
2849 return TRUE;
2850 }
2852 return FALSE;
2853 }
2855 /* Scrolling backend */
2856 static void
2857 do_scroll_view(struct view *view, int lines)
2858 {
2859 bool redraw_current_line = FALSE;
2861 /* The rendering expects the new offset. */
2862 view->offset += lines;
2864 assert(0 <= view->offset && view->offset < view->lines);
2865 assert(lines);
2867 /* Move current line into the view. */
2868 if (view->lineno < view->offset) {
2869 view->lineno = view->offset;
2870 redraw_current_line = TRUE;
2871 } else if (view->lineno >= view->offset + view->height) {
2872 view->lineno = view->offset + view->height - 1;
2873 redraw_current_line = TRUE;
2874 }
2876 assert(view->offset <= view->lineno && view->lineno < view->lines);
2878 /* Redraw the whole screen if scrolling is pointless. */
2879 if (view->height < ABS(lines)) {
2880 redraw_view(view);
2882 } else {
2883 int line = lines > 0 ? view->height - lines : 0;
2884 int end = line + ABS(lines);
2886 scrollok(view->win, TRUE);
2887 wscrl(view->win, lines);
2888 scrollok(view->win, FALSE);
2890 while (line < end && draw_view_line(view, line))
2891 line++;
2893 if (redraw_current_line)
2894 draw_view_line(view, view->lineno - view->offset);
2895 wnoutrefresh(view->win);
2896 }
2898 view->has_scrolled = TRUE;
2899 report("");
2900 }
2902 /* Scroll frontend */
2903 static void
2904 scroll_view(struct view *view, enum request request)
2905 {
2906 int lines = 1;
2908 assert(view_is_displayed(view));
2910 switch (request) {
2911 case REQ_SCROLL_FIRST_COL:
2912 view->yoffset = 0;
2913 redraw_view_from(view, 0);
2914 report("");
2915 return;
2916 case REQ_SCROLL_LEFT:
2917 if (view->yoffset == 0) {
2918 report("Cannot scroll beyond the first column");
2919 return;
2920 }
2921 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2922 view->yoffset = 0;
2923 else
2924 view->yoffset -= apply_step(opt_hscroll, view->width);
2925 redraw_view_from(view, 0);
2926 report("");
2927 return;
2928 case REQ_SCROLL_RIGHT:
2929 view->yoffset += apply_step(opt_hscroll, view->width);
2930 redraw_view(view);
2931 report("");
2932 return;
2933 case REQ_SCROLL_PAGE_DOWN:
2934 lines = view->height;
2935 case REQ_SCROLL_LINE_DOWN:
2936 if (view->offset + lines > view->lines)
2937 lines = view->lines - view->offset;
2939 if (lines == 0 || view->offset + view->height >= view->lines) {
2940 report("Cannot scroll beyond the last line");
2941 return;
2942 }
2943 break;
2945 case REQ_SCROLL_PAGE_UP:
2946 lines = view->height;
2947 case REQ_SCROLL_LINE_UP:
2948 if (lines > view->offset)
2949 lines = view->offset;
2951 if (lines == 0) {
2952 report("Cannot scroll beyond the first line");
2953 return;
2954 }
2956 lines = -lines;
2957 break;
2959 default:
2960 die("request %d not handled in switch", request);
2961 }
2963 do_scroll_view(view, lines);
2964 }
2966 /* Cursor moving */
2967 static void
2968 move_view(struct view *view, enum request request)
2969 {
2970 int scroll_steps = 0;
2971 int steps;
2973 switch (request) {
2974 case REQ_MOVE_FIRST_LINE:
2975 steps = -view->lineno;
2976 break;
2978 case REQ_MOVE_LAST_LINE:
2979 steps = view->lines - view->lineno - 1;
2980 break;
2982 case REQ_MOVE_PAGE_UP:
2983 steps = view->height > view->lineno
2984 ? -view->lineno : -view->height;
2985 break;
2987 case REQ_MOVE_PAGE_DOWN:
2988 steps = view->lineno + view->height >= view->lines
2989 ? view->lines - view->lineno - 1 : view->height;
2990 break;
2992 case REQ_MOVE_UP:
2993 steps = -1;
2994 break;
2996 case REQ_MOVE_DOWN:
2997 steps = 1;
2998 break;
3000 default:
3001 die("request %d not handled in switch", request);
3002 }
3004 if (steps <= 0 && view->lineno == 0) {
3005 report("Cannot move beyond the first line");
3006 return;
3008 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3009 report("Cannot move beyond the last line");
3010 return;
3011 }
3013 /* Move the current line */
3014 view->lineno += steps;
3015 assert(0 <= view->lineno && view->lineno < view->lines);
3017 /* Check whether the view needs to be scrolled */
3018 if (view->lineno < view->offset ||
3019 view->lineno >= view->offset + view->height) {
3020 scroll_steps = steps;
3021 if (steps < 0 && -steps > view->offset) {
3022 scroll_steps = -view->offset;
3024 } else if (steps > 0) {
3025 if (view->lineno == view->lines - 1 &&
3026 view->lines > view->height) {
3027 scroll_steps = view->lines - view->offset - 1;
3028 if (scroll_steps >= view->height)
3029 scroll_steps -= view->height - 1;
3030 }
3031 }
3032 }
3034 if (!view_is_displayed(view)) {
3035 view->offset += scroll_steps;
3036 assert(0 <= view->offset && view->offset < view->lines);
3037 view->ops->select(view, &view->line[view->lineno]);
3038 return;
3039 }
3041 /* Repaint the old "current" line if we be scrolling */
3042 if (ABS(steps) < view->height)
3043 draw_view_line(view, view->lineno - steps - view->offset);
3045 if (scroll_steps) {
3046 do_scroll_view(view, scroll_steps);
3047 return;
3048 }
3050 /* Draw the current line */
3051 draw_view_line(view, view->lineno - view->offset);
3053 wnoutrefresh(view->win);
3054 report("");
3055 }
3058 /*
3059 * Searching
3060 */
3062 static void search_view(struct view *view, enum request request);
3064 static bool
3065 grep_text(struct view *view, const char *text[])
3066 {
3067 regmatch_t pmatch;
3068 size_t i;
3070 for (i = 0; text[i]; i++)
3071 if (*text[i] &&
3072 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3073 return TRUE;
3074 return FALSE;
3075 }
3077 static void
3078 select_view_line(struct view *view, unsigned long lineno)
3079 {
3080 unsigned long old_lineno = view->lineno;
3081 unsigned long old_offset = view->offset;
3083 if (goto_view_line(view, view->offset, lineno)) {
3084 if (view_is_displayed(view)) {
3085 if (old_offset != view->offset) {
3086 redraw_view(view);
3087 } else {
3088 draw_view_line(view, old_lineno - view->offset);
3089 draw_view_line(view, view->lineno - view->offset);
3090 wnoutrefresh(view->win);
3091 }
3092 } else {
3093 view->ops->select(view, &view->line[view->lineno]);
3094 }
3095 }
3096 }
3098 static void
3099 find_next(struct view *view, enum request request)
3100 {
3101 unsigned long lineno = view->lineno;
3102 int direction;
3104 if (!*view->grep) {
3105 if (!*opt_search)
3106 report("No previous search");
3107 else
3108 search_view(view, request);
3109 return;
3110 }
3112 switch (request) {
3113 case REQ_SEARCH:
3114 case REQ_FIND_NEXT:
3115 direction = 1;
3116 break;
3118 case REQ_SEARCH_BACK:
3119 case REQ_FIND_PREV:
3120 direction = -1;
3121 break;
3123 default:
3124 return;
3125 }
3127 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3128 lineno += direction;
3130 /* Note, lineno is unsigned long so will wrap around in which case it
3131 * will become bigger than view->lines. */
3132 for (; lineno < view->lines; lineno += direction) {
3133 if (view->ops->grep(view, &view->line[lineno])) {
3134 select_view_line(view, lineno);
3135 report("Line %ld matches '%s'", lineno + 1, view->grep);
3136 return;
3137 }
3138 }
3140 report("No match found for '%s'", view->grep);
3141 }
3143 static void
3144 search_view(struct view *view, enum request request)
3145 {
3146 int regex_err;
3148 if (view->regex) {
3149 regfree(view->regex);
3150 *view->grep = 0;
3151 } else {
3152 view->regex = calloc(1, sizeof(*view->regex));
3153 if (!view->regex)
3154 return;
3155 }
3157 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3158 if (regex_err != 0) {
3159 char buf[SIZEOF_STR] = "unknown error";
3161 regerror(regex_err, view->regex, buf, sizeof(buf));
3162 report("Search failed: %s", buf);
3163 return;
3164 }
3166 string_copy(view->grep, opt_search);
3168 find_next(view, request);
3169 }
3171 /*
3172 * Incremental updating
3173 */
3175 static void
3176 reset_view(struct view *view)
3177 {
3178 int i;
3180 for (i = 0; i < view->lines; i++)
3181 free(view->line[i].data);
3182 free(view->line);
3184 view->p_offset = view->offset;
3185 view->p_yoffset = view->yoffset;
3186 view->p_lineno = view->lineno;
3188 view->line = NULL;
3189 view->offset = 0;
3190 view->yoffset = 0;
3191 view->lines = 0;
3192 view->lineno = 0;
3193 view->vid[0] = 0;
3194 view->update_secs = 0;
3195 }
3197 static const char *
3198 format_arg(const char *name)
3199 {
3200 static struct {
3201 const char *name;
3202 size_t namelen;
3203 const char *value;
3204 const char *value_if_empty;
3205 } vars[] = {
3206 #define FORMAT_VAR(name, value, value_if_empty) \
3207 { name, STRING_SIZE(name), value, value_if_empty }
3208 FORMAT_VAR("%(directory)", opt_path, ""),
3209 FORMAT_VAR("%(file)", opt_file, ""),
3210 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3211 FORMAT_VAR("%(head)", ref_head, ""),
3212 FORMAT_VAR("%(commit)", ref_commit, ""),
3213 FORMAT_VAR("%(blob)", ref_blob, ""),
3214 FORMAT_VAR("%(branch)", ref_branch, ""),
3215 };
3216 int i;
3218 for (i = 0; i < ARRAY_SIZE(vars); i++)
3219 if (!strncmp(name, vars[i].name, vars[i].namelen))
3220 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3222 report("Unknown replacement: `%s`", name);
3223 return NULL;
3224 }
3226 static bool
3227 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3228 {
3229 char buf[SIZEOF_STR];
3230 int argc;
3232 argv_free(*dst_argv);
3234 for (argc = 0; src_argv[argc]; argc++) {
3235 const char *arg = src_argv[argc];
3236 size_t bufpos = 0;
3238 if (!strcmp(arg, "%(fileargs)")) {
3239 if (!argv_append_array(dst_argv, opt_file_args))
3240 break;
3241 continue;
3243 } else if (!strcmp(arg, "%(diffargs)")) {
3244 if (!argv_append_array(dst_argv, opt_diff_args))
3245 break;
3246 continue;
3248 } else if (!strcmp(arg, "%(revargs)")) {
3249 if (!argv_append_array(dst_argv, opt_rev_args))
3250 break;
3251 continue;
3252 }
3254 while (arg) {
3255 char *next = strstr(arg, "%(");
3256 int len = next - arg;
3257 const char *value;
3259 if (!next || !replace) {
3260 len = strlen(arg);
3261 value = "";
3263 } else {
3264 value = format_arg(next);
3266 if (!value) {
3267 return FALSE;
3268 }
3269 }
3271 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3272 return FALSE;
3274 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3275 }
3277 if (!argv_append(dst_argv, buf))
3278 break;
3279 }
3281 return src_argv[argc] == NULL;
3282 }
3284 static bool
3285 restore_view_position(struct view *view)
3286 {
3287 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3288 return FALSE;
3290 /* Changing the view position cancels the restoring. */
3291 /* FIXME: Changing back to the first line is not detected. */
3292 if (view->offset != 0 || view->lineno != 0) {
3293 view->p_restore = FALSE;
3294 return FALSE;
3295 }
3297 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3298 view_is_displayed(view))
3299 werase(view->win);
3301 view->yoffset = view->p_yoffset;
3302 view->p_restore = FALSE;
3304 return TRUE;
3305 }
3307 static void
3308 end_update(struct view *view, bool force)
3309 {
3310 if (!view->pipe)
3311 return;
3312 while (!view->ops->read(view, NULL))
3313 if (!force)
3314 return;
3315 if (force)
3316 io_kill(view->pipe);
3317 io_done(view->pipe);
3318 view->pipe = NULL;
3319 }
3321 static void
3322 setup_update(struct view *view, const char *vid)
3323 {
3324 reset_view(view);
3325 string_copy_rev(view->vid, vid);
3326 view->pipe = &view->io;
3327 view->start_time = time(NULL);
3328 }
3330 static bool
3331 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3332 {
3333 view->dir = dir;
3334 return format_argv(&view->argv, argv, replace);
3335 }
3337 static bool
3338 prepare_update(struct view *view, const char *argv[], const char *dir)
3339 {
3340 if (view->pipe)
3341 end_update(view, TRUE);
3342 return prepare_io(view, dir, argv, FALSE);
3343 }
3345 static bool
3346 start_update(struct view *view, const char **argv, const char *dir)
3347 {
3348 if (view->pipe)
3349 io_done(view->pipe);
3350 return prepare_io(view, dir, argv, FALSE) &&
3351 io_run(&view->io, IO_RD, dir, view->argv);
3352 }
3354 static bool
3355 prepare_update_file(struct view *view, const char *name)
3356 {
3357 if (view->pipe)
3358 end_update(view, TRUE);
3359 argv_free(view->argv);
3360 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3361 }
3363 static bool
3364 begin_update(struct view *view, bool refresh)
3365 {
3366 if (view->pipe)
3367 end_update(view, TRUE);
3369 if (!refresh) {
3370 if (view->ops->prepare) {
3371 if (!view->ops->prepare(view))
3372 return FALSE;
3373 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3374 return FALSE;
3375 }
3377 /* Put the current ref_* value to the view title ref
3378 * member. This is needed by the blob view. Most other
3379 * views sets it automatically after loading because the
3380 * first line is a commit line. */
3381 string_copy_rev(view->ref, view->id);
3382 }
3384 if (view->argv && view->argv[0] &&
3385 !io_run(&view->io, IO_RD, view->dir, view->argv))
3386 return FALSE;
3388 setup_update(view, view->id);
3390 return TRUE;
3391 }
3393 static bool
3394 update_view(struct view *view)
3395 {
3396 char out_buffer[BUFSIZ * 2];
3397 char *line;
3398 /* Clear the view and redraw everything since the tree sorting
3399 * might have rearranged things. */
3400 bool redraw = view->lines == 0;
3401 bool can_read = TRUE;
3403 if (!view->pipe)
3404 return TRUE;
3406 if (!io_can_read(view->pipe)) {
3407 if (view->lines == 0 && view_is_displayed(view)) {
3408 time_t secs = time(NULL) - view->start_time;
3410 if (secs > 1 && secs > view->update_secs) {
3411 if (view->update_secs == 0)
3412 redraw_view(view);
3413 update_view_title(view);
3414 view->update_secs = secs;
3415 }
3416 }
3417 return TRUE;
3418 }
3420 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3421 if (opt_iconv_in != ICONV_NONE) {
3422 ICONV_CONST char *inbuf = line;
3423 size_t inlen = strlen(line) + 1;
3425 char *outbuf = out_buffer;
3426 size_t outlen = sizeof(out_buffer);
3428 size_t ret;
3430 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3431 if (ret != (size_t) -1)
3432 line = out_buffer;
3433 }
3435 if (!view->ops->read(view, line)) {
3436 report("Allocation failure");
3437 end_update(view, TRUE);
3438 return FALSE;
3439 }
3440 }
3442 {
3443 unsigned long lines = view->lines;
3444 int digits;
3446 for (digits = 0; lines; digits++)
3447 lines /= 10;
3449 /* Keep the displayed view in sync with line number scaling. */
3450 if (digits != view->digits) {
3451 view->digits = digits;
3452 if (opt_line_number || view->type == VIEW_BLAME)
3453 redraw = TRUE;
3454 }
3455 }
3457 if (io_error(view->pipe)) {
3458 report("Failed to read: %s", io_strerror(view->pipe));
3459 end_update(view, TRUE);
3461 } else if (io_eof(view->pipe)) {
3462 if (view_is_displayed(view))
3463 report("");
3464 end_update(view, FALSE);
3465 }
3467 if (restore_view_position(view))
3468 redraw = TRUE;
3470 if (!view_is_displayed(view))
3471 return TRUE;
3473 if (redraw)
3474 redraw_view_from(view, 0);
3475 else
3476 redraw_view_dirty(view);
3478 /* Update the title _after_ the redraw so that if the redraw picks up a
3479 * commit reference in view->ref it'll be available here. */
3480 update_view_title(view);
3481 return TRUE;
3482 }
3484 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3486 static struct line *
3487 add_line_data(struct view *view, void *data, enum line_type type)
3488 {
3489 struct line *line;
3491 if (!realloc_lines(&view->line, view->lines, 1))
3492 return NULL;
3494 line = &view->line[view->lines++];
3495 memset(line, 0, sizeof(*line));
3496 line->type = type;
3497 line->data = data;
3498 line->dirty = 1;
3500 return line;
3501 }
3503 static struct line *
3504 add_line_text(struct view *view, const char *text, enum line_type type)
3505 {
3506 char *data = text ? strdup(text) : NULL;
3508 return data ? add_line_data(view, data, type) : NULL;
3509 }
3511 static struct line *
3512 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3513 {
3514 char buf[SIZEOF_STR];
3515 va_list args;
3517 va_start(args, fmt);
3518 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3519 buf[0] = 0;
3520 va_end(args);
3522 return buf[0] ? add_line_text(view, buf, type) : NULL;
3523 }
3525 /*
3526 * View opening
3527 */
3529 enum open_flags {
3530 OPEN_DEFAULT = 0, /* Use default view switching. */
3531 OPEN_SPLIT = 1, /* Split current view. */
3532 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3533 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3534 OPEN_PREPARED = 32, /* Open already prepared command. */
3535 };
3537 static void
3538 open_view(struct view *prev, enum request request, enum open_flags flags)
3539 {
3540 bool split = !!(flags & OPEN_SPLIT);
3541 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3542 bool nomaximize = !!(flags & OPEN_REFRESH);
3543 struct view *view = VIEW(request);
3544 int nviews = displayed_views();
3545 struct view *base_view = display[0];
3547 if (view == prev && nviews == 1 && !reload) {
3548 report("Already in %s view", view->name);
3549 return;
3550 }
3552 if (view->git_dir && !opt_git_dir[0]) {
3553 report("The %s view is disabled in pager view", view->name);
3554 return;
3555 }
3557 if (split) {
3558 display[1] = view;
3559 current_view = 1;
3560 view->parent = prev;
3561 } else if (!nomaximize) {
3562 /* Maximize the current view. */
3563 memset(display, 0, sizeof(display));
3564 current_view = 0;
3565 display[current_view] = view;
3566 }
3568 /* No prev signals that this is the first loaded view. */
3569 if (prev && view != prev) {
3570 view->prev = prev;
3571 }
3573 /* Resize the view when switching between split- and full-screen,
3574 * or when switching between two different full-screen views. */
3575 if (nviews != displayed_views() ||
3576 (nviews == 1 && base_view != display[0]))
3577 resize_display();
3579 if (view->ops->open) {
3580 if (view->pipe)
3581 end_update(view, TRUE);
3582 if (!view->ops->open(view)) {
3583 report("Failed to load %s view", view->name);
3584 return;
3585 }
3586 restore_view_position(view);
3588 } else if ((reload || strcmp(view->vid, view->id)) &&
3589 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3590 report("Failed to load %s view", view->name);
3591 return;
3592 }
3594 if (split && prev->lineno - prev->offset >= prev->height) {
3595 /* Take the title line into account. */
3596 int lines = prev->lineno - prev->offset - prev->height + 1;
3598 /* Scroll the view that was split if the current line is
3599 * outside the new limited view. */
3600 do_scroll_view(prev, lines);
3601 }
3603 if (prev && view != prev && split && view_is_displayed(prev)) {
3604 /* "Blur" the previous view. */
3605 update_view_title(prev);
3606 }
3608 if (view->pipe && view->lines == 0) {
3609 /* Clear the old view and let the incremental updating refill
3610 * the screen. */
3611 werase(view->win);
3612 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3613 report("");
3614 } else if (view_is_displayed(view)) {
3615 redraw_view(view);
3616 report("");
3617 }
3618 }
3620 static void
3621 open_external_viewer(const char *argv[], const char *dir)
3622 {
3623 def_prog_mode(); /* save current tty modes */
3624 endwin(); /* restore original tty modes */
3625 io_run_fg(argv, dir);
3626 fprintf(stderr, "Press Enter to continue");
3627 getc(opt_tty);
3628 reset_prog_mode();
3629 redraw_display(TRUE);
3630 }
3632 static void
3633 open_mergetool(const char *file)
3634 {
3635 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3637 open_external_viewer(mergetool_argv, opt_cdup);
3638 }
3640 static void
3641 open_editor(const char *file)
3642 {
3643 const char *editor_argv[] = { "vi", file, NULL };
3644 const char *editor;
3646 editor = getenv("GIT_EDITOR");
3647 if (!editor && *opt_editor)
3648 editor = opt_editor;
3649 if (!editor)
3650 editor = getenv("VISUAL");
3651 if (!editor)
3652 editor = getenv("EDITOR");
3653 if (!editor)
3654 editor = "vi";
3656 editor_argv[0] = editor;
3657 open_external_viewer(editor_argv, opt_cdup);
3658 }
3660 static void
3661 open_run_request(enum request request)
3662 {
3663 struct run_request *req = get_run_request(request);
3664 const char **argv = NULL;
3666 if (!req) {
3667 report("Unknown run request");
3668 return;
3669 }
3671 if (format_argv(&argv, req->argv, TRUE))
3672 open_external_viewer(argv, NULL);
3673 if (argv)
3674 argv_free(argv);
3675 free(argv);
3676 }
3678 /*
3679 * User request switch noodle
3680 */
3682 static int
3683 view_driver(struct view *view, enum request request)
3684 {
3685 int i;
3687 if (request == REQ_NONE)
3688 return TRUE;
3690 if (request > REQ_NONE) {
3691 open_run_request(request);
3692 view_request(view, REQ_REFRESH);
3693 return TRUE;
3694 }
3696 request = view_request(view, request);
3697 if (request == REQ_NONE)
3698 return TRUE;
3700 switch (request) {
3701 case REQ_MOVE_UP:
3702 case REQ_MOVE_DOWN:
3703 case REQ_MOVE_PAGE_UP:
3704 case REQ_MOVE_PAGE_DOWN:
3705 case REQ_MOVE_FIRST_LINE:
3706 case REQ_MOVE_LAST_LINE:
3707 move_view(view, request);
3708 break;
3710 case REQ_SCROLL_FIRST_COL:
3711 case REQ_SCROLL_LEFT:
3712 case REQ_SCROLL_RIGHT:
3713 case REQ_SCROLL_LINE_DOWN:
3714 case REQ_SCROLL_LINE_UP:
3715 case REQ_SCROLL_PAGE_DOWN:
3716 case REQ_SCROLL_PAGE_UP:
3717 scroll_view(view, request);
3718 break;
3720 case REQ_VIEW_BLAME:
3721 if (!opt_file[0]) {
3722 report("No file chosen, press %s to open tree view",
3723 get_key(view->keymap, REQ_VIEW_TREE));
3724 break;
3725 }
3726 open_view(view, request, OPEN_DEFAULT);
3727 break;
3729 case REQ_VIEW_BLOB:
3730 if (!ref_blob[0]) {
3731 report("No file chosen, press %s to open tree view",
3732 get_key(view->keymap, REQ_VIEW_TREE));
3733 break;
3734 }
3735 open_view(view, request, OPEN_DEFAULT);
3736 break;
3738 case REQ_VIEW_PAGER:
3739 if (view == NULL) {
3740 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3741 die("Failed to open stdin");
3742 open_view(view, request, OPEN_PREPARED);
3743 break;
3744 }
3746 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3747 report("No pager content, press %s to run command from prompt",
3748 get_key(view->keymap, REQ_PROMPT));
3749 break;
3750 }
3751 open_view(view, request, OPEN_DEFAULT);
3752 break;
3754 case REQ_VIEW_STAGE:
3755 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3756 report("No stage content, press %s to open the status view and choose file",
3757 get_key(view->keymap, REQ_VIEW_STATUS));
3758 break;
3759 }
3760 open_view(view, request, OPEN_DEFAULT);
3761 break;
3763 case REQ_VIEW_STATUS:
3764 if (opt_is_inside_work_tree == FALSE) {
3765 report("The status view requires a working tree");
3766 break;
3767 }
3768 open_view(view, request, OPEN_DEFAULT);
3769 break;
3771 case REQ_VIEW_MAIN:
3772 case REQ_VIEW_DIFF:
3773 case REQ_VIEW_LOG:
3774 case REQ_VIEW_TREE:
3775 case REQ_VIEW_HELP:
3776 case REQ_VIEW_BRANCH:
3777 open_view(view, request, OPEN_DEFAULT);
3778 break;
3780 case REQ_NEXT:
3781 case REQ_PREVIOUS:
3782 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3784 if (view->parent) {
3785 int line;
3787 view = view->parent;
3788 line = view->lineno;
3789 move_view(view, request);
3790 if (view_is_displayed(view))
3791 update_view_title(view);
3792 if (line != view->lineno)
3793 view_request(view, REQ_ENTER);
3794 } else {
3795 move_view(view, request);
3796 }
3797 break;
3799 case REQ_VIEW_NEXT:
3800 {
3801 int nviews = displayed_views();
3802 int next_view = (current_view + 1) % nviews;
3804 if (next_view == current_view) {
3805 report("Only one view is displayed");
3806 break;
3807 }
3809 current_view = next_view;
3810 /* Blur out the title of the previous view. */
3811 update_view_title(view);
3812 report("");
3813 break;
3814 }
3815 case REQ_REFRESH:
3816 report("Refreshing is not yet supported for the %s view", view->name);
3817 break;
3819 case REQ_MAXIMIZE:
3820 if (displayed_views() == 2)
3821 maximize_view(view);
3822 break;
3824 case REQ_OPTIONS:
3825 open_option_menu();
3826 break;
3828 case REQ_TOGGLE_LINENO:
3829 toggle_view_option(&opt_line_number, "line numbers");
3830 break;
3832 case REQ_TOGGLE_DATE:
3833 toggle_date();
3834 break;
3836 case REQ_TOGGLE_AUTHOR:
3837 toggle_author();
3838 break;
3840 case REQ_TOGGLE_REV_GRAPH:
3841 toggle_view_option(&opt_rev_graph, "revision graph display");
3842 break;
3844 case REQ_TOGGLE_REFS:
3845 toggle_view_option(&opt_show_refs, "reference display");
3846 break;
3848 case REQ_TOGGLE_SORT_FIELD:
3849 case REQ_TOGGLE_SORT_ORDER:
3850 report("Sorting is not yet supported for the %s view", view->name);
3851 break;
3853 case REQ_SEARCH:
3854 case REQ_SEARCH_BACK:
3855 search_view(view, request);
3856 break;
3858 case REQ_FIND_NEXT:
3859 case REQ_FIND_PREV:
3860 find_next(view, request);
3861 break;
3863 case REQ_STOP_LOADING:
3864 foreach_view(view, i) {
3865 if (view->pipe)
3866 report("Stopped loading the %s view", view->name),
3867 end_update(view, TRUE);
3868 }
3869 break;
3871 case REQ_SHOW_VERSION:
3872 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3873 return TRUE;
3875 case REQ_SCREEN_REDRAW:
3876 redraw_display(TRUE);
3877 break;
3879 case REQ_EDIT:
3880 report("Nothing to edit");
3881 break;
3883 case REQ_ENTER:
3884 report("Nothing to enter");
3885 break;
3887 case REQ_VIEW_CLOSE:
3888 /* XXX: Mark closed views by letting view->prev point to the
3889 * view itself. Parents to closed view should never be
3890 * followed. */
3891 if (view->prev && view->prev != view) {
3892 maximize_view(view->prev);
3893 view->prev = view;
3894 break;
3895 }
3896 /* Fall-through */
3897 case REQ_QUIT:
3898 return FALSE;
3900 default:
3901 report("Unknown key, press %s for help",
3902 get_key(view->keymap, REQ_VIEW_HELP));
3903 return TRUE;
3904 }
3906 return TRUE;
3907 }
3910 /*
3911 * View backend utilities
3912 */
3914 enum sort_field {
3915 ORDERBY_NAME,
3916 ORDERBY_DATE,
3917 ORDERBY_AUTHOR,
3918 };
3920 struct sort_state {
3921 const enum sort_field *fields;
3922 size_t size, current;
3923 bool reverse;
3924 };
3926 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3927 #define get_sort_field(state) ((state).fields[(state).current])
3928 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3930 static void
3931 sort_view(struct view *view, enum request request, struct sort_state *state,
3932 int (*compare)(const void *, const void *))
3933 {
3934 switch (request) {
3935 case REQ_TOGGLE_SORT_FIELD:
3936 state->current = (state->current + 1) % state->size;
3937 break;
3939 case REQ_TOGGLE_SORT_ORDER:
3940 state->reverse = !state->reverse;
3941 break;
3942 default:
3943 die("Not a sort request");
3944 }
3946 qsort(view->line, view->lines, sizeof(*view->line), compare);
3947 redraw_view(view);
3948 }
3950 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3952 /* Small author cache to reduce memory consumption. It uses binary
3953 * search to lookup or find place to position new entries. No entries
3954 * are ever freed. */
3955 static const char *
3956 get_author(const char *name)
3957 {
3958 static const char **authors;
3959 static size_t authors_size;
3960 int from = 0, to = authors_size - 1;
3962 while (from <= to) {
3963 size_t pos = (to + from) / 2;
3964 int cmp = strcmp(name, authors[pos]);
3966 if (!cmp)
3967 return authors[pos];
3969 if (cmp < 0)
3970 to = pos - 1;
3971 else
3972 from = pos + 1;
3973 }
3975 if (!realloc_authors(&authors, authors_size, 1))
3976 return NULL;
3977 name = strdup(name);
3978 if (!name)
3979 return NULL;
3981 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3982 authors[from] = name;
3983 authors_size++;
3985 return name;
3986 }
3988 static void
3989 parse_timesec(struct time *time, const char *sec)
3990 {
3991 time->sec = (time_t) atol(sec);
3992 }
3994 static void
3995 parse_timezone(struct time *time, const char *zone)
3996 {
3997 long tz;
3999 tz = ('0' - zone[1]) * 60 * 60 * 10;
4000 tz += ('0' - zone[2]) * 60 * 60;
4001 tz += ('0' - zone[3]) * 60 * 10;
4002 tz += ('0' - zone[4]) * 60;
4004 if (zone[0] == '-')
4005 tz = -tz;
4007 time->tz = tz;
4008 time->sec -= tz;
4009 }
4011 /* Parse author lines where the name may be empty:
4012 * author <email@address.tld> 1138474660 +0100
4013 */
4014 static void
4015 parse_author_line(char *ident, const char **author, struct time *time)
4016 {
4017 char *nameend = strchr(ident, '<');
4018 char *emailend = strchr(ident, '>');
4020 if (nameend && emailend)
4021 *nameend = *emailend = 0;
4022 ident = chomp_string(ident);
4023 if (!*ident) {
4024 if (nameend)
4025 ident = chomp_string(nameend + 1);
4026 if (!*ident)
4027 ident = "Unknown";
4028 }
4030 *author = get_author(ident);
4032 /* Parse epoch and timezone */
4033 if (emailend && emailend[1] == ' ') {
4034 char *secs = emailend + 2;
4035 char *zone = strchr(secs, ' ');
4037 parse_timesec(time, secs);
4039 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4040 parse_timezone(time, zone + 1);
4041 }
4042 }
4044 /*
4045 * Pager backend
4046 */
4048 static bool
4049 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4050 {
4051 if (opt_line_number && draw_lineno(view, lineno))
4052 return TRUE;
4054 draw_text(view, line->type, line->data, TRUE);
4055 return TRUE;
4056 }
4058 static bool
4059 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4060 {
4061 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4062 char ref[SIZEOF_STR];
4064 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4065 return TRUE;
4067 /* This is the only fatal call, since it can "corrupt" the buffer. */
4068 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4069 return FALSE;
4071 return TRUE;
4072 }
4074 static void
4075 add_pager_refs(struct view *view, struct line *line)
4076 {
4077 char buf[SIZEOF_STR];
4078 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4079 struct ref_list *list;
4080 size_t bufpos = 0, i;
4081 const char *sep = "Refs: ";
4082 bool is_tag = FALSE;
4084 assert(line->type == LINE_COMMIT);
4086 list = get_ref_list(commit_id);
4087 if (!list) {
4088 if (view->type == VIEW_DIFF)
4089 goto try_add_describe_ref;
4090 return;
4091 }
4093 for (i = 0; i < list->size; i++) {
4094 struct ref *ref = list->refs[i];
4095 const char *fmt = ref->tag ? "%s[%s]" :
4096 ref->remote ? "%s<%s>" : "%s%s";
4098 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4099 return;
4100 sep = ", ";
4101 if (ref->tag)
4102 is_tag = TRUE;
4103 }
4105 if (!is_tag && view->type == VIEW_DIFF) {
4106 try_add_describe_ref:
4107 /* Add <tag>-g<commit_id> "fake" reference. */
4108 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4109 return;
4110 }
4112 if (bufpos == 0)
4113 return;
4115 add_line_text(view, buf, LINE_PP_REFS);
4116 }
4118 static bool
4119 pager_read(struct view *view, char *data)
4120 {
4121 struct line *line;
4123 if (!data)
4124 return TRUE;
4126 line = add_line_text(view, data, get_line_type(data));
4127 if (!line)
4128 return FALSE;
4130 if (line->type == LINE_COMMIT &&
4131 (view->type == VIEW_DIFF ||
4132 view->type == VIEW_LOG))
4133 add_pager_refs(view, line);
4135 return TRUE;
4136 }
4138 static enum request
4139 pager_request(struct view *view, enum request request, struct line *line)
4140 {
4141 int split = 0;
4143 if (request != REQ_ENTER)
4144 return request;
4146 if (line->type == LINE_COMMIT &&
4147 (view->type == VIEW_LOG ||
4148 view->type == VIEW_PAGER)) {
4149 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4150 split = 1;
4151 }
4153 /* Always scroll the view even if it was split. That way
4154 * you can use Enter to scroll through the log view and
4155 * split open each commit diff. */
4156 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4158 /* FIXME: A minor workaround. Scrolling the view will call report("")
4159 * but if we are scrolling a non-current view this won't properly
4160 * update the view title. */
4161 if (split)
4162 update_view_title(view);
4164 return REQ_NONE;
4165 }
4167 static bool
4168 pager_grep(struct view *view, struct line *line)
4169 {
4170 const char *text[] = { line->data, NULL };
4172 return grep_text(view, text);
4173 }
4175 static void
4176 pager_select(struct view *view, struct line *line)
4177 {
4178 if (line->type == LINE_COMMIT) {
4179 char *text = (char *)line->data + STRING_SIZE("commit ");
4181 if (view->type != VIEW_PAGER)
4182 string_copy_rev(view->ref, text);
4183 string_copy_rev(ref_commit, text);
4184 }
4185 }
4187 static struct view_ops pager_ops = {
4188 "line",
4189 NULL,
4190 NULL,
4191 pager_read,
4192 pager_draw,
4193 pager_request,
4194 pager_grep,
4195 pager_select,
4196 };
4198 static const char *log_argv[SIZEOF_ARG] = {
4199 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4200 };
4202 static enum request
4203 log_request(struct view *view, enum request request, struct line *line)
4204 {
4205 switch (request) {
4206 case REQ_REFRESH:
4207 load_refs();
4208 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4209 return REQ_NONE;
4210 default:
4211 return pager_request(view, request, line);
4212 }
4213 }
4215 static struct view_ops log_ops = {
4216 "line",
4217 log_argv,
4218 NULL,
4219 pager_read,
4220 pager_draw,
4221 log_request,
4222 pager_grep,
4223 pager_select,
4224 };
4226 static const char *diff_argv[SIZEOF_ARG] = {
4227 "git", "show", "--pretty=fuller", "--no-color", "--root",
4228 "--patch-with-stat", "--find-copies-harder", "-C",
4229 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4230 };
4232 static bool
4233 diff_read(struct view *view, char *data)
4234 {
4235 if (!data) {
4236 /* Fall back to retry if no diff will be shown. */
4237 if (view->lines == 0 && opt_file_args) {
4238 int pos = argv_size(view->argv)
4239 - argv_size(opt_file_args) - 1;
4241 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4242 for (; view->argv[pos]; pos++) {
4243 free((void *) view->argv[pos]);
4244 view->argv[pos] = NULL;
4245 }
4247 if (view->pipe)
4248 io_done(view->pipe);
4249 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4250 return FALSE;
4251 }
4252 }
4253 return TRUE;
4254 }
4256 return pager_read(view, data);
4257 }
4259 static struct view_ops diff_ops = {
4260 "line",
4261 diff_argv,
4262 NULL,
4263 diff_read,
4264 pager_draw,
4265 pager_request,
4266 pager_grep,
4267 pager_select,
4268 };
4270 /*
4271 * Help backend
4272 */
4274 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4276 static bool
4277 help_open_keymap_title(struct view *view, enum keymap keymap)
4278 {
4279 struct line *line;
4281 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4282 help_keymap_hidden[keymap] ? '+' : '-',
4283 enum_name(keymap_table[keymap]));
4284 if (line)
4285 line->other = keymap;
4287 return help_keymap_hidden[keymap];
4288 }
4290 static void
4291 help_open_keymap(struct view *view, enum keymap keymap)
4292 {
4293 const char *group = NULL;
4294 char buf[SIZEOF_STR];
4295 size_t bufpos;
4296 bool add_title = TRUE;
4297 int i;
4299 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4300 const char *key = NULL;
4302 if (req_info[i].request == REQ_NONE)
4303 continue;
4305 if (!req_info[i].request) {
4306 group = req_info[i].help;
4307 continue;
4308 }
4310 key = get_keys(keymap, req_info[i].request, TRUE);
4311 if (!key || !*key)
4312 continue;
4314 if (add_title && help_open_keymap_title(view, keymap))
4315 return;
4316 add_title = FALSE;
4318 if (group) {
4319 add_line_text(view, group, LINE_HELP_GROUP);
4320 group = NULL;
4321 }
4323 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4324 enum_name(req_info[i]), req_info[i].help);
4325 }
4327 group = "External commands:";
4329 for (i = 0; i < run_requests; i++) {
4330 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4331 const char *key;
4332 int argc;
4334 if (!req || req->keymap != keymap)
4335 continue;
4337 key = get_key_name(req->key);
4338 if (!*key)
4339 key = "(no key defined)";
4341 if (add_title && help_open_keymap_title(view, keymap))
4342 return;
4343 if (group) {
4344 add_line_text(view, group, LINE_HELP_GROUP);
4345 group = NULL;
4346 }
4348 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4349 if (!string_format_from(buf, &bufpos, "%s%s",
4350 argc ? " " : "", req->argv[argc]))
4351 return;
4353 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4354 }
4355 }
4357 static bool
4358 help_open(struct view *view)
4359 {
4360 enum keymap keymap;
4362 reset_view(view);
4363 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4364 add_line_text(view, "", LINE_DEFAULT);
4366 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4367 help_open_keymap(view, keymap);
4369 return TRUE;
4370 }
4372 static enum request
4373 help_request(struct view *view, enum request request, struct line *line)
4374 {
4375 switch (request) {
4376 case REQ_ENTER:
4377 if (line->type == LINE_HELP_KEYMAP) {
4378 help_keymap_hidden[line->other] =
4379 !help_keymap_hidden[line->other];
4380 view->p_restore = TRUE;
4381 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4382 }
4384 return REQ_NONE;
4385 default:
4386 return pager_request(view, request, line);
4387 }
4388 }
4390 static struct view_ops help_ops = {
4391 "line",
4392 NULL,
4393 help_open,
4394 NULL,
4395 pager_draw,
4396 help_request,
4397 pager_grep,
4398 pager_select,
4399 };
4402 /*
4403 * Tree backend
4404 */
4406 struct tree_stack_entry {
4407 struct tree_stack_entry *prev; /* Entry below this in the stack */
4408 unsigned long lineno; /* Line number to restore */
4409 char *name; /* Position of name in opt_path */
4410 };
4412 /* The top of the path stack. */
4413 static struct tree_stack_entry *tree_stack = NULL;
4414 unsigned long tree_lineno = 0;
4416 static void
4417 pop_tree_stack_entry(void)
4418 {
4419 struct tree_stack_entry *entry = tree_stack;
4421 tree_lineno = entry->lineno;
4422 entry->name[0] = 0;
4423 tree_stack = entry->prev;
4424 free(entry);
4425 }
4427 static void
4428 push_tree_stack_entry(const char *name, unsigned long lineno)
4429 {
4430 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4431 size_t pathlen = strlen(opt_path);
4433 if (!entry)
4434 return;
4436 entry->prev = tree_stack;
4437 entry->name = opt_path + pathlen;
4438 tree_stack = entry;
4440 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4441 pop_tree_stack_entry();
4442 return;
4443 }
4445 /* Move the current line to the first tree entry. */
4446 tree_lineno = 1;
4447 entry->lineno = lineno;
4448 }
4450 /* Parse output from git-ls-tree(1):
4451 *
4452 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4453 */
4455 #define SIZEOF_TREE_ATTR \
4456 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4458 #define SIZEOF_TREE_MODE \
4459 STRING_SIZE("100644 ")
4461 #define TREE_ID_OFFSET \
4462 STRING_SIZE("100644 blob ")
4464 struct tree_entry {
4465 char id[SIZEOF_REV];
4466 mode_t mode;
4467 struct time time; /* Date from the author ident. */
4468 const char *author; /* Author of the commit. */
4469 char name[1];
4470 };
4472 static const char *
4473 tree_path(const struct line *line)
4474 {
4475 return ((struct tree_entry *) line->data)->name;
4476 }
4478 static int
4479 tree_compare_entry(const struct line *line1, const struct line *line2)
4480 {
4481 if (line1->type != line2->type)
4482 return line1->type == LINE_TREE_DIR ? -1 : 1;
4483 return strcmp(tree_path(line1), tree_path(line2));
4484 }
4486 static const enum sort_field tree_sort_fields[] = {
4487 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4488 };
4489 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4491 static int
4492 tree_compare(const void *l1, const void *l2)
4493 {
4494 const struct line *line1 = (const struct line *) l1;
4495 const struct line *line2 = (const struct line *) l2;
4496 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4497 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4499 if (line1->type == LINE_TREE_HEAD)
4500 return -1;
4501 if (line2->type == LINE_TREE_HEAD)
4502 return 1;
4504 switch (get_sort_field(tree_sort_state)) {
4505 case ORDERBY_DATE:
4506 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4508 case ORDERBY_AUTHOR:
4509 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4511 case ORDERBY_NAME:
4512 default:
4513 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4514 }
4515 }
4518 static struct line *
4519 tree_entry(struct view *view, enum line_type type, const char *path,
4520 const char *mode, const char *id)
4521 {
4522 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4523 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4525 if (!entry || !line) {
4526 free(entry);
4527 return NULL;
4528 }
4530 strncpy(entry->name, path, strlen(path));
4531 if (mode)
4532 entry->mode = strtoul(mode, NULL, 8);
4533 if (id)
4534 string_copy_rev(entry->id, id);
4536 return line;
4537 }
4539 static bool
4540 tree_read_date(struct view *view, char *text, bool *read_date)
4541 {
4542 static const char *author_name;
4543 static struct time author_time;
4545 if (!text && *read_date) {
4546 *read_date = FALSE;
4547 return TRUE;
4549 } else if (!text) {
4550 char *path = *opt_path ? opt_path : ".";
4551 /* Find next entry to process */
4552 const char *log_file[] = {
4553 "git", "log", "--no-color", "--pretty=raw",
4554 "--cc", "--raw", view->id, "--", path, NULL
4555 };
4557 if (!view->lines) {
4558 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4559 report("Tree is empty");
4560 return TRUE;
4561 }
4563 if (!start_update(view, log_file, opt_cdup)) {
4564 report("Failed to load tree data");
4565 return TRUE;
4566 }
4568 *read_date = TRUE;
4569 return FALSE;
4571 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4572 parse_author_line(text + STRING_SIZE("author "),
4573 &author_name, &author_time);
4575 } else if (*text == ':') {
4576 char *pos;
4577 size_t annotated = 1;
4578 size_t i;
4580 pos = strchr(text, '\t');
4581 if (!pos)
4582 return TRUE;
4583 text = pos + 1;
4584 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4585 text += strlen(opt_path);
4586 pos = strchr(text, '/');
4587 if (pos)
4588 *pos = 0;
4590 for (i = 1; i < view->lines; i++) {
4591 struct line *line = &view->line[i];
4592 struct tree_entry *entry = line->data;
4594 annotated += !!entry->author;
4595 if (entry->author || strcmp(entry->name, text))
4596 continue;
4598 entry->author = author_name;
4599 entry->time = author_time;
4600 line->dirty = 1;
4601 break;
4602 }
4604 if (annotated == view->lines)
4605 io_kill(view->pipe);
4606 }
4607 return TRUE;
4608 }
4610 static bool
4611 tree_read(struct view *view, char *text)
4612 {
4613 static bool read_date = FALSE;
4614 struct tree_entry *data;
4615 struct line *entry, *line;
4616 enum line_type type;
4617 size_t textlen = text ? strlen(text) : 0;
4618 char *path = text + SIZEOF_TREE_ATTR;
4620 if (read_date || !text)
4621 return tree_read_date(view, text, &read_date);
4623 if (textlen <= SIZEOF_TREE_ATTR)
4624 return FALSE;
4625 if (view->lines == 0 &&
4626 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4627 return FALSE;
4629 /* Strip the path part ... */
4630 if (*opt_path) {
4631 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4632 size_t striplen = strlen(opt_path);
4634 if (pathlen > striplen)
4635 memmove(path, path + striplen,
4636 pathlen - striplen + 1);
4638 /* Insert "link" to parent directory. */
4639 if (view->lines == 1 &&
4640 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4641 return FALSE;
4642 }
4644 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4645 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4646 if (!entry)
4647 return FALSE;
4648 data = entry->data;
4650 /* Skip "Directory ..." and ".." line. */
4651 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4652 if (tree_compare_entry(line, entry) <= 0)
4653 continue;
4655 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4657 line->data = data;
4658 line->type = type;
4659 for (; line <= entry; line++)
4660 line->dirty = line->cleareol = 1;
4661 return TRUE;
4662 }
4664 if (tree_lineno > view->lineno) {
4665 view->lineno = tree_lineno;
4666 tree_lineno = 0;
4667 }
4669 return TRUE;
4670 }
4672 static bool
4673 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4674 {
4675 struct tree_entry *entry = line->data;
4677 if (line->type == LINE_TREE_HEAD) {
4678 if (draw_text(view, line->type, "Directory path /", TRUE))
4679 return TRUE;
4680 } else {
4681 if (draw_mode(view, entry->mode))
4682 return TRUE;
4684 if (opt_author && draw_author(view, entry->author))
4685 return TRUE;
4687 if (opt_date && draw_date(view, &entry->time))
4688 return TRUE;
4689 }
4690 if (draw_text(view, line->type, entry->name, TRUE))
4691 return TRUE;
4692 return TRUE;
4693 }
4695 static void
4696 open_blob_editor(const char *id)
4697 {
4698 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4699 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4700 int fd = mkstemp(file);
4702 if (fd == -1)
4703 report("Failed to create temporary file");
4704 else if (!io_run_append(blob_argv, fd))
4705 report("Failed to save blob data to file");
4706 else
4707 open_editor(file);
4708 if (fd != -1)
4709 unlink(file);
4710 }
4712 static enum request
4713 tree_request(struct view *view, enum request request, struct line *line)
4714 {
4715 enum open_flags flags;
4716 struct tree_entry *entry = line->data;
4718 switch (request) {
4719 case REQ_VIEW_BLAME:
4720 if (line->type != LINE_TREE_FILE) {
4721 report("Blame only supported for files");
4722 return REQ_NONE;
4723 }
4725 string_copy(opt_ref, view->vid);
4726 return request;
4728 case REQ_EDIT:
4729 if (line->type != LINE_TREE_FILE) {
4730 report("Edit only supported for files");
4731 } else if (!is_head_commit(view->vid)) {
4732 open_blob_editor(entry->id);
4733 } else {
4734 open_editor(opt_file);
4735 }
4736 return REQ_NONE;
4738 case REQ_TOGGLE_SORT_FIELD:
4739 case REQ_TOGGLE_SORT_ORDER:
4740 sort_view(view, request, &tree_sort_state, tree_compare);
4741 return REQ_NONE;
4743 case REQ_PARENT:
4744 if (!*opt_path) {
4745 /* quit view if at top of tree */
4746 return REQ_VIEW_CLOSE;
4747 }
4748 /* fake 'cd ..' */
4749 line = &view->line[1];
4750 break;
4752 case REQ_ENTER:
4753 break;
4755 default:
4756 return request;
4757 }
4759 /* Cleanup the stack if the tree view is at a different tree. */
4760 while (!*opt_path && tree_stack)
4761 pop_tree_stack_entry();
4763 switch (line->type) {
4764 case LINE_TREE_DIR:
4765 /* Depending on whether it is a subdirectory or parent link
4766 * mangle the path buffer. */
4767 if (line == &view->line[1] && *opt_path) {
4768 pop_tree_stack_entry();
4770 } else {
4771 const char *basename = tree_path(line);
4773 push_tree_stack_entry(basename, view->lineno);
4774 }
4776 /* Trees and subtrees share the same ID, so they are not not
4777 * unique like blobs. */
4778 flags = OPEN_RELOAD;
4779 request = REQ_VIEW_TREE;
4780 break;
4782 case LINE_TREE_FILE:
4783 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4784 request = REQ_VIEW_BLOB;
4785 break;
4787 default:
4788 return REQ_NONE;
4789 }
4791 open_view(view, request, flags);
4792 if (request == REQ_VIEW_TREE)
4793 view->lineno = tree_lineno;
4795 return REQ_NONE;
4796 }
4798 static bool
4799 tree_grep(struct view *view, struct line *line)
4800 {
4801 struct tree_entry *entry = line->data;
4802 const char *text[] = {
4803 entry->name,
4804 opt_author ? entry->author : "",
4805 mkdate(&entry->time, opt_date),
4806 NULL
4807 };
4809 return grep_text(view, text);
4810 }
4812 static void
4813 tree_select(struct view *view, struct line *line)
4814 {
4815 struct tree_entry *entry = line->data;
4817 if (line->type == LINE_TREE_FILE) {
4818 string_copy_rev(ref_blob, entry->id);
4819 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4821 } else if (line->type != LINE_TREE_DIR) {
4822 return;
4823 }
4825 string_copy_rev(view->ref, entry->id);
4826 }
4828 static bool
4829 tree_prepare(struct view *view)
4830 {
4831 if (view->lines == 0 && opt_prefix[0]) {
4832 char *pos = opt_prefix;
4834 while (pos && *pos) {
4835 char *end = strchr(pos, '/');
4837 if (end)
4838 *end = 0;
4839 push_tree_stack_entry(pos, 0);
4840 pos = end;
4841 if (end) {
4842 *end = '/';
4843 pos++;
4844 }
4845 }
4847 } else if (strcmp(view->vid, view->id)) {
4848 opt_path[0] = 0;
4849 }
4851 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4852 }
4854 static const char *tree_argv[SIZEOF_ARG] = {
4855 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4856 };
4858 static struct view_ops tree_ops = {
4859 "file",
4860 tree_argv,
4861 NULL,
4862 tree_read,
4863 tree_draw,
4864 tree_request,
4865 tree_grep,
4866 tree_select,
4867 tree_prepare,
4868 };
4870 static bool
4871 blob_read(struct view *view, char *line)
4872 {
4873 if (!line)
4874 return TRUE;
4875 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4876 }
4878 static enum request
4879 blob_request(struct view *view, enum request request, struct line *line)
4880 {
4881 switch (request) {
4882 case REQ_EDIT:
4883 open_blob_editor(view->vid);
4884 return REQ_NONE;
4885 default:
4886 return pager_request(view, request, line);
4887 }
4888 }
4890 static const char *blob_argv[SIZEOF_ARG] = {
4891 "git", "cat-file", "blob", "%(blob)", NULL
4892 };
4894 static struct view_ops blob_ops = {
4895 "line",
4896 blob_argv,
4897 NULL,
4898 blob_read,
4899 pager_draw,
4900 blob_request,
4901 pager_grep,
4902 pager_select,
4903 };
4905 /*
4906 * Blame backend
4907 *
4908 * Loading the blame view is a two phase job:
4909 *
4910 * 1. File content is read either using opt_file from the
4911 * filesystem or using git-cat-file.
4912 * 2. Then blame information is incrementally added by
4913 * reading output from git-blame.
4914 */
4916 struct blame_commit {
4917 char id[SIZEOF_REV]; /* SHA1 ID. */
4918 char title[128]; /* First line of the commit message. */
4919 const char *author; /* Author of the commit. */
4920 struct time time; /* Date from the author ident. */
4921 char filename[128]; /* Name of file. */
4922 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4923 char parent_filename[128]; /* Parent/previous name of file. */
4924 };
4926 struct blame {
4927 struct blame_commit *commit;
4928 unsigned long lineno;
4929 char text[1];
4930 };
4932 static bool
4933 blame_open(struct view *view)
4934 {
4935 char path[SIZEOF_STR];
4936 size_t i;
4938 if (!view->prev && *opt_prefix) {
4939 string_copy(path, opt_file);
4940 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4941 return FALSE;
4942 }
4944 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4945 const char *blame_cat_file_argv[] = {
4946 "git", "cat-file", "blob", path, NULL
4947 };
4949 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4950 !start_update(view, blame_cat_file_argv, opt_cdup))
4951 return FALSE;
4952 }
4954 /* First pass: remove multiple references to the same commit. */
4955 for (i = 0; i < view->lines; i++) {
4956 struct blame *blame = view->line[i].data;
4958 if (blame->commit && blame->commit->id[0])
4959 blame->commit->id[0] = 0;
4960 else
4961 blame->commit = NULL;
4962 }
4964 /* Second pass: free existing references. */
4965 for (i = 0; i < view->lines; i++) {
4966 struct blame *blame = view->line[i].data;
4968 if (blame->commit)
4969 free(blame->commit);
4970 }
4972 setup_update(view, opt_file);
4973 string_format(view->ref, "%s ...", opt_file);
4975 return TRUE;
4976 }
4978 static struct blame_commit *
4979 get_blame_commit(struct view *view, const char *id)
4980 {
4981 size_t i;
4983 for (i = 0; i < view->lines; i++) {
4984 struct blame *blame = view->line[i].data;
4986 if (!blame->commit)
4987 continue;
4989 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4990 return blame->commit;
4991 }
4993 {
4994 struct blame_commit *commit = calloc(1, sizeof(*commit));
4996 if (commit)
4997 string_ncopy(commit->id, id, SIZEOF_REV);
4998 return commit;
4999 }
5000 }
5002 static bool
5003 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5004 {
5005 const char *pos = *posref;
5007 *posref = NULL;
5008 pos = strchr(pos + 1, ' ');
5009 if (!pos || !isdigit(pos[1]))
5010 return FALSE;
5011 *number = atoi(pos + 1);
5012 if (*number < min || *number > max)
5013 return FALSE;
5015 *posref = pos;
5016 return TRUE;
5017 }
5019 static struct blame_commit *
5020 parse_blame_commit(struct view *view, const char *text, int *blamed)
5021 {
5022 struct blame_commit *commit;
5023 struct blame *blame;
5024 const char *pos = text + SIZEOF_REV - 2;
5025 size_t orig_lineno = 0;
5026 size_t lineno;
5027 size_t group;
5029 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5030 return NULL;
5032 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5033 !parse_number(&pos, &lineno, 1, view->lines) ||
5034 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5035 return NULL;
5037 commit = get_blame_commit(view, text);
5038 if (!commit)
5039 return NULL;
5041 *blamed += group;
5042 while (group--) {
5043 struct line *line = &view->line[lineno + group - 1];
5045 blame = line->data;
5046 blame->commit = commit;
5047 blame->lineno = orig_lineno + group - 1;
5048 line->dirty = 1;
5049 }
5051 return commit;
5052 }
5054 static bool
5055 blame_read_file(struct view *view, const char *line, bool *read_file)
5056 {
5057 if (!line) {
5058 const char *blame_argv[] = {
5059 "git", "blame", "--incremental",
5060 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5061 };
5063 if (view->lines == 0 && !view->prev)
5064 die("No blame exist for %s", view->vid);
5066 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5067 report("Failed to load blame data");
5068 return TRUE;
5069 }
5071 *read_file = FALSE;
5072 return FALSE;
5074 } else {
5075 size_t linelen = strlen(line);
5076 struct blame *blame = malloc(sizeof(*blame) + linelen);
5078 if (!blame)
5079 return FALSE;
5081 blame->commit = NULL;
5082 strncpy(blame->text, line, linelen);
5083 blame->text[linelen] = 0;
5084 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5085 }
5086 }
5088 static bool
5089 match_blame_header(const char *name, char **line)
5090 {
5091 size_t namelen = strlen(name);
5092 bool matched = !strncmp(name, *line, namelen);
5094 if (matched)
5095 *line += namelen;
5097 return matched;
5098 }
5100 static bool
5101 blame_read(struct view *view, char *line)
5102 {
5103 static struct blame_commit *commit = NULL;
5104 static int blamed = 0;
5105 static bool read_file = TRUE;
5107 if (read_file)
5108 return blame_read_file(view, line, &read_file);
5110 if (!line) {
5111 /* Reset all! */
5112 commit = NULL;
5113 blamed = 0;
5114 read_file = TRUE;
5115 string_format(view->ref, "%s", view->vid);
5116 if (view_is_displayed(view)) {
5117 update_view_title(view);
5118 redraw_view_from(view, 0);
5119 }
5120 return TRUE;
5121 }
5123 if (!commit) {
5124 commit = parse_blame_commit(view, line, &blamed);
5125 string_format(view->ref, "%s %2d%%", view->vid,
5126 view->lines ? blamed * 100 / view->lines : 0);
5128 } else if (match_blame_header("author ", &line)) {
5129 commit->author = get_author(line);
5131 } else if (match_blame_header("author-time ", &line)) {
5132 parse_timesec(&commit->time, line);
5134 } else if (match_blame_header("author-tz ", &line)) {
5135 parse_timezone(&commit->time, line);
5137 } else if (match_blame_header("summary ", &line)) {
5138 string_ncopy(commit->title, line, strlen(line));
5140 } else if (match_blame_header("previous ", &line)) {
5141 if (strlen(line) <= SIZEOF_REV)
5142 return FALSE;
5143 string_copy_rev(commit->parent_id, line);
5144 line += SIZEOF_REV;
5145 string_ncopy(commit->parent_filename, line, strlen(line));
5147 } else if (match_blame_header("filename ", &line)) {
5148 string_ncopy(commit->filename, line, strlen(line));
5149 commit = NULL;
5150 }
5152 return TRUE;
5153 }
5155 static bool
5156 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5157 {
5158 struct blame *blame = line->data;
5159 struct time *time = NULL;
5160 const char *id = NULL, *author = NULL;
5162 if (blame->commit && *blame->commit->filename) {
5163 id = blame->commit->id;
5164 author = blame->commit->author;
5165 time = &blame->commit->time;
5166 }
5168 if (opt_date && draw_date(view, time))
5169 return TRUE;
5171 if (opt_author && draw_author(view, author))
5172 return TRUE;
5174 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5175 return TRUE;
5177 if (draw_lineno(view, lineno))
5178 return TRUE;
5180 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5181 return TRUE;
5182 }
5184 static bool
5185 check_blame_commit(struct blame *blame, bool check_null_id)
5186 {
5187 if (!blame->commit)
5188 report("Commit data not loaded yet");
5189 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5190 report("No commit exist for the selected line");
5191 else
5192 return TRUE;
5193 return FALSE;
5194 }
5196 static void
5197 setup_blame_parent_line(struct view *view, struct blame *blame)
5198 {
5199 char from[SIZEOF_REF + SIZEOF_STR];
5200 char to[SIZEOF_REF + SIZEOF_STR];
5201 const char *diff_tree_argv[] = {
5202 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5203 "-U0", from, to, "--", NULL
5204 };
5205 struct io io;
5206 int parent_lineno = -1;
5207 int blamed_lineno = -1;
5208 char *line;
5210 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5211 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5212 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5213 return;
5215 while ((line = io_get(&io, '\n', TRUE))) {
5216 if (*line == '@') {
5217 char *pos = strchr(line, '+');
5219 parent_lineno = atoi(line + 4);
5220 if (pos)
5221 blamed_lineno = atoi(pos + 1);
5223 } else if (*line == '+' && parent_lineno != -1) {
5224 if (blame->lineno == blamed_lineno - 1 &&
5225 !strcmp(blame->text, line + 1)) {
5226 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5227 break;
5228 }
5229 blamed_lineno++;
5230 }
5231 }
5233 io_done(&io);
5234 }
5236 static enum request
5237 blame_request(struct view *view, enum request request, struct line *line)
5238 {
5239 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5240 struct blame *blame = line->data;
5242 switch (request) {
5243 case REQ_VIEW_BLAME:
5244 if (check_blame_commit(blame, TRUE)) {
5245 string_copy(opt_ref, blame->commit->id);
5246 string_copy(opt_file, blame->commit->filename);
5247 if (blame->lineno)
5248 view->lineno = blame->lineno;
5249 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5250 }
5251 break;
5253 case REQ_PARENT:
5254 if (!check_blame_commit(blame, TRUE))
5255 break;
5256 if (!*blame->commit->parent_id) {
5257 report("The selected commit has no parents");
5258 } else {
5259 string_copy_rev(opt_ref, blame->commit->parent_id);
5260 string_copy(opt_file, blame->commit->parent_filename);
5261 setup_blame_parent_line(view, blame);
5262 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5263 }
5264 break;
5266 case REQ_ENTER:
5267 if (!check_blame_commit(blame, FALSE))
5268 break;
5270 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5271 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5272 break;
5274 if (!strcmp(blame->commit->id, NULL_ID)) {
5275 struct view *diff = VIEW(REQ_VIEW_DIFF);
5276 const char *diff_index_argv[] = {
5277 "git", "diff-index", "--root", "--patch-with-stat",
5278 "-C", "-M", "HEAD", "--", view->vid, NULL
5279 };
5281 if (!*blame->commit->parent_id) {
5282 diff_index_argv[1] = "diff";
5283 diff_index_argv[2] = "--no-color";
5284 diff_index_argv[6] = "--";
5285 diff_index_argv[7] = "/dev/null";
5286 }
5288 if (!prepare_update(diff, diff_index_argv, NULL)) {
5289 report("Failed to allocate diff command");
5290 break;
5291 }
5292 flags |= OPEN_PREPARED;
5293 }
5295 open_view(view, REQ_VIEW_DIFF, flags);
5296 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5297 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5298 break;
5300 default:
5301 return request;
5302 }
5304 return REQ_NONE;
5305 }
5307 static bool
5308 blame_grep(struct view *view, struct line *line)
5309 {
5310 struct blame *blame = line->data;
5311 struct blame_commit *commit = blame->commit;
5312 const char *text[] = {
5313 blame->text,
5314 commit ? commit->title : "",
5315 commit ? commit->id : "",
5316 commit && opt_author ? commit->author : "",
5317 commit ? mkdate(&commit->time, opt_date) : "",
5318 NULL
5319 };
5321 return grep_text(view, text);
5322 }
5324 static void
5325 blame_select(struct view *view, struct line *line)
5326 {
5327 struct blame *blame = line->data;
5328 struct blame_commit *commit = blame->commit;
5330 if (!commit)
5331 return;
5333 if (!strcmp(commit->id, NULL_ID))
5334 string_ncopy(ref_commit, "HEAD", 4);
5335 else
5336 string_copy_rev(ref_commit, commit->id);
5337 }
5339 static struct view_ops blame_ops = {
5340 "line",
5341 NULL,
5342 blame_open,
5343 blame_read,
5344 blame_draw,
5345 blame_request,
5346 blame_grep,
5347 blame_select,
5348 };
5350 /*
5351 * Branch backend
5352 */
5354 struct branch {
5355 const char *author; /* Author of the last commit. */
5356 struct time time; /* Date of the last activity. */
5357 const struct ref *ref; /* Name and commit ID information. */
5358 };
5360 static const struct ref branch_all;
5362 static const enum sort_field branch_sort_fields[] = {
5363 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5364 };
5365 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5367 static int
5368 branch_compare(const void *l1, const void *l2)
5369 {
5370 const struct branch *branch1 = ((const struct line *) l1)->data;
5371 const struct branch *branch2 = ((const struct line *) l2)->data;
5373 switch (get_sort_field(branch_sort_state)) {
5374 case ORDERBY_DATE:
5375 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5377 case ORDERBY_AUTHOR:
5378 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5380 case ORDERBY_NAME:
5381 default:
5382 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5383 }
5384 }
5386 static bool
5387 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5388 {
5389 struct branch *branch = line->data;
5390 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5392 if (opt_date && draw_date(view, &branch->time))
5393 return TRUE;
5395 if (opt_author && draw_author(view, branch->author))
5396 return TRUE;
5398 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5399 return TRUE;
5400 }
5402 static enum request
5403 branch_request(struct view *view, enum request request, struct line *line)
5404 {
5405 struct branch *branch = line->data;
5407 switch (request) {
5408 case REQ_REFRESH:
5409 load_refs();
5410 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5411 return REQ_NONE;
5413 case REQ_TOGGLE_SORT_FIELD:
5414 case REQ_TOGGLE_SORT_ORDER:
5415 sort_view(view, request, &branch_sort_state, branch_compare);
5416 return REQ_NONE;
5418 case REQ_ENTER:
5419 {
5420 const struct ref *ref = branch->ref;
5421 const char *all_branches_argv[] = {
5422 "git", "log", "--no-color", "--pretty=raw", "--parents",
5423 "--topo-order",
5424 ref == &branch_all ? "--all" : ref->name, NULL
5425 };
5426 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5428 if (!prepare_update(main_view, all_branches_argv, NULL))
5429 report("Failed to load view of all branches");
5430 else
5431 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5432 return REQ_NONE;
5433 }
5434 default:
5435 return request;
5436 }
5437 }
5439 static bool
5440 branch_read(struct view *view, char *line)
5441 {
5442 static char id[SIZEOF_REV];
5443 struct branch *reference;
5444 size_t i;
5446 if (!line)
5447 return TRUE;
5449 switch (get_line_type(line)) {
5450 case LINE_COMMIT:
5451 string_copy_rev(id, line + STRING_SIZE("commit "));
5452 return TRUE;
5454 case LINE_AUTHOR:
5455 for (i = 0, reference = NULL; i < view->lines; i++) {
5456 struct branch *branch = view->line[i].data;
5458 if (strcmp(branch->ref->id, id))
5459 continue;
5461 view->line[i].dirty = TRUE;
5462 if (reference) {
5463 branch->author = reference->author;
5464 branch->time = reference->time;
5465 continue;
5466 }
5468 parse_author_line(line + STRING_SIZE("author "),
5469 &branch->author, &branch->time);
5470 reference = branch;
5471 }
5472 return TRUE;
5474 default:
5475 return TRUE;
5476 }
5478 }
5480 static bool
5481 branch_open_visitor(void *data, const struct ref *ref)
5482 {
5483 struct view *view = data;
5484 struct branch *branch;
5486 if (ref->tag || ref->ltag || ref->remote)
5487 return TRUE;
5489 branch = calloc(1, sizeof(*branch));
5490 if (!branch)
5491 return FALSE;
5493 branch->ref = ref;
5494 return !!add_line_data(view, branch, LINE_DEFAULT);
5495 }
5497 static bool
5498 branch_open(struct view *view)
5499 {
5500 const char *branch_log[] = {
5501 "git", "log", "--no-color", "--pretty=raw",
5502 "--simplify-by-decoration", "--all", NULL
5503 };
5505 if (!start_update(view, branch_log, NULL)) {
5506 report("Failed to load branch data");
5507 return TRUE;
5508 }
5510 setup_update(view, view->id);
5511 branch_open_visitor(view, &branch_all);
5512 foreach_ref(branch_open_visitor, view);
5513 view->p_restore = TRUE;
5515 return TRUE;
5516 }
5518 static bool
5519 branch_grep(struct view *view, struct line *line)
5520 {
5521 struct branch *branch = line->data;
5522 const char *text[] = {
5523 branch->ref->name,
5524 branch->author,
5525 NULL
5526 };
5528 return grep_text(view, text);
5529 }
5531 static void
5532 branch_select(struct view *view, struct line *line)
5533 {
5534 struct branch *branch = line->data;
5536 string_copy_rev(view->ref, branch->ref->id);
5537 string_copy_rev(ref_commit, branch->ref->id);
5538 string_copy_rev(ref_head, branch->ref->id);
5539 string_copy_rev(ref_branch, branch->ref->name);
5540 }
5542 static struct view_ops branch_ops = {
5543 "branch",
5544 NULL,
5545 branch_open,
5546 branch_read,
5547 branch_draw,
5548 branch_request,
5549 branch_grep,
5550 branch_select,
5551 };
5553 /*
5554 * Status backend
5555 */
5557 struct status {
5558 char status;
5559 struct {
5560 mode_t mode;
5561 char rev[SIZEOF_REV];
5562 char name[SIZEOF_STR];
5563 } old;
5564 struct {
5565 mode_t mode;
5566 char rev[SIZEOF_REV];
5567 char name[SIZEOF_STR];
5568 } new;
5569 };
5571 static char status_onbranch[SIZEOF_STR];
5572 static struct status stage_status;
5573 static enum line_type stage_line_type;
5574 static size_t stage_chunks;
5575 static int *stage_chunk;
5577 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5579 /* This should work even for the "On branch" line. */
5580 static inline bool
5581 status_has_none(struct view *view, struct line *line)
5582 {
5583 return line < view->line + view->lines && !line[1].data;
5584 }
5586 /* Get fields from the diff line:
5587 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5588 */
5589 static inline bool
5590 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5591 {
5592 const char *old_mode = buf + 1;
5593 const char *new_mode = buf + 8;
5594 const char *old_rev = buf + 15;
5595 const char *new_rev = buf + 56;
5596 const char *status = buf + 97;
5598 if (bufsize < 98 ||
5599 old_mode[-1] != ':' ||
5600 new_mode[-1] != ' ' ||
5601 old_rev[-1] != ' ' ||
5602 new_rev[-1] != ' ' ||
5603 status[-1] != ' ')
5604 return FALSE;
5606 file->status = *status;
5608 string_copy_rev(file->old.rev, old_rev);
5609 string_copy_rev(file->new.rev, new_rev);
5611 file->old.mode = strtoul(old_mode, NULL, 8);
5612 file->new.mode = strtoul(new_mode, NULL, 8);
5614 file->old.name[0] = file->new.name[0] = 0;
5616 return TRUE;
5617 }
5619 static bool
5620 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5621 {
5622 struct status *unmerged = NULL;
5623 char *buf;
5624 struct io io;
5626 if (!io_run(&io, IO_RD, opt_cdup, argv))
5627 return FALSE;
5629 add_line_data(view, NULL, type);
5631 while ((buf = io_get(&io, 0, TRUE))) {
5632 struct status *file = unmerged;
5634 if (!file) {
5635 file = calloc(1, sizeof(*file));
5636 if (!file || !add_line_data(view, file, type))
5637 goto error_out;
5638 }
5640 /* Parse diff info part. */
5641 if (status) {
5642 file->status = status;
5643 if (status == 'A')
5644 string_copy(file->old.rev, NULL_ID);
5646 } else if (!file->status || file == unmerged) {
5647 if (!status_get_diff(file, buf, strlen(buf)))
5648 goto error_out;
5650 buf = io_get(&io, 0, TRUE);
5651 if (!buf)
5652 break;
5654 /* Collapse all modified entries that follow an
5655 * associated unmerged entry. */
5656 if (unmerged == file) {
5657 unmerged->status = 'U';
5658 unmerged = NULL;
5659 } else if (file->status == 'U') {
5660 unmerged = file;
5661 }
5662 }
5664 /* Grab the old name for rename/copy. */
5665 if (!*file->old.name &&
5666 (file->status == 'R' || file->status == 'C')) {
5667 string_ncopy(file->old.name, buf, strlen(buf));
5669 buf = io_get(&io, 0, TRUE);
5670 if (!buf)
5671 break;
5672 }
5674 /* git-ls-files just delivers a NUL separated list of
5675 * file names similar to the second half of the
5676 * git-diff-* output. */
5677 string_ncopy(file->new.name, buf, strlen(buf));
5678 if (!*file->old.name)
5679 string_copy(file->old.name, file->new.name);
5680 file = NULL;
5681 }
5683 if (io_error(&io)) {
5684 error_out:
5685 io_done(&io);
5686 return FALSE;
5687 }
5689 if (!view->line[view->lines - 1].data)
5690 add_line_data(view, NULL, LINE_STAT_NONE);
5692 io_done(&io);
5693 return TRUE;
5694 }
5696 /* Don't show unmerged entries in the staged section. */
5697 static const char *status_diff_index_argv[] = {
5698 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5699 "--cached", "-M", "HEAD", NULL
5700 };
5702 static const char *status_diff_files_argv[] = {
5703 "git", "diff-files", "-z", NULL
5704 };
5706 static const char *status_list_other_argv[] = {
5707 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5708 };
5710 static const char *status_list_no_head_argv[] = {
5711 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5712 };
5714 static const char *update_index_argv[] = {
5715 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5716 };
5718 /* Restore the previous line number to stay in the context or select a
5719 * line with something that can be updated. */
5720 static void
5721 status_restore(struct view *view)
5722 {
5723 if (view->p_lineno >= view->lines)
5724 view->p_lineno = view->lines - 1;
5725 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5726 view->p_lineno++;
5727 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5728 view->p_lineno--;
5730 /* If the above fails, always skip the "On branch" line. */
5731 if (view->p_lineno < view->lines)
5732 view->lineno = view->p_lineno;
5733 else
5734 view->lineno = 1;
5736 if (view->lineno < view->offset)
5737 view->offset = view->lineno;
5738 else if (view->offset + view->height <= view->lineno)
5739 view->offset = view->lineno - view->height + 1;
5741 view->p_restore = FALSE;
5742 }
5744 static void
5745 status_update_onbranch(void)
5746 {
5747 static const char *paths[][2] = {
5748 { "rebase-apply/rebasing", "Rebasing" },
5749 { "rebase-apply/applying", "Applying mailbox" },
5750 { "rebase-apply/", "Rebasing mailbox" },
5751 { "rebase-merge/interactive", "Interactive rebase" },
5752 { "rebase-merge/", "Rebase merge" },
5753 { "MERGE_HEAD", "Merging" },
5754 { "BISECT_LOG", "Bisecting" },
5755 { "HEAD", "On branch" },
5756 };
5757 char buf[SIZEOF_STR];
5758 struct stat stat;
5759 int i;
5761 if (is_initial_commit()) {
5762 string_copy(status_onbranch, "Initial commit");
5763 return;
5764 }
5766 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5767 char *head = opt_head;
5769 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5770 lstat(buf, &stat) < 0)
5771 continue;
5773 if (!*opt_head) {
5774 struct io io;
5776 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5777 io_read_buf(&io, buf, sizeof(buf))) {
5778 head = buf;
5779 if (!prefixcmp(head, "refs/heads/"))
5780 head += STRING_SIZE("refs/heads/");
5781 }
5782 }
5784 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5785 string_copy(status_onbranch, opt_head);
5786 return;
5787 }
5789 string_copy(status_onbranch, "Not currently on any branch");
5790 }
5792 /* First parse staged info using git-diff-index(1), then parse unstaged
5793 * info using git-diff-files(1), and finally untracked files using
5794 * git-ls-files(1). */
5795 static bool
5796 status_open(struct view *view)
5797 {
5798 reset_view(view);
5800 add_line_data(view, NULL, LINE_STAT_HEAD);
5801 status_update_onbranch();
5803 io_run_bg(update_index_argv);
5805 if (is_initial_commit()) {
5806 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5807 return FALSE;
5808 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5809 return FALSE;
5810 }
5812 if (!opt_untracked_dirs_content)
5813 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5815 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5816 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5817 return FALSE;
5819 /* Restore the exact position or use the specialized restore
5820 * mode? */
5821 if (!view->p_restore)
5822 status_restore(view);
5823 return TRUE;
5824 }
5826 static bool
5827 status_draw(struct view *view, struct line *line, unsigned int lineno)
5828 {
5829 struct status *status = line->data;
5830 enum line_type type;
5831 const char *text;
5833 if (!status) {
5834 switch (line->type) {
5835 case LINE_STAT_STAGED:
5836 type = LINE_STAT_SECTION;
5837 text = "Changes to be committed:";
5838 break;
5840 case LINE_STAT_UNSTAGED:
5841 type = LINE_STAT_SECTION;
5842 text = "Changed but not updated:";
5843 break;
5845 case LINE_STAT_UNTRACKED:
5846 type = LINE_STAT_SECTION;
5847 text = "Untracked files:";
5848 break;
5850 case LINE_STAT_NONE:
5851 type = LINE_DEFAULT;
5852 text = " (no files)";
5853 break;
5855 case LINE_STAT_HEAD:
5856 type = LINE_STAT_HEAD;
5857 text = status_onbranch;
5858 break;
5860 default:
5861 return FALSE;
5862 }
5863 } else {
5864 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5866 buf[0] = status->status;
5867 if (draw_text(view, line->type, buf, TRUE))
5868 return TRUE;
5869 type = LINE_DEFAULT;
5870 text = status->new.name;
5871 }
5873 draw_text(view, type, text, TRUE);
5874 return TRUE;
5875 }
5877 static enum request
5878 status_load_error(struct view *view, struct view *stage, const char *path)
5879 {
5880 if (displayed_views() == 2 || display[current_view] != view)
5881 maximize_view(view);
5882 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5883 return REQ_NONE;
5884 }
5886 static enum request
5887 status_enter(struct view *view, struct line *line)
5888 {
5889 struct status *status = line->data;
5890 const char *oldpath = status ? status->old.name : NULL;
5891 /* Diffs for unmerged entries are empty when passing the new
5892 * path, so leave it empty. */
5893 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5894 const char *info;
5895 enum open_flags split;
5896 struct view *stage = VIEW(REQ_VIEW_STAGE);
5898 if (line->type == LINE_STAT_NONE ||
5899 (!status && line[1].type == LINE_STAT_NONE)) {
5900 report("No file to diff");
5901 return REQ_NONE;
5902 }
5904 switch (line->type) {
5905 case LINE_STAT_STAGED:
5906 if (is_initial_commit()) {
5907 const char *no_head_diff_argv[] = {
5908 "git", "diff", "--no-color", "--patch-with-stat",
5909 "--", "/dev/null", newpath, NULL
5910 };
5912 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5913 return status_load_error(view, stage, newpath);
5914 } else {
5915 const char *index_show_argv[] = {
5916 "git", "diff-index", "--root", "--patch-with-stat",
5917 "-C", "-M", "--cached", "HEAD", "--",
5918 oldpath, newpath, NULL
5919 };
5921 if (!prepare_update(stage, index_show_argv, opt_cdup))
5922 return status_load_error(view, stage, newpath);
5923 }
5925 if (status)
5926 info = "Staged changes to %s";
5927 else
5928 info = "Staged changes";
5929 break;
5931 case LINE_STAT_UNSTAGED:
5932 {
5933 const char *files_show_argv[] = {
5934 "git", "diff-files", "--root", "--patch-with-stat",
5935 "-C", "-M", "--", oldpath, newpath, NULL
5936 };
5938 if (!prepare_update(stage, files_show_argv, opt_cdup))
5939 return status_load_error(view, stage, newpath);
5940 if (status)
5941 info = "Unstaged changes to %s";
5942 else
5943 info = "Unstaged changes";
5944 break;
5945 }
5946 case LINE_STAT_UNTRACKED:
5947 if (!newpath) {
5948 report("No file to show");
5949 return REQ_NONE;
5950 }
5952 if (!suffixcmp(status->new.name, -1, "/")) {
5953 report("Cannot display a directory");
5954 return REQ_NONE;
5955 }
5957 if (!prepare_update_file(stage, newpath))
5958 return status_load_error(view, stage, newpath);
5959 info = "Untracked file %s";
5960 break;
5962 case LINE_STAT_HEAD:
5963 return REQ_NONE;
5965 default:
5966 die("line type %d not handled in switch", line->type);
5967 }
5969 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5970 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5971 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5972 if (status) {
5973 stage_status = *status;
5974 } else {
5975 memset(&stage_status, 0, sizeof(stage_status));
5976 }
5978 stage_line_type = line->type;
5979 stage_chunks = 0;
5980 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5981 }
5983 return REQ_NONE;
5984 }
5986 static bool
5987 status_exists(struct status *status, enum line_type type)
5988 {
5989 struct view *view = VIEW(REQ_VIEW_STATUS);
5990 unsigned long lineno;
5992 for (lineno = 0; lineno < view->lines; lineno++) {
5993 struct line *line = &view->line[lineno];
5994 struct status *pos = line->data;
5996 if (line->type != type)
5997 continue;
5998 if (!pos && (!status || !status->status) && line[1].data) {
5999 select_view_line(view, lineno);
6000 return TRUE;
6001 }
6002 if (pos && !strcmp(status->new.name, pos->new.name)) {
6003 select_view_line(view, lineno);
6004 return TRUE;
6005 }
6006 }
6008 return FALSE;
6009 }
6012 static bool
6013 status_update_prepare(struct io *io, enum line_type type)
6014 {
6015 const char *staged_argv[] = {
6016 "git", "update-index", "-z", "--index-info", NULL
6017 };
6018 const char *others_argv[] = {
6019 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6020 };
6022 switch (type) {
6023 case LINE_STAT_STAGED:
6024 return io_run(io, IO_WR, opt_cdup, staged_argv);
6026 case LINE_STAT_UNSTAGED:
6027 case LINE_STAT_UNTRACKED:
6028 return io_run(io, IO_WR, opt_cdup, others_argv);
6030 default:
6031 die("line type %d not handled in switch", type);
6032 return FALSE;
6033 }
6034 }
6036 static bool
6037 status_update_write(struct io *io, struct status *status, enum line_type type)
6038 {
6039 char buf[SIZEOF_STR];
6040 size_t bufsize = 0;
6042 switch (type) {
6043 case LINE_STAT_STAGED:
6044 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6045 status->old.mode,
6046 status->old.rev,
6047 status->old.name, 0))
6048 return FALSE;
6049 break;
6051 case LINE_STAT_UNSTAGED:
6052 case LINE_STAT_UNTRACKED:
6053 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6054 return FALSE;
6055 break;
6057 default:
6058 die("line type %d not handled in switch", type);
6059 }
6061 return io_write(io, buf, bufsize);
6062 }
6064 static bool
6065 status_update_file(struct status *status, enum line_type type)
6066 {
6067 struct io io;
6068 bool result;
6070 if (!status_update_prepare(&io, type))
6071 return FALSE;
6073 result = status_update_write(&io, status, type);
6074 return io_done(&io) && result;
6075 }
6077 static bool
6078 status_update_files(struct view *view, struct line *line)
6079 {
6080 char buf[sizeof(view->ref)];
6081 struct io io;
6082 bool result = TRUE;
6083 struct line *pos = view->line + view->lines;
6084 int files = 0;
6085 int file, done;
6086 int cursor_y = -1, cursor_x = -1;
6088 if (!status_update_prepare(&io, line->type))
6089 return FALSE;
6091 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6092 files++;
6094 string_copy(buf, view->ref);
6095 getsyx(cursor_y, cursor_x);
6096 for (file = 0, done = 5; result && file < files; line++, file++) {
6097 int almost_done = file * 100 / files;
6099 if (almost_done > done) {
6100 done = almost_done;
6101 string_format(view->ref, "updating file %u of %u (%d%% done)",
6102 file, files, done);
6103 update_view_title(view);
6104 setsyx(cursor_y, cursor_x);
6105 doupdate();
6106 }
6107 result = status_update_write(&io, line->data, line->type);
6108 }
6109 string_copy(view->ref, buf);
6111 return io_done(&io) && result;
6112 }
6114 static bool
6115 status_update(struct view *view)
6116 {
6117 struct line *line = &view->line[view->lineno];
6119 assert(view->lines);
6121 if (!line->data) {
6122 /* This should work even for the "On branch" line. */
6123 if (line < view->line + view->lines && !line[1].data) {
6124 report("Nothing to update");
6125 return FALSE;
6126 }
6128 if (!status_update_files(view, line + 1)) {
6129 report("Failed to update file status");
6130 return FALSE;
6131 }
6133 } else if (!status_update_file(line->data, line->type)) {
6134 report("Failed to update file status");
6135 return FALSE;
6136 }
6138 return TRUE;
6139 }
6141 static bool
6142 status_revert(struct status *status, enum line_type type, bool has_none)
6143 {
6144 if (!status || type != LINE_STAT_UNSTAGED) {
6145 if (type == LINE_STAT_STAGED) {
6146 report("Cannot revert changes to staged files");
6147 } else if (type == LINE_STAT_UNTRACKED) {
6148 report("Cannot revert changes to untracked files");
6149 } else if (has_none) {
6150 report("Nothing to revert");
6151 } else {
6152 report("Cannot revert changes to multiple files");
6153 }
6155 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6156 char mode[10] = "100644";
6157 const char *reset_argv[] = {
6158 "git", "update-index", "--cacheinfo", mode,
6159 status->old.rev, status->old.name, NULL
6160 };
6161 const char *checkout_argv[] = {
6162 "git", "checkout", "--", status->old.name, NULL
6163 };
6165 if (status->status == 'U') {
6166 string_format(mode, "%5o", status->old.mode);
6168 if (status->old.mode == 0 && status->new.mode == 0) {
6169 reset_argv[2] = "--force-remove";
6170 reset_argv[3] = status->old.name;
6171 reset_argv[4] = NULL;
6172 }
6174 if (!io_run_fg(reset_argv, opt_cdup))
6175 return FALSE;
6176 if (status->old.mode == 0 && status->new.mode == 0)
6177 return TRUE;
6178 }
6180 return io_run_fg(checkout_argv, opt_cdup);
6181 }
6183 return FALSE;
6184 }
6186 static enum request
6187 status_request(struct view *view, enum request request, struct line *line)
6188 {
6189 struct status *status = line->data;
6191 switch (request) {
6192 case REQ_STATUS_UPDATE:
6193 if (!status_update(view))
6194 return REQ_NONE;
6195 break;
6197 case REQ_STATUS_REVERT:
6198 if (!status_revert(status, line->type, status_has_none(view, line)))
6199 return REQ_NONE;
6200 break;
6202 case REQ_STATUS_MERGE:
6203 if (!status || status->status != 'U') {
6204 report("Merging only possible for files with unmerged status ('U').");
6205 return REQ_NONE;
6206 }
6207 open_mergetool(status->new.name);
6208 break;
6210 case REQ_EDIT:
6211 if (!status)
6212 return request;
6213 if (status->status == 'D') {
6214 report("File has been deleted.");
6215 return REQ_NONE;
6216 }
6218 open_editor(status->new.name);
6219 break;
6221 case REQ_VIEW_BLAME:
6222 if (status)
6223 opt_ref[0] = 0;
6224 return request;
6226 case REQ_ENTER:
6227 /* After returning the status view has been split to
6228 * show the stage view. No further reloading is
6229 * necessary. */
6230 return status_enter(view, line);
6232 case REQ_REFRESH:
6233 /* Simply reload the view. */
6234 break;
6236 default:
6237 return request;
6238 }
6240 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6242 return REQ_NONE;
6243 }
6245 static void
6246 status_select(struct view *view, struct line *line)
6247 {
6248 struct status *status = line->data;
6249 char file[SIZEOF_STR] = "all files";
6250 const char *text;
6251 const char *key;
6253 if (status && !string_format(file, "'%s'", status->new.name))
6254 return;
6256 if (!status && line[1].type == LINE_STAT_NONE)
6257 line++;
6259 switch (line->type) {
6260 case LINE_STAT_STAGED:
6261 text = "Press %s to unstage %s for commit";
6262 break;
6264 case LINE_STAT_UNSTAGED:
6265 text = "Press %s to stage %s for commit";
6266 break;
6268 case LINE_STAT_UNTRACKED:
6269 text = "Press %s to stage %s for addition";
6270 break;
6272 case LINE_STAT_HEAD:
6273 case LINE_STAT_NONE:
6274 text = "Nothing to update";
6275 break;
6277 default:
6278 die("line type %d not handled in switch", line->type);
6279 }
6281 if (status && status->status == 'U') {
6282 text = "Press %s to resolve conflict in %s";
6283 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6285 } else {
6286 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6287 }
6289 string_format(view->ref, text, key, file);
6290 if (status)
6291 string_copy(opt_file, status->new.name);
6292 }
6294 static bool
6295 status_grep(struct view *view, struct line *line)
6296 {
6297 struct status *status = line->data;
6299 if (status) {
6300 const char buf[2] = { status->status, 0 };
6301 const char *text[] = { status->new.name, buf, NULL };
6303 return grep_text(view, text);
6304 }
6306 return FALSE;
6307 }
6309 static struct view_ops status_ops = {
6310 "file",
6311 NULL,
6312 status_open,
6313 NULL,
6314 status_draw,
6315 status_request,
6316 status_grep,
6317 status_select,
6318 };
6321 static bool
6322 stage_diff_write(struct io *io, struct line *line, struct line *end)
6323 {
6324 while (line < end) {
6325 if (!io_write(io, line->data, strlen(line->data)) ||
6326 !io_write(io, "\n", 1))
6327 return FALSE;
6328 line++;
6329 if (line->type == LINE_DIFF_CHUNK ||
6330 line->type == LINE_DIFF_HEADER)
6331 break;
6332 }
6334 return TRUE;
6335 }
6337 static struct line *
6338 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6339 {
6340 for (; view->line < line; line--)
6341 if (line->type == type)
6342 return line;
6344 return NULL;
6345 }
6347 static bool
6348 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6349 {
6350 const char *apply_argv[SIZEOF_ARG] = {
6351 "git", "apply", "--whitespace=nowarn", NULL
6352 };
6353 struct line *diff_hdr;
6354 struct io io;
6355 int argc = 3;
6357 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6358 if (!diff_hdr)
6359 return FALSE;
6361 if (!revert)
6362 apply_argv[argc++] = "--cached";
6363 if (revert || stage_line_type == LINE_STAT_STAGED)
6364 apply_argv[argc++] = "-R";
6365 apply_argv[argc++] = "-";
6366 apply_argv[argc++] = NULL;
6367 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6368 return FALSE;
6370 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6371 !stage_diff_write(&io, chunk, view->line + view->lines))
6372 chunk = NULL;
6374 io_done(&io);
6375 io_run_bg(update_index_argv);
6377 return chunk ? TRUE : FALSE;
6378 }
6380 static bool
6381 stage_update(struct view *view, struct line *line)
6382 {
6383 struct line *chunk = NULL;
6385 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6386 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6388 if (chunk) {
6389 if (!stage_apply_chunk(view, chunk, FALSE)) {
6390 report("Failed to apply chunk");
6391 return FALSE;
6392 }
6394 } else if (!stage_status.status) {
6395 view = VIEW(REQ_VIEW_STATUS);
6397 for (line = view->line; line < view->line + view->lines; line++)
6398 if (line->type == stage_line_type)
6399 break;
6401 if (!status_update_files(view, line + 1)) {
6402 report("Failed to update files");
6403 return FALSE;
6404 }
6406 } else if (!status_update_file(&stage_status, stage_line_type)) {
6407 report("Failed to update file");
6408 return FALSE;
6409 }
6411 return TRUE;
6412 }
6414 static bool
6415 stage_revert(struct view *view, struct line *line)
6416 {
6417 struct line *chunk = NULL;
6419 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6420 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6422 if (chunk) {
6423 if (!prompt_yesno("Are you sure you want to revert changes?"))
6424 return FALSE;
6426 if (!stage_apply_chunk(view, chunk, TRUE)) {
6427 report("Failed to revert chunk");
6428 return FALSE;
6429 }
6430 return TRUE;
6432 } else {
6433 return status_revert(stage_status.status ? &stage_status : NULL,
6434 stage_line_type, FALSE);
6435 }
6436 }
6439 static void
6440 stage_next(struct view *view, struct line *line)
6441 {
6442 int i;
6444 if (!stage_chunks) {
6445 for (line = view->line; line < view->line + view->lines; line++) {
6446 if (line->type != LINE_DIFF_CHUNK)
6447 continue;
6449 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6450 report("Allocation failure");
6451 return;
6452 }
6454 stage_chunk[stage_chunks++] = line - view->line;
6455 }
6456 }
6458 for (i = 0; i < stage_chunks; i++) {
6459 if (stage_chunk[i] > view->lineno) {
6460 do_scroll_view(view, stage_chunk[i] - view->lineno);
6461 report("Chunk %d of %d", i + 1, stage_chunks);
6462 return;
6463 }
6464 }
6466 report("No next chunk found");
6467 }
6469 static enum request
6470 stage_request(struct view *view, enum request request, struct line *line)
6471 {
6472 switch (request) {
6473 case REQ_STATUS_UPDATE:
6474 if (!stage_update(view, line))
6475 return REQ_NONE;
6476 break;
6478 case REQ_STATUS_REVERT:
6479 if (!stage_revert(view, line))
6480 return REQ_NONE;
6481 break;
6483 case REQ_STAGE_NEXT:
6484 if (stage_line_type == LINE_STAT_UNTRACKED) {
6485 report("File is untracked; press %s to add",
6486 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6487 return REQ_NONE;
6488 }
6489 stage_next(view, line);
6490 return REQ_NONE;
6492 case REQ_EDIT:
6493 if (!stage_status.new.name[0])
6494 return request;
6495 if (stage_status.status == 'D') {
6496 report("File has been deleted.");
6497 return REQ_NONE;
6498 }
6500 open_editor(stage_status.new.name);
6501 break;
6503 case REQ_REFRESH:
6504 /* Reload everything ... */
6505 break;
6507 case REQ_VIEW_BLAME:
6508 if (stage_status.new.name[0]) {
6509 string_copy(opt_file, stage_status.new.name);
6510 opt_ref[0] = 0;
6511 }
6512 return request;
6514 case REQ_ENTER:
6515 return pager_request(view, request, line);
6517 default:
6518 return request;
6519 }
6521 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6522 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6524 /* Check whether the staged entry still exists, and close the
6525 * stage view if it doesn't. */
6526 if (!status_exists(&stage_status, stage_line_type)) {
6527 status_restore(VIEW(REQ_VIEW_STATUS));
6528 return REQ_VIEW_CLOSE;
6529 }
6531 if (stage_line_type == LINE_STAT_UNTRACKED) {
6532 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6533 report("Cannot display a directory");
6534 return REQ_NONE;
6535 }
6537 if (!prepare_update_file(view, stage_status.new.name)) {
6538 report("Failed to open file: %s", strerror(errno));
6539 return REQ_NONE;
6540 }
6541 }
6542 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6544 return REQ_NONE;
6545 }
6547 static struct view_ops stage_ops = {
6548 "line",
6549 NULL,
6550 NULL,
6551 pager_read,
6552 pager_draw,
6553 stage_request,
6554 pager_grep,
6555 pager_select,
6556 };
6559 /*
6560 * Revision graph
6561 */
6563 struct commit {
6564 char id[SIZEOF_REV]; /* SHA1 ID. */
6565 char title[128]; /* First line of the commit message. */
6566 const char *author; /* Author of the commit. */
6567 struct time time; /* Date from the author ident. */
6568 struct ref_list *refs; /* Repository references. */
6569 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6570 size_t graph_size; /* The width of the graph array. */
6571 bool has_parents; /* Rewritten --parents seen. */
6572 };
6574 /* Size of rev graph with no "padding" columns */
6575 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6577 struct rev_graph {
6578 struct rev_graph *prev, *next, *parents;
6579 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6580 size_t size;
6581 struct commit *commit;
6582 size_t pos;
6583 unsigned int boundary:1;
6584 };
6586 /* Parents of the commit being visualized. */
6587 static struct rev_graph graph_parents[4];
6589 /* The current stack of revisions on the graph. */
6590 static struct rev_graph graph_stacks[4] = {
6591 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6592 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6593 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6594 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6595 };
6597 static inline bool
6598 graph_parent_is_merge(struct rev_graph *graph)
6599 {
6600 return graph->parents->size > 1;
6601 }
6603 static inline void
6604 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6605 {
6606 struct commit *commit = graph->commit;
6608 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6609 commit->graph[commit->graph_size++] = symbol;
6610 }
6612 static void
6613 clear_rev_graph(struct rev_graph *graph)
6614 {
6615 graph->boundary = 0;
6616 graph->size = graph->pos = 0;
6617 graph->commit = NULL;
6618 memset(graph->parents, 0, sizeof(*graph->parents));
6619 }
6621 static void
6622 done_rev_graph(struct rev_graph *graph)
6623 {
6624 if (graph_parent_is_merge(graph) &&
6625 graph->pos < graph->size - 1 &&
6626 graph->next->size == graph->size + graph->parents->size - 1) {
6627 size_t i = graph->pos + graph->parents->size - 1;
6629 graph->commit->graph_size = i * 2;
6630 while (i < graph->next->size - 1) {
6631 append_to_rev_graph(graph, ' ');
6632 append_to_rev_graph(graph, '\\');
6633 i++;
6634 }
6635 }
6637 clear_rev_graph(graph);
6638 }
6640 static void
6641 push_rev_graph(struct rev_graph *graph, const char *parent)
6642 {
6643 int i;
6645 /* "Collapse" duplicate parents lines.
6646 *
6647 * FIXME: This needs to also update update the drawn graph but
6648 * for now it just serves as a method for pruning graph lines. */
6649 for (i = 0; i < graph->size; i++)
6650 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6651 return;
6653 if (graph->size < SIZEOF_REVITEMS) {
6654 string_copy_rev(graph->rev[graph->size++], parent);
6655 }
6656 }
6658 static chtype
6659 get_rev_graph_symbol(struct rev_graph *graph)
6660 {
6661 chtype symbol;
6663 if (graph->boundary)
6664 symbol = REVGRAPH_BOUND;
6665 else if (graph->parents->size == 0)
6666 symbol = REVGRAPH_INIT;
6667 else if (graph_parent_is_merge(graph))
6668 symbol = REVGRAPH_MERGE;
6669 else if (graph->pos >= graph->size)
6670 symbol = REVGRAPH_BRANCH;
6671 else
6672 symbol = REVGRAPH_COMMIT;
6674 return symbol;
6675 }
6677 static void
6678 draw_rev_graph(struct rev_graph *graph)
6679 {
6680 struct rev_filler {
6681 chtype separator, line;
6682 };
6683 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6684 static struct rev_filler fillers[] = {
6685 { ' ', '|' },
6686 { '`', '.' },
6687 { '\'', ' ' },
6688 { '/', ' ' },
6689 };
6690 chtype symbol = get_rev_graph_symbol(graph);
6691 struct rev_filler *filler;
6692 size_t i;
6694 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6695 filler = &fillers[DEFAULT];
6697 for (i = 0; i < graph->pos; i++) {
6698 append_to_rev_graph(graph, filler->line);
6699 if (graph_parent_is_merge(graph->prev) &&
6700 graph->prev->pos == i)
6701 filler = &fillers[RSHARP];
6703 append_to_rev_graph(graph, filler->separator);
6704 }
6706 /* Place the symbol for this revision. */
6707 append_to_rev_graph(graph, symbol);
6709 if (graph->prev->size > graph->size)
6710 filler = &fillers[RDIAG];
6711 else
6712 filler = &fillers[DEFAULT];
6714 i++;
6716 for (; i < graph->size; i++) {
6717 append_to_rev_graph(graph, filler->separator);
6718 append_to_rev_graph(graph, filler->line);
6719 if (graph_parent_is_merge(graph->prev) &&
6720 i < graph->prev->pos + graph->parents->size)
6721 filler = &fillers[RSHARP];
6722 if (graph->prev->size > graph->size)
6723 filler = &fillers[LDIAG];
6724 }
6726 if (graph->prev->size > graph->size) {
6727 append_to_rev_graph(graph, filler->separator);
6728 if (filler->line != ' ')
6729 append_to_rev_graph(graph, filler->line);
6730 }
6731 }
6733 /* Prepare the next rev graph */
6734 static void
6735 prepare_rev_graph(struct rev_graph *graph)
6736 {
6737 size_t i;
6739 /* First, traverse all lines of revisions up to the active one. */
6740 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6741 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6742 break;
6744 push_rev_graph(graph->next, graph->rev[graph->pos]);
6745 }
6747 /* Interleave the new revision parent(s). */
6748 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6749 push_rev_graph(graph->next, graph->parents->rev[i]);
6751 /* Lastly, put any remaining revisions. */
6752 for (i = graph->pos + 1; i < graph->size; i++)
6753 push_rev_graph(graph->next, graph->rev[i]);
6754 }
6756 static void
6757 update_rev_graph(struct view *view, struct rev_graph *graph)
6758 {
6759 /* If this is the finalizing update ... */
6760 if (graph->commit)
6761 prepare_rev_graph(graph);
6763 /* Graph visualization needs a one rev look-ahead,
6764 * so the first update doesn't visualize anything. */
6765 if (!graph->prev->commit)
6766 return;
6768 if (view->lines > 2)
6769 view->line[view->lines - 3].dirty = 1;
6770 if (view->lines > 1)
6771 view->line[view->lines - 2].dirty = 1;
6772 draw_rev_graph(graph->prev);
6773 done_rev_graph(graph->prev->prev);
6774 }
6777 /*
6778 * Main view backend
6779 */
6781 static const char *main_argv[SIZEOF_ARG] = {
6782 "git", "log", "--no-color", "--pretty=raw", "--parents",
6783 "--topo-order", "%(diffargs)", "%(revargs)",
6784 "--", "%(fileargs)", NULL
6785 };
6787 static bool
6788 main_draw(struct view *view, struct line *line, unsigned int lineno)
6789 {
6790 struct commit *commit = line->data;
6792 if (!commit->author)
6793 return FALSE;
6795 if (opt_date && draw_date(view, &commit->time))
6796 return TRUE;
6798 if (opt_author && draw_author(view, commit->author))
6799 return TRUE;
6801 if (opt_rev_graph && commit->graph_size &&
6802 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6803 return TRUE;
6805 if (opt_show_refs && commit->refs) {
6806 size_t i;
6808 for (i = 0; i < commit->refs->size; i++) {
6809 struct ref *ref = commit->refs->refs[i];
6810 enum line_type type;
6812 if (ref->head)
6813 type = LINE_MAIN_HEAD;
6814 else if (ref->ltag)
6815 type = LINE_MAIN_LOCAL_TAG;
6816 else if (ref->tag)
6817 type = LINE_MAIN_TAG;
6818 else if (ref->tracked)
6819 type = LINE_MAIN_TRACKED;
6820 else if (ref->remote)
6821 type = LINE_MAIN_REMOTE;
6822 else
6823 type = LINE_MAIN_REF;
6825 if (draw_text(view, type, "[", TRUE) ||
6826 draw_text(view, type, ref->name, TRUE) ||
6827 draw_text(view, type, "]", TRUE))
6828 return TRUE;
6830 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6831 return TRUE;
6832 }
6833 }
6835 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6836 return TRUE;
6837 }
6839 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6840 static bool
6841 main_read(struct view *view, char *line)
6842 {
6843 static struct rev_graph *graph = graph_stacks;
6844 enum line_type type;
6845 struct commit *commit;
6847 if (!line) {
6848 int i;
6850 if (!view->lines && !view->prev)
6851 die("No revisions match the given arguments.");
6852 if (view->lines > 0) {
6853 commit = view->line[view->lines - 1].data;
6854 view->line[view->lines - 1].dirty = 1;
6855 if (!commit->author) {
6856 view->lines--;
6857 free(commit);
6858 graph->commit = NULL;
6859 }
6860 }
6861 update_rev_graph(view, graph);
6863 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6864 clear_rev_graph(&graph_stacks[i]);
6865 return TRUE;
6866 }
6868 type = get_line_type(line);
6869 if (type == LINE_COMMIT) {
6870 commit = calloc(1, sizeof(struct commit));
6871 if (!commit)
6872 return FALSE;
6874 line += STRING_SIZE("commit ");
6875 if (*line == '-') {
6876 graph->boundary = 1;
6877 line++;
6878 }
6880 string_copy_rev(commit->id, line);
6881 commit->refs = get_ref_list(commit->id);
6882 graph->commit = commit;
6883 add_line_data(view, commit, LINE_MAIN_COMMIT);
6885 while ((line = strchr(line, ' '))) {
6886 line++;
6887 push_rev_graph(graph->parents, line);
6888 commit->has_parents = TRUE;
6889 }
6890 return TRUE;
6891 }
6893 if (!view->lines)
6894 return TRUE;
6895 commit = view->line[view->lines - 1].data;
6897 switch (type) {
6898 case LINE_PARENT:
6899 if (commit->has_parents)
6900 break;
6901 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6902 break;
6904 case LINE_AUTHOR:
6905 parse_author_line(line + STRING_SIZE("author "),
6906 &commit->author, &commit->time);
6907 update_rev_graph(view, graph);
6908 graph = graph->next;
6909 break;
6911 default:
6912 /* Fill in the commit title if it has not already been set. */
6913 if (commit->title[0])
6914 break;
6916 /* Require titles to start with a non-space character at the
6917 * offset used by git log. */
6918 if (strncmp(line, " ", 4))
6919 break;
6920 line += 4;
6921 /* Well, if the title starts with a whitespace character,
6922 * try to be forgiving. Otherwise we end up with no title. */
6923 while (isspace(*line))
6924 line++;
6925 if (*line == '\0')
6926 break;
6927 /* FIXME: More graceful handling of titles; append "..." to
6928 * shortened titles, etc. */
6930 string_expand(commit->title, sizeof(commit->title), line, 1);
6931 view->line[view->lines - 1].dirty = 1;
6932 }
6934 return TRUE;
6935 }
6937 static enum request
6938 main_request(struct view *view, enum request request, struct line *line)
6939 {
6940 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6942 switch (request) {
6943 case REQ_ENTER:
6944 open_view(view, REQ_VIEW_DIFF, flags);
6945 break;
6946 case REQ_REFRESH:
6947 load_refs();
6948 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6949 break;
6950 default:
6951 return request;
6952 }
6954 return REQ_NONE;
6955 }
6957 static bool
6958 grep_refs(struct ref_list *list, regex_t *regex)
6959 {
6960 regmatch_t pmatch;
6961 size_t i;
6963 if (!opt_show_refs || !list)
6964 return FALSE;
6966 for (i = 0; i < list->size; i++) {
6967 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6968 return TRUE;
6969 }
6971 return FALSE;
6972 }
6974 static bool
6975 main_grep(struct view *view, struct line *line)
6976 {
6977 struct commit *commit = line->data;
6978 const char *text[] = {
6979 commit->title,
6980 opt_author ? commit->author : "",
6981 mkdate(&commit->time, opt_date),
6982 NULL
6983 };
6985 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6986 }
6988 static void
6989 main_select(struct view *view, struct line *line)
6990 {
6991 struct commit *commit = line->data;
6993 string_copy_rev(view->ref, commit->id);
6994 string_copy_rev(ref_commit, view->ref);
6995 }
6997 static struct view_ops main_ops = {
6998 "commit",
6999 main_argv,
7000 NULL,
7001 main_read,
7002 main_draw,
7003 main_request,
7004 main_grep,
7005 main_select,
7006 };
7009 /*
7010 * Status management
7011 */
7013 /* Whether or not the curses interface has been initialized. */
7014 static bool cursed = FALSE;
7016 /* Terminal hacks and workarounds. */
7017 static bool use_scroll_redrawwin;
7018 static bool use_scroll_status_wclear;
7020 /* The status window is used for polling keystrokes. */
7021 static WINDOW *status_win;
7023 /* Reading from the prompt? */
7024 static bool input_mode = FALSE;
7026 static bool status_empty = FALSE;
7028 /* Update status and title window. */
7029 static void
7030 report(const char *msg, ...)
7031 {
7032 struct view *view = display[current_view];
7034 if (input_mode)
7035 return;
7037 if (!view) {
7038 char buf[SIZEOF_STR];
7039 va_list args;
7041 va_start(args, msg);
7042 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7043 buf[sizeof(buf) - 1] = 0;
7044 buf[sizeof(buf) - 2] = '.';
7045 buf[sizeof(buf) - 3] = '.';
7046 buf[sizeof(buf) - 4] = '.';
7047 }
7048 va_end(args);
7049 die("%s", buf);
7050 }
7052 if (!status_empty || *msg) {
7053 va_list args;
7055 va_start(args, msg);
7057 wmove(status_win, 0, 0);
7058 if (view->has_scrolled && use_scroll_status_wclear)
7059 wclear(status_win);
7060 if (*msg) {
7061 vwprintw(status_win, msg, args);
7062 status_empty = FALSE;
7063 } else {
7064 status_empty = TRUE;
7065 }
7066 wclrtoeol(status_win);
7067 wnoutrefresh(status_win);
7069 va_end(args);
7070 }
7072 update_view_title(view);
7073 }
7075 static void
7076 init_display(void)
7077 {
7078 const char *term;
7079 int x, y;
7081 /* Initialize the curses library */
7082 if (isatty(STDIN_FILENO)) {
7083 cursed = !!initscr();
7084 opt_tty = stdin;
7085 } else {
7086 /* Leave stdin and stdout alone when acting as a pager. */
7087 opt_tty = fopen("/dev/tty", "r+");
7088 if (!opt_tty)
7089 die("Failed to open /dev/tty");
7090 cursed = !!newterm(NULL, opt_tty, opt_tty);
7091 }
7093 if (!cursed)
7094 die("Failed to initialize curses");
7096 nonl(); /* Disable conversion and detect newlines from input. */
7097 cbreak(); /* Take input chars one at a time, no wait for \n */
7098 noecho(); /* Don't echo input */
7099 leaveok(stdscr, FALSE);
7101 if (has_colors())
7102 init_colors();
7104 getmaxyx(stdscr, y, x);
7105 status_win = newwin(1, 0, y - 1, 0);
7106 if (!status_win)
7107 die("Failed to create status window");
7109 /* Enable keyboard mapping */
7110 keypad(status_win, TRUE);
7111 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7113 TABSIZE = opt_tab_size;
7115 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7116 if (term && !strcmp(term, "gnome-terminal")) {
7117 /* In the gnome-terminal-emulator, the message from
7118 * scrolling up one line when impossible followed by
7119 * scrolling down one line causes corruption of the
7120 * status line. This is fixed by calling wclear. */
7121 use_scroll_status_wclear = TRUE;
7122 use_scroll_redrawwin = FALSE;
7124 } else if (term && !strcmp(term, "xrvt-xpm")) {
7125 /* No problems with full optimizations in xrvt-(unicode)
7126 * and aterm. */
7127 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7129 } else {
7130 /* When scrolling in (u)xterm the last line in the
7131 * scrolling direction will update slowly. */
7132 use_scroll_redrawwin = TRUE;
7133 use_scroll_status_wclear = FALSE;
7134 }
7135 }
7137 static int
7138 get_input(int prompt_position)
7139 {
7140 struct view *view;
7141 int i, key, cursor_y, cursor_x;
7143 if (prompt_position)
7144 input_mode = TRUE;
7146 while (TRUE) {
7147 bool loading = FALSE;
7149 foreach_view (view, i) {
7150 update_view(view);
7151 if (view_is_displayed(view) && view->has_scrolled &&
7152 use_scroll_redrawwin)
7153 redrawwin(view->win);
7154 view->has_scrolled = FALSE;
7155 if (view->pipe)
7156 loading = TRUE;
7157 }
7159 /* Update the cursor position. */
7160 if (prompt_position) {
7161 getbegyx(status_win, cursor_y, cursor_x);
7162 cursor_x = prompt_position;
7163 } else {
7164 view = display[current_view];
7165 getbegyx(view->win, cursor_y, cursor_x);
7166 cursor_x = view->width - 1;
7167 cursor_y += view->lineno - view->offset;
7168 }
7169 setsyx(cursor_y, cursor_x);
7171 /* Refresh, accept single keystroke of input */
7172 doupdate();
7173 nodelay(status_win, loading);
7174 key = wgetch(status_win);
7176 /* wgetch() with nodelay() enabled returns ERR when
7177 * there's no input. */
7178 if (key == ERR) {
7180 } else if (key == KEY_RESIZE) {
7181 int height, width;
7183 getmaxyx(stdscr, height, width);
7185 wresize(status_win, 1, width);
7186 mvwin(status_win, height - 1, 0);
7187 wnoutrefresh(status_win);
7188 resize_display();
7189 redraw_display(TRUE);
7191 } else {
7192 input_mode = FALSE;
7193 return key;
7194 }
7195 }
7196 }
7198 static char *
7199 prompt_input(const char *prompt, input_handler handler, void *data)
7200 {
7201 enum input_status status = INPUT_OK;
7202 static char buf[SIZEOF_STR];
7203 size_t pos = 0;
7205 buf[pos] = 0;
7207 while (status == INPUT_OK || status == INPUT_SKIP) {
7208 int key;
7210 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7211 wclrtoeol(status_win);
7213 key = get_input(pos + 1);
7214 switch (key) {
7215 case KEY_RETURN:
7216 case KEY_ENTER:
7217 case '\n':
7218 status = pos ? INPUT_STOP : INPUT_CANCEL;
7219 break;
7221 case KEY_BACKSPACE:
7222 if (pos > 0)
7223 buf[--pos] = 0;
7224 else
7225 status = INPUT_CANCEL;
7226 break;
7228 case KEY_ESC:
7229 status = INPUT_CANCEL;
7230 break;
7232 default:
7233 if (pos >= sizeof(buf)) {
7234 report("Input string too long");
7235 return NULL;
7236 }
7238 status = handler(data, buf, key);
7239 if (status == INPUT_OK)
7240 buf[pos++] = (char) key;
7241 }
7242 }
7244 /* Clear the status window */
7245 status_empty = FALSE;
7246 report("");
7248 if (status == INPUT_CANCEL)
7249 return NULL;
7251 buf[pos++] = 0;
7253 return buf;
7254 }
7256 static enum input_status
7257 prompt_yesno_handler(void *data, char *buf, int c)
7258 {
7259 if (c == 'y' || c == 'Y')
7260 return INPUT_STOP;
7261 if (c == 'n' || c == 'N')
7262 return INPUT_CANCEL;
7263 return INPUT_SKIP;
7264 }
7266 static bool
7267 prompt_yesno(const char *prompt)
7268 {
7269 char prompt2[SIZEOF_STR];
7271 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7272 return FALSE;
7274 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7275 }
7277 static enum input_status
7278 read_prompt_handler(void *data, char *buf, int c)
7279 {
7280 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7281 }
7283 static char *
7284 read_prompt(const char *prompt)
7285 {
7286 return prompt_input(prompt, read_prompt_handler, NULL);
7287 }
7289 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7290 {
7291 enum input_status status = INPUT_OK;
7292 int size = 0;
7294 while (items[size].text)
7295 size++;
7297 while (status == INPUT_OK) {
7298 const struct menu_item *item = &items[*selected];
7299 int key;
7300 int i;
7302 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7303 prompt, *selected + 1, size);
7304 if (item->hotkey)
7305 wprintw(status_win, "[%c] ", (char) item->hotkey);
7306 wprintw(status_win, "%s", item->text);
7307 wclrtoeol(status_win);
7309 key = get_input(COLS - 1);
7310 switch (key) {
7311 case KEY_RETURN:
7312 case KEY_ENTER:
7313 case '\n':
7314 status = INPUT_STOP;
7315 break;
7317 case KEY_LEFT:
7318 case KEY_UP:
7319 *selected = *selected - 1;
7320 if (*selected < 0)
7321 *selected = size - 1;
7322 break;
7324 case KEY_RIGHT:
7325 case KEY_DOWN:
7326 *selected = (*selected + 1) % size;
7327 break;
7329 case KEY_ESC:
7330 status = INPUT_CANCEL;
7331 break;
7333 default:
7334 for (i = 0; items[i].text; i++)
7335 if (items[i].hotkey == key) {
7336 *selected = i;
7337 status = INPUT_STOP;
7338 break;
7339 }
7340 }
7341 }
7343 /* Clear the status window */
7344 status_empty = FALSE;
7345 report("");
7347 return status != INPUT_CANCEL;
7348 }
7350 /*
7351 * Repository properties
7352 */
7354 static struct ref **refs = NULL;
7355 static size_t refs_size = 0;
7356 static struct ref *refs_head = NULL;
7358 static struct ref_list **ref_lists = NULL;
7359 static size_t ref_lists_size = 0;
7361 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7362 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7363 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7365 static int
7366 compare_refs(const void *ref1_, const void *ref2_)
7367 {
7368 const struct ref *ref1 = *(const struct ref **)ref1_;
7369 const struct ref *ref2 = *(const struct ref **)ref2_;
7371 if (ref1->tag != ref2->tag)
7372 return ref2->tag - ref1->tag;
7373 if (ref1->ltag != ref2->ltag)
7374 return ref2->ltag - ref2->ltag;
7375 if (ref1->head != ref2->head)
7376 return ref2->head - ref1->head;
7377 if (ref1->tracked != ref2->tracked)
7378 return ref2->tracked - ref1->tracked;
7379 if (ref1->remote != ref2->remote)
7380 return ref2->remote - ref1->remote;
7381 return strcmp(ref1->name, ref2->name);
7382 }
7384 static void
7385 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7386 {
7387 size_t i;
7389 for (i = 0; i < refs_size; i++)
7390 if (!visitor(data, refs[i]))
7391 break;
7392 }
7394 static struct ref *
7395 get_ref_head()
7396 {
7397 return refs_head;
7398 }
7400 static struct ref_list *
7401 get_ref_list(const char *id)
7402 {
7403 struct ref_list *list;
7404 size_t i;
7406 for (i = 0; i < ref_lists_size; i++)
7407 if (!strcmp(id, ref_lists[i]->id))
7408 return ref_lists[i];
7410 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7411 return NULL;
7412 list = calloc(1, sizeof(*list));
7413 if (!list)
7414 return NULL;
7416 for (i = 0; i < refs_size; i++) {
7417 if (!strcmp(id, refs[i]->id) &&
7418 realloc_refs_list(&list->refs, list->size, 1))
7419 list->refs[list->size++] = refs[i];
7420 }
7422 if (!list->refs) {
7423 free(list);
7424 return NULL;
7425 }
7427 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7428 ref_lists[ref_lists_size++] = list;
7429 return list;
7430 }
7432 static int
7433 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7434 {
7435 struct ref *ref = NULL;
7436 bool tag = FALSE;
7437 bool ltag = FALSE;
7438 bool remote = FALSE;
7439 bool tracked = FALSE;
7440 bool head = FALSE;
7441 int from = 0, to = refs_size - 1;
7443 if (!prefixcmp(name, "refs/tags/")) {
7444 if (!suffixcmp(name, namelen, "^{}")) {
7445 namelen -= 3;
7446 name[namelen] = 0;
7447 } else {
7448 ltag = TRUE;
7449 }
7451 tag = TRUE;
7452 namelen -= STRING_SIZE("refs/tags/");
7453 name += STRING_SIZE("refs/tags/");
7455 } else if (!prefixcmp(name, "refs/remotes/")) {
7456 remote = TRUE;
7457 namelen -= STRING_SIZE("refs/remotes/");
7458 name += STRING_SIZE("refs/remotes/");
7459 tracked = !strcmp(opt_remote, name);
7461 } else if (!prefixcmp(name, "refs/heads/")) {
7462 namelen -= STRING_SIZE("refs/heads/");
7463 name += STRING_SIZE("refs/heads/");
7464 if (!strncmp(opt_head, name, namelen))
7465 return OK;
7467 } else if (!strcmp(name, "HEAD")) {
7468 head = TRUE;
7469 if (*opt_head) {
7470 namelen = strlen(opt_head);
7471 name = opt_head;
7472 }
7473 }
7475 /* If we are reloading or it's an annotated tag, replace the
7476 * previous SHA1 with the resolved commit id; relies on the fact
7477 * git-ls-remote lists the commit id of an annotated tag right
7478 * before the commit id it points to. */
7479 while (from <= to) {
7480 size_t pos = (to + from) / 2;
7481 int cmp = strcmp(name, refs[pos]->name);
7483 if (!cmp) {
7484 ref = refs[pos];
7485 break;
7486 }
7488 if (cmp < 0)
7489 to = pos - 1;
7490 else
7491 from = pos + 1;
7492 }
7494 if (!ref) {
7495 if (!realloc_refs(&refs, refs_size, 1))
7496 return ERR;
7497 ref = calloc(1, sizeof(*ref) + namelen);
7498 if (!ref)
7499 return ERR;
7500 memmove(refs + from + 1, refs + from,
7501 (refs_size - from) * sizeof(*refs));
7502 refs[from] = ref;
7503 strncpy(ref->name, name, namelen);
7504 refs_size++;
7505 }
7507 ref->head = head;
7508 ref->tag = tag;
7509 ref->ltag = ltag;
7510 ref->remote = remote;
7511 ref->tracked = tracked;
7512 string_copy_rev(ref->id, id);
7514 if (head)
7515 refs_head = ref;
7516 return OK;
7517 }
7519 static int
7520 load_refs(void)
7521 {
7522 const char *head_argv[] = {
7523 "git", "symbolic-ref", "HEAD", NULL
7524 };
7525 static const char *ls_remote_argv[SIZEOF_ARG] = {
7526 "git", "ls-remote", opt_git_dir, NULL
7527 };
7528 static bool init = FALSE;
7529 size_t i;
7531 if (!init) {
7532 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7533 die("TIG_LS_REMOTE contains too many arguments");
7534 init = TRUE;
7535 }
7537 if (!*opt_git_dir)
7538 return OK;
7540 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7541 !prefixcmp(opt_head, "refs/heads/")) {
7542 char *offset = opt_head + STRING_SIZE("refs/heads/");
7544 memmove(opt_head, offset, strlen(offset) + 1);
7545 }
7547 refs_head = NULL;
7548 for (i = 0; i < refs_size; i++)
7549 refs[i]->id[0] = 0;
7551 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7552 return ERR;
7554 /* Update the ref lists to reflect changes. */
7555 for (i = 0; i < ref_lists_size; i++) {
7556 struct ref_list *list = ref_lists[i];
7557 size_t old, new;
7559 for (old = new = 0; old < list->size; old++)
7560 if (!strcmp(list->id, list->refs[old]->id))
7561 list->refs[new++] = list->refs[old];
7562 list->size = new;
7563 }
7565 return OK;
7566 }
7568 static void
7569 set_remote_branch(const char *name, const char *value, size_t valuelen)
7570 {
7571 if (!strcmp(name, ".remote")) {
7572 string_ncopy(opt_remote, value, valuelen);
7574 } else if (*opt_remote && !strcmp(name, ".merge")) {
7575 size_t from = strlen(opt_remote);
7577 if (!prefixcmp(value, "refs/heads/"))
7578 value += STRING_SIZE("refs/heads/");
7580 if (!string_format_from(opt_remote, &from, "/%s", value))
7581 opt_remote[0] = 0;
7582 }
7583 }
7585 static void
7586 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7587 {
7588 const char *argv[SIZEOF_ARG] = { name, "=" };
7589 int argc = 1 + (cmd == option_set_command);
7590 int error = ERR;
7592 if (!argv_from_string(argv, &argc, value))
7593 config_msg = "Too many option arguments";
7594 else
7595 error = cmd(argc, argv);
7597 if (error == ERR)
7598 warn("Option 'tig.%s': %s", name, config_msg);
7599 }
7601 static bool
7602 set_environment_variable(const char *name, const char *value)
7603 {
7604 size_t len = strlen(name) + 1 + strlen(value) + 1;
7605 char *env = malloc(len);
7607 if (env &&
7608 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7609 putenv(env) == 0)
7610 return TRUE;
7611 free(env);
7612 return FALSE;
7613 }
7615 static void
7616 set_work_tree(const char *value)
7617 {
7618 char cwd[SIZEOF_STR];
7620 if (!getcwd(cwd, sizeof(cwd)))
7621 die("Failed to get cwd path: %s", strerror(errno));
7622 if (chdir(opt_git_dir) < 0)
7623 die("Failed to chdir(%s): %s", strerror(errno));
7624 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7625 die("Failed to get git path: %s", strerror(errno));
7626 if (chdir(cwd) < 0)
7627 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7628 if (chdir(value) < 0)
7629 die("Failed to chdir(%s): %s", value, strerror(errno));
7630 if (!getcwd(cwd, sizeof(cwd)))
7631 die("Failed to get cwd path: %s", strerror(errno));
7632 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7633 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7634 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7635 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7636 opt_is_inside_work_tree = TRUE;
7637 }
7639 static int
7640 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7641 {
7642 if (!strcmp(name, "i18n.commitencoding"))
7643 string_ncopy(opt_encoding, value, valuelen);
7645 else if (!strcmp(name, "core.editor"))
7646 string_ncopy(opt_editor, value, valuelen);
7648 else if (!strcmp(name, "core.worktree"))
7649 set_work_tree(value);
7651 else if (!prefixcmp(name, "tig.color."))
7652 set_repo_config_option(name + 10, value, option_color_command);
7654 else if (!prefixcmp(name, "tig.bind."))
7655 set_repo_config_option(name + 9, value, option_bind_command);
7657 else if (!prefixcmp(name, "tig."))
7658 set_repo_config_option(name + 4, value, option_set_command);
7660 else if (*opt_head && !prefixcmp(name, "branch.") &&
7661 !strncmp(name + 7, opt_head, strlen(opt_head)))
7662 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7664 return OK;
7665 }
7667 static int
7668 load_git_config(void)
7669 {
7670 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7672 return io_run_load(config_list_argv, "=", read_repo_config_option);
7673 }
7675 static int
7676 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7677 {
7678 if (!opt_git_dir[0]) {
7679 string_ncopy(opt_git_dir, name, namelen);
7681 } else if (opt_is_inside_work_tree == -1) {
7682 /* This can be 3 different values depending on the
7683 * version of git being used. If git-rev-parse does not
7684 * understand --is-inside-work-tree it will simply echo
7685 * the option else either "true" or "false" is printed.
7686 * Default to true for the unknown case. */
7687 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7689 } else if (*name == '.') {
7690 string_ncopy(opt_cdup, name, namelen);
7692 } else {
7693 string_ncopy(opt_prefix, name, namelen);
7694 }
7696 return OK;
7697 }
7699 static int
7700 load_repo_info(void)
7701 {
7702 const char *rev_parse_argv[] = {
7703 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7704 "--show-cdup", "--show-prefix", NULL
7705 };
7707 return io_run_load(rev_parse_argv, "=", read_repo_info);
7708 }
7711 /*
7712 * Main
7713 */
7715 static const char usage[] =
7716 "tig " TIG_VERSION " (" __DATE__ ")\n"
7717 "\n"
7718 "Usage: tig [options] [revs] [--] [paths]\n"
7719 " or: tig show [options] [revs] [--] [paths]\n"
7720 " or: tig blame [rev] path\n"
7721 " or: tig status\n"
7722 " or: tig < [git command output]\n"
7723 "\n"
7724 "Options:\n"
7725 " -v, --version Show version and exit\n"
7726 " -h, --help Show help message and exit";
7728 static void __NORETURN
7729 quit(int sig)
7730 {
7731 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7732 if (cursed)
7733 endwin();
7734 exit(0);
7735 }
7737 static void __NORETURN
7738 die(const char *err, ...)
7739 {
7740 va_list args;
7742 endwin();
7744 va_start(args, err);
7745 fputs("tig: ", stderr);
7746 vfprintf(stderr, err, args);
7747 fputs("\n", stderr);
7748 va_end(args);
7750 exit(1);
7751 }
7753 static void
7754 warn(const char *msg, ...)
7755 {
7756 va_list args;
7758 va_start(args, msg);
7759 fputs("tig warning: ", stderr);
7760 vfprintf(stderr, msg, args);
7761 fputs("\n", stderr);
7762 va_end(args);
7763 }
7765 static const char ***filter_args;
7767 static int
7768 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7769 {
7770 return argv_append(filter_args, name) ? OK : ERR;
7771 }
7773 static void
7774 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7775 {
7776 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7777 const char **all_argv = NULL;
7779 filter_args = args;
7780 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7781 !argv_append_array(&all_argv, argv) ||
7782 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7783 die("Failed to split arguments");
7784 argv_free(all_argv);
7785 free(all_argv);
7786 }
7788 static void
7789 filter_options(const char *argv[])
7790 {
7791 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7792 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7793 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7794 }
7796 static enum request
7797 parse_options(int argc, const char *argv[])
7798 {
7799 enum request request = REQ_VIEW_MAIN;
7800 const char *subcommand;
7801 bool seen_dashdash = FALSE;
7802 const char **filter_argv = NULL;
7803 int i;
7805 if (!isatty(STDIN_FILENO))
7806 return REQ_VIEW_PAGER;
7808 if (argc <= 1)
7809 return REQ_VIEW_MAIN;
7811 subcommand = argv[1];
7812 if (!strcmp(subcommand, "status")) {
7813 if (argc > 2)
7814 warn("ignoring arguments after `%s'", subcommand);
7815 return REQ_VIEW_STATUS;
7817 } else if (!strcmp(subcommand, "blame")) {
7818 if (argc <= 2 || argc > 4)
7819 die("invalid number of options to blame\n\n%s", usage);
7821 i = 2;
7822 if (argc == 4) {
7823 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7824 i++;
7825 }
7827 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7828 return REQ_VIEW_BLAME;
7830 } else if (!strcmp(subcommand, "show")) {
7831 request = REQ_VIEW_DIFF;
7833 } else {
7834 subcommand = NULL;
7835 }
7837 for (i = 1 + !!subcommand; i < argc; i++) {
7838 const char *opt = argv[i];
7840 if (seen_dashdash) {
7841 argv_append(&opt_file_args, opt);
7842 continue;
7844 } else if (!strcmp(opt, "--")) {
7845 seen_dashdash = TRUE;
7846 continue;
7848 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7849 printf("tig version %s\n", TIG_VERSION);
7850 quit(0);
7852 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7853 printf("%s\n", usage);
7854 quit(0);
7856 } else if (!strcmp(opt, "--all")) {
7857 argv_append(&opt_rev_args, opt);
7858 continue;
7859 }
7861 if (!argv_append(&filter_argv, opt))
7862 die("command too long");
7863 }
7865 if (filter_argv)
7866 filter_options(filter_argv);
7868 return request;
7869 }
7871 int
7872 main(int argc, const char *argv[])
7873 {
7874 const char *codeset = "UTF-8";
7875 enum request request = parse_options(argc, argv);
7876 struct view *view;
7877 size_t i;
7879 signal(SIGINT, quit);
7880 signal(SIGPIPE, SIG_IGN);
7882 if (setlocale(LC_ALL, "")) {
7883 codeset = nl_langinfo(CODESET);
7884 }
7886 if (load_repo_info() == ERR)
7887 die("Failed to load repo info.");
7889 if (load_options() == ERR)
7890 die("Failed to load user config.");
7892 if (load_git_config() == ERR)
7893 die("Failed to load repo config.");
7895 /* Require a git repository unless when running in pager mode. */
7896 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7897 die("Not a git repository");
7899 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7900 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7901 if (opt_iconv_in == ICONV_NONE)
7902 die("Failed to initialize character set conversion");
7903 }
7905 if (codeset && strcmp(codeset, "UTF-8")) {
7906 opt_iconv_out = iconv_open(codeset, "UTF-8");
7907 if (opt_iconv_out == ICONV_NONE)
7908 die("Failed to initialize character set conversion");
7909 }
7911 if (load_refs() == ERR)
7912 die("Failed to load refs.");
7914 foreach_view (view, i) {
7915 if (getenv(view->cmd_env))
7916 warn("Use of the %s environment variable is deprecated,"
7917 " use options or TIG_DIFF_ARGS instead",
7918 view->cmd_env);
7919 if (!argv_from_env(view->ops->argv, view->cmd_env))
7920 die("Too many arguments in the `%s` environment variable",
7921 view->cmd_env);
7922 }
7924 init_display();
7926 while (view_driver(display[current_view], request)) {
7927 int key = get_input(0);
7929 view = display[current_view];
7930 request = get_keybinding(view->keymap, key);
7932 /* Some low-level request handling. This keeps access to
7933 * status_win restricted. */
7934 switch (request) {
7935 case REQ_NONE:
7936 report("Unknown key, press %s for help",
7937 get_key(view->keymap, REQ_VIEW_HELP));
7938 break;
7939 case REQ_PROMPT:
7940 {
7941 char *cmd = read_prompt(":");
7943 if (cmd && isdigit(*cmd)) {
7944 int lineno = view->lineno + 1;
7946 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7947 select_view_line(view, lineno - 1);
7948 report("");
7949 } else {
7950 report("Unable to parse '%s' as a line number", cmd);
7951 }
7953 } else if (cmd) {
7954 struct view *next = VIEW(REQ_VIEW_PAGER);
7955 const char *argv[SIZEOF_ARG] = { "git" };
7956 int argc = 1;
7958 /* When running random commands, initially show the
7959 * command in the title. However, it maybe later be
7960 * overwritten if a commit line is selected. */
7961 string_ncopy(next->ref, cmd, strlen(cmd));
7963 if (!argv_from_string(argv, &argc, cmd)) {
7964 report("Too many arguments");
7965 } else if (!prepare_update(next, argv, NULL)) {
7966 report("Failed to format command");
7967 } else {
7968 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7969 }
7970 }
7972 request = REQ_NONE;
7973 break;
7974 }
7975 case REQ_SEARCH:
7976 case REQ_SEARCH_BACK:
7977 {
7978 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7979 char *search = read_prompt(prompt);
7981 if (search)
7982 string_ncopy(opt_search, search, strlen(search));
7983 else if (*opt_search)
7984 request = request == REQ_SEARCH ?
7985 REQ_FIND_NEXT :
7986 REQ_FIND_PREV;
7987 else
7988 request = REQ_NONE;
7989 break;
7990 }
7991 default:
7992 break;
7993 }
7994 }
7996 quit(0);
7998 return 0;
7999 }