903efdeb7fe264ba5312e0a7979cfcc99fe00c5f
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
137 };
139 static struct ref *get_ref_head();
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum input_status {
145 INPUT_OK,
146 INPUT_SKIP,
147 INPUT_STOP,
148 INPUT_CANCEL
149 };
151 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
153 static char *prompt_input(const char *prompt, input_handler handler, void *data);
154 static bool prompt_yesno(const char *prompt);
156 struct menu_item {
157 int hotkey;
158 const char *text;
159 void *data;
160 };
162 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
164 /*
165 * Allocation helpers ... Entering macro hell to never be seen again.
166 */
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 static type * \
170 name(type **mem, size_t size, size_t increase) \
171 { \
172 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
173 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174 type *tmp = *mem; \
175 \
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178 if (tmp) \
179 *mem = tmp; \
180 } \
181 \
182 return tmp; \
183 }
185 /*
186 * String helpers
187 */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191 {
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
197 }
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static size_t
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215 {
216 size_t size, pos;
218 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219 if (src[pos] == '\t') {
220 size_t expanded = tabsize - (size % tabsize);
222 if (expanded + size >= dstlen - 1)
223 expanded = dstlen - size - 1;
224 memcpy(dst + size, " ", expanded);
225 size += expanded;
226 } else {
227 dst[size++] = src[pos];
228 }
229 }
231 dst[size] = 0;
232 return pos;
233 }
235 static char *
236 chomp_string(char *name)
237 {
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
248 }
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252 {
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
264 }
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
274 {
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
289 }
291 return 0;
292 }
294 #define enum_equals(entry, str, len) \
295 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
297 struct enum_map {
298 const char *name;
299 int namelen;
300 int value;
301 };
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 static char *
306 enum_map_name(const char *name, size_t namelen)
307 {
308 static char buf[SIZEOF_STR];
309 int bufpos;
311 for (bufpos = 0; bufpos <= namelen; bufpos++) {
312 buf[bufpos] = tolower(name[bufpos]);
313 if (buf[bufpos] == '_')
314 buf[bufpos] = '-';
315 }
317 buf[bufpos] = 0;
318 return buf;
319 }
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 static bool
324 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
325 {
326 size_t namelen = strlen(name);
327 int i;
329 for (i = 0; i < map_size; i++)
330 if (enum_equals(map[i], name, namelen)) {
331 *value = map[i].value;
332 return TRUE;
333 }
335 return FALSE;
336 }
338 #define map_enum(attr, map, name) \
339 map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342 strncmp(str1, str2, STRING_SIZE(str2))
344 static inline int
345 suffixcmp(const char *str, int slen, const char *suffix)
346 {
347 size_t len = slen >= 0 ? slen : strlen(str);
348 size_t suffixlen = strlen(suffix);
350 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
351 }
354 /*
355 * Unicode / UTF-8 handling
356 *
357 * NOTE: Much of the following code for dealing with Unicode is derived from
358 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
360 */
362 static inline int
363 unicode_width(unsigned long c, int tab_size)
364 {
365 if (c >= 0x1100 &&
366 (c <= 0x115f /* Hangul Jamo */
367 || c == 0x2329
368 || c == 0x232a
369 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
370 /* CJK ... Yi */
371 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
372 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
373 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
374 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
375 || (c >= 0xffe0 && c <= 0xffe6)
376 || (c >= 0x20000 && c <= 0x2fffd)
377 || (c >= 0x30000 && c <= 0x3fffd)))
378 return 2;
380 if (c == '\t')
381 return tab_size;
383 return 1;
384 }
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387 * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes[256] = {
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
396 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
397 };
399 static inline unsigned char
400 utf8_char_length(const char *string, const char *end)
401 {
402 int c = *(unsigned char *) string;
404 return utf8_bytes[c];
405 }
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string, size_t length)
410 {
411 unsigned long unicode;
413 switch (length) {
414 case 1:
415 unicode = string[0];
416 break;
417 case 2:
418 unicode = (string[0] & 0x1f) << 6;
419 unicode += (string[1] & 0x3f);
420 break;
421 case 3:
422 unicode = (string[0] & 0x0f) << 12;
423 unicode += ((string[1] & 0x3f) << 6);
424 unicode += (string[2] & 0x3f);
425 break;
426 case 4:
427 unicode = (string[0] & 0x0f) << 18;
428 unicode += ((string[1] & 0x3f) << 12);
429 unicode += ((string[2] & 0x3f) << 6);
430 unicode += (string[3] & 0x3f);
431 break;
432 case 5:
433 unicode = (string[0] & 0x0f) << 24;
434 unicode += ((string[1] & 0x3f) << 18);
435 unicode += ((string[2] & 0x3f) << 12);
436 unicode += ((string[3] & 0x3f) << 6);
437 unicode += (string[4] & 0x3f);
438 break;
439 case 6:
440 unicode = (string[0] & 0x01) << 30;
441 unicode += ((string[1] & 0x3f) << 24);
442 unicode += ((string[2] & 0x3f) << 18);
443 unicode += ((string[3] & 0x3f) << 12);
444 unicode += ((string[4] & 0x3f) << 6);
445 unicode += (string[5] & 0x3f);
446 break;
447 default:
448 return 0;
449 }
451 /* Invalid characters could return the special 0xfffd value but NUL
452 * should be just as good. */
453 return unicode > 0xffff ? 0 : unicode;
454 }
456 /* Calculates how much of string can be shown within the given maximum width
457 * and sets trimmed parameter to non-zero value if all of string could not be
458 * shown. If the reserve flag is TRUE, it will reserve at least one
459 * trailing character, which can be useful when drawing a delimiter.
460 *
461 * Returns the number of bytes to output from string to satisfy max_width. */
462 static size_t
463 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
464 {
465 const char *string = *start;
466 const char *end = strchr(string, '\0');
467 unsigned char last_bytes = 0;
468 size_t last_ucwidth = 0;
470 *width = 0;
471 *trimmed = 0;
473 while (string < end) {
474 unsigned char bytes = utf8_char_length(string, end);
475 size_t ucwidth;
476 unsigned long unicode;
478 if (string + bytes > end)
479 break;
481 /* Change representation to figure out whether
482 * it is a single- or double-width character. */
484 unicode = utf8_to_unicode(string, bytes);
485 /* FIXME: Graceful handling of invalid Unicode character. */
486 if (!unicode)
487 break;
489 ucwidth = unicode_width(unicode, tab_size);
490 if (skip > 0) {
491 skip -= ucwidth <= skip ? ucwidth : skip;
492 *start += bytes;
493 }
494 *width += ucwidth;
495 if (*width > max_width) {
496 *trimmed = 1;
497 *width -= ucwidth;
498 if (reserve && *width == max_width) {
499 string -= last_bytes;
500 *width -= last_ucwidth;
501 }
502 break;
503 }
505 string += bytes;
506 last_bytes = ucwidth ? bytes : 0;
507 last_ucwidth = ucwidth;
508 }
510 return string - *start;
511 }
514 #define DATE_INFO \
515 DATE_(NO), \
516 DATE_(DEFAULT), \
517 DATE_(LOCAL), \
518 DATE_(RELATIVE), \
519 DATE_(SHORT)
521 enum date {
522 #define DATE_(name) DATE_##name
523 DATE_INFO
524 #undef DATE_
525 };
527 static const struct enum_map date_map[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
529 DATE_INFO
530 #undef DATE_
531 };
533 struct time {
534 time_t sec;
535 int tz;
536 };
538 static inline int timecmp(const struct time *t1, const struct time *t2)
539 {
540 return t1->sec - t2->sec;
541 }
543 static const char *
544 mkdate(const struct time *time, enum date date)
545 {
546 static char buf[DATE_COLS + 1];
547 static const struct enum_map reldate[] = {
548 { "second", 1, 60 * 2 },
549 { "minute", 60, 60 * 60 * 2 },
550 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
551 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
552 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
553 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
554 };
555 struct tm tm;
557 if (!date || !time || !time->sec)
558 return "";
560 if (date == DATE_RELATIVE) {
561 struct timeval now;
562 time_t date = time->sec + time->tz;
563 time_t seconds;
564 int i;
566 gettimeofday(&now, NULL);
567 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
568 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
569 if (seconds >= reldate[i].value)
570 continue;
572 seconds /= reldate[i].namelen;
573 if (!string_format(buf, "%ld %s%s %s",
574 seconds, reldate[i].name,
575 seconds > 1 ? "s" : "",
576 now.tv_sec >= date ? "ago" : "ahead"))
577 break;
578 return buf;
579 }
580 }
582 if (date == DATE_LOCAL) {
583 time_t date = time->sec + time->tz;
584 localtime_r(&date, &tm);
585 }
586 else {
587 gmtime_r(&time->sec, &tm);
588 }
589 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
590 }
593 #define AUTHOR_VALUES \
594 AUTHOR_(NO), \
595 AUTHOR_(FULL), \
596 AUTHOR_(ABBREVIATED)
598 enum author {
599 #define AUTHOR_(name) AUTHOR_##name
600 AUTHOR_VALUES,
601 #undef AUTHOR_
602 AUTHOR_DEFAULT = AUTHOR_FULL
603 };
605 static const struct enum_map author_map[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
607 AUTHOR_VALUES
608 #undef AUTHOR_
609 };
611 static const char *
612 get_author_initials(const char *author)
613 {
614 static char initials[AUTHOR_COLS * 6 + 1];
615 size_t pos = 0;
616 const char *end = strchr(author, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620 memset(initials, 0, sizeof(initials));
621 while (author < end) {
622 unsigned char bytes;
623 size_t i;
625 while (is_initial_sep(*author))
626 author++;
628 bytes = utf8_char_length(author, end);
629 if (bytes < sizeof(initials) - 1 - pos) {
630 while (bytes--) {
631 initials[pos++] = *author++;
632 }
633 }
635 for (i = pos; author < end && !is_initial_sep(*author); author++) {
636 if (i < sizeof(initials) - 1)
637 initials[i++] = *author;
638 }
640 initials[i++] = 0;
641 }
643 return initials;
644 }
647 static bool
648 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649 {
650 int valuelen;
652 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
653 bool advance = cmd[valuelen] != 0;
655 cmd[valuelen] = 0;
656 argv[(*argc)++] = chomp_string(cmd);
657 cmd = chomp_string(cmd + valuelen + advance);
658 }
660 if (*argc < SIZEOF_ARG)
661 argv[*argc] = NULL;
662 return *argc < SIZEOF_ARG;
663 }
665 static bool
666 argv_from_env(const char **argv, const char *name)
667 {
668 char *env = argv ? getenv(name) : NULL;
669 int argc = 0;
671 if (env && *env)
672 env = strdup(env);
673 return !env || argv_from_string(argv, &argc, env);
674 }
676 static void
677 argv_free(const char *argv[])
678 {
679 int argc;
681 if (!argv)
682 return;
683 for (argc = 0; argv[argc]; argc++)
684 free((void *) argv[argc]);
685 argv[0] = NULL;
686 }
688 static size_t
689 argv_size(const char **argv)
690 {
691 int argc = 0;
693 while (argv && argv[argc])
694 argc++;
696 return argc;
697 }
699 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
701 static bool
702 argv_append(const char ***argv, const char *arg)
703 {
704 size_t argc = argv_size(*argv);
706 if (!argv_realloc(argv, argc, 2))
707 return FALSE;
709 (*argv)[argc++] = strdup(arg);
710 (*argv)[argc] = NULL;
711 return TRUE;
712 }
714 static bool
715 argv_append_array(const char ***dst_argv, const char *src_argv[])
716 {
717 int i;
719 for (i = 0; src_argv && src_argv[i]; i++)
720 if (!argv_append(dst_argv, src_argv[i]))
721 return FALSE;
722 return TRUE;
723 }
725 static bool
726 argv_copy(const char ***dst, const char *src[])
727 {
728 int argc;
730 for (argc = 0; src[argc]; argc++)
731 if (!argv_append(dst, src[argc]))
732 return FALSE;
733 return TRUE;
734 }
737 /*
738 * Executing external commands.
739 */
741 enum io_type {
742 IO_FD, /* File descriptor based IO. */
743 IO_BG, /* Execute command in the background. */
744 IO_FG, /* Execute command with same std{in,out,err}. */
745 IO_RD, /* Read only fork+exec IO. */
746 IO_WR, /* Write only fork+exec IO. */
747 IO_AP, /* Append fork+exec output to file. */
748 };
750 struct io {
751 int pipe; /* Pipe end for reading or writing. */
752 pid_t pid; /* PID of spawned process. */
753 int error; /* Error status. */
754 char *buf; /* Read buffer. */
755 size_t bufalloc; /* Allocated buffer size. */
756 size_t bufsize; /* Buffer content size. */
757 char *bufpos; /* Current buffer position. */
758 unsigned int eof:1; /* Has end of file been reached. */
759 };
761 static void
762 io_init(struct io *io)
763 {
764 memset(io, 0, sizeof(*io));
765 io->pipe = -1;
766 }
768 static bool
769 io_open(struct io *io, const char *fmt, ...)
770 {
771 char name[SIZEOF_STR] = "";
772 bool fits;
773 va_list args;
775 io_init(io);
777 va_start(args, fmt);
778 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
779 va_end(args);
781 if (!fits) {
782 io->error = ENAMETOOLONG;
783 return FALSE;
784 }
785 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
786 if (io->pipe == -1)
787 io->error = errno;
788 return io->pipe != -1;
789 }
791 static bool
792 io_kill(struct io *io)
793 {
794 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
795 }
797 static bool
798 io_done(struct io *io)
799 {
800 pid_t pid = io->pid;
802 if (io->pipe != -1)
803 close(io->pipe);
804 free(io->buf);
805 io_init(io);
807 while (pid > 0) {
808 int status;
809 pid_t waiting = waitpid(pid, &status, 0);
811 if (waiting < 0) {
812 if (errno == EINTR)
813 continue;
814 io->error = errno;
815 return FALSE;
816 }
818 return waiting == pid &&
819 !WIFSIGNALED(status) &&
820 WIFEXITED(status) &&
821 !WEXITSTATUS(status);
822 }
824 return TRUE;
825 }
827 static bool
828 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
829 {
830 int pipefds[2] = { -1, -1 };
831 va_list args;
833 io_init(io);
835 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
836 io->error = errno;
837 return FALSE;
838 } else if (type == IO_AP) {
839 va_start(args, argv);
840 pipefds[1] = va_arg(args, int);
841 va_end(args);
842 }
844 if ((io->pid = fork())) {
845 if (io->pid == -1)
846 io->error = errno;
847 if (pipefds[!(type == IO_WR)] != -1)
848 close(pipefds[!(type == IO_WR)]);
849 if (io->pid != -1) {
850 io->pipe = pipefds[!!(type == IO_WR)];
851 return TRUE;
852 }
854 } else {
855 if (type != IO_FG) {
856 int devnull = open("/dev/null", O_RDWR);
857 int readfd = type == IO_WR ? pipefds[0] : devnull;
858 int writefd = (type == IO_RD || type == IO_AP)
859 ? pipefds[1] : devnull;
861 dup2(readfd, STDIN_FILENO);
862 dup2(writefd, STDOUT_FILENO);
863 dup2(devnull, STDERR_FILENO);
865 close(devnull);
866 if (pipefds[0] != -1)
867 close(pipefds[0]);
868 if (pipefds[1] != -1)
869 close(pipefds[1]);
870 }
872 if (dir && *dir && chdir(dir) == -1)
873 exit(errno);
875 execvp(argv[0], (char *const*) argv);
876 exit(errno);
877 }
879 if (pipefds[!!(type == IO_WR)] != -1)
880 close(pipefds[!!(type == IO_WR)]);
881 return FALSE;
882 }
884 static bool
885 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
886 {
887 struct io io;
889 return io_run(&io, type, dir, argv, fd) && io_done(&io);
890 }
892 static bool
893 io_run_bg(const char **argv)
894 {
895 return io_complete(IO_BG, argv, NULL, -1);
896 }
898 static bool
899 io_run_fg(const char **argv, const char *dir)
900 {
901 return io_complete(IO_FG, argv, dir, -1);
902 }
904 static bool
905 io_run_append(const char **argv, int fd)
906 {
907 return io_complete(IO_AP, argv, NULL, fd);
908 }
910 static bool
911 io_eof(struct io *io)
912 {
913 return io->eof;
914 }
916 static int
917 io_error(struct io *io)
918 {
919 return io->error;
920 }
922 static char *
923 io_strerror(struct io *io)
924 {
925 return strerror(io->error);
926 }
928 static bool
929 io_can_read(struct io *io)
930 {
931 struct timeval tv = { 0, 500 };
932 fd_set fds;
934 FD_ZERO(&fds);
935 FD_SET(io->pipe, &fds);
937 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
938 }
940 static ssize_t
941 io_read(struct io *io, void *buf, size_t bufsize)
942 {
943 do {
944 ssize_t readsize = read(io->pipe, buf, bufsize);
946 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
947 continue;
948 else if (readsize == -1)
949 io->error = errno;
950 else if (readsize == 0)
951 io->eof = 1;
952 return readsize;
953 } while (1);
954 }
956 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
958 static char *
959 io_get(struct io *io, int c, bool can_read)
960 {
961 char *eol;
962 ssize_t readsize;
964 while (TRUE) {
965 if (io->bufsize > 0) {
966 eol = memchr(io->bufpos, c, io->bufsize);
967 if (eol) {
968 char *line = io->bufpos;
970 *eol = 0;
971 io->bufpos = eol + 1;
972 io->bufsize -= io->bufpos - line;
973 return line;
974 }
975 }
977 if (io_eof(io)) {
978 if (io->bufsize) {
979 io->bufpos[io->bufsize] = 0;
980 io->bufsize = 0;
981 return io->bufpos;
982 }
983 return NULL;
984 }
986 if (!can_read)
987 return NULL;
989 if (io->bufsize > 0 && io->bufpos > io->buf)
990 memmove(io->buf, io->bufpos, io->bufsize);
992 if (io->bufalloc == io->bufsize) {
993 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
994 return NULL;
995 io->bufalloc += BUFSIZ;
996 }
998 io->bufpos = io->buf;
999 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1000 if (io_error(io))
1001 return NULL;
1002 io->bufsize += readsize;
1003 }
1004 }
1006 static bool
1007 io_write(struct io *io, const void *buf, size_t bufsize)
1008 {
1009 size_t written = 0;
1011 while (!io_error(io) && written < bufsize) {
1012 ssize_t size;
1014 size = write(io->pipe, buf + written, bufsize - written);
1015 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1016 continue;
1017 else if (size == -1)
1018 io->error = errno;
1019 else
1020 written += size;
1021 }
1023 return written == bufsize;
1024 }
1026 static bool
1027 io_read_buf(struct io *io, char buf[], size_t bufsize)
1028 {
1029 char *result = io_get(io, '\n', TRUE);
1031 if (result) {
1032 result = chomp_string(result);
1033 string_ncopy_do(buf, bufsize, result, strlen(result));
1034 }
1036 return io_done(io) && result;
1037 }
1039 static bool
1040 io_run_buf(const char **argv, char buf[], size_t bufsize)
1041 {
1042 struct io io;
1044 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1045 }
1047 static int
1048 io_load(struct io *io, const char *separators,
1049 int (*read_property)(char *, size_t, char *, size_t))
1050 {
1051 char *name;
1052 int state = OK;
1054 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1055 char *value;
1056 size_t namelen;
1057 size_t valuelen;
1059 name = chomp_string(name);
1060 namelen = strcspn(name, separators);
1062 if (name[namelen]) {
1063 name[namelen] = 0;
1064 value = chomp_string(name + namelen + 1);
1065 valuelen = strlen(value);
1067 } else {
1068 value = "";
1069 valuelen = 0;
1070 }
1072 state = read_property(name, namelen, value, valuelen);
1073 }
1075 if (state != ERR && io_error(io))
1076 state = ERR;
1077 io_done(io);
1079 return state;
1080 }
1082 static int
1083 io_run_load(const char **argv, const char *separators,
1084 int (*read_property)(char *, size_t, char *, size_t))
1085 {
1086 struct io io;
1088 if (!io_run(&io, IO_RD, NULL, argv))
1089 return ERR;
1090 return io_load(&io, separators, read_property);
1091 }
1094 /*
1095 * User requests
1096 */
1098 #define REQ_INFO \
1099 /* XXX: Keep the view request first and in sync with views[]. */ \
1100 REQ_GROUP("View switching") \
1101 REQ_(VIEW_MAIN, "Show main view"), \
1102 REQ_(VIEW_DIFF, "Show diff view"), \
1103 REQ_(VIEW_LOG, "Show log view"), \
1104 REQ_(VIEW_TREE, "Show tree view"), \
1105 REQ_(VIEW_BLOB, "Show blob view"), \
1106 REQ_(VIEW_BLAME, "Show blame view"), \
1107 REQ_(VIEW_BRANCH, "Show branch view"), \
1108 REQ_(VIEW_HELP, "Show help page"), \
1109 REQ_(VIEW_PAGER, "Show pager view"), \
1110 REQ_(VIEW_STATUS, "Show status view"), \
1111 REQ_(VIEW_STAGE, "Show stage view"), \
1112 \
1113 REQ_GROUP("View manipulation") \
1114 REQ_(ENTER, "Enter current line and scroll"), \
1115 REQ_(NEXT, "Move to next"), \
1116 REQ_(PREVIOUS, "Move to previous"), \
1117 REQ_(PARENT, "Move to parent"), \
1118 REQ_(VIEW_NEXT, "Move focus to next view"), \
1119 REQ_(REFRESH, "Reload and refresh"), \
1120 REQ_(MAXIMIZE, "Maximize the current view"), \
1121 REQ_(VIEW_CLOSE, "Close the current view"), \
1122 REQ_(QUIT, "Close all views and quit"), \
1123 \
1124 REQ_GROUP("View specific requests") \
1125 REQ_(STATUS_UPDATE, "Update file status"), \
1126 REQ_(STATUS_REVERT, "Revert file changes"), \
1127 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1128 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1129 \
1130 REQ_GROUP("Cursor navigation") \
1131 REQ_(MOVE_UP, "Move cursor one line up"), \
1132 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1133 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1134 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1135 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1136 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1137 \
1138 REQ_GROUP("Scrolling") \
1139 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1140 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1141 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1142 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1143 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1144 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1145 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1146 \
1147 REQ_GROUP("Searching") \
1148 REQ_(SEARCH, "Search the view"), \
1149 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1150 REQ_(FIND_NEXT, "Find next search match"), \
1151 REQ_(FIND_PREV, "Find previous search match"), \
1152 \
1153 REQ_GROUP("Option manipulation") \
1154 REQ_(OPTIONS, "Open option menu"), \
1155 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1156 REQ_(TOGGLE_DATE, "Toggle date display"), \
1157 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1158 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1159 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1160 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1161 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1162 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1163 \
1164 REQ_GROUP("Misc") \
1165 REQ_(PROMPT, "Bring up the prompt"), \
1166 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1167 REQ_(SHOW_VERSION, "Show version information"), \
1168 REQ_(STOP_LOADING, "Stop all loading views"), \
1169 REQ_(EDIT, "Open in editor"), \
1170 REQ_(NONE, "Do nothing")
1173 /* User action requests. */
1174 enum request {
1175 #define REQ_GROUP(help)
1176 #define REQ_(req, help) REQ_##req
1178 /* Offset all requests to avoid conflicts with ncurses getch values. */
1179 REQ_UNKNOWN = KEY_MAX + 1,
1180 REQ_OFFSET,
1181 REQ_INFO
1183 #undef REQ_GROUP
1184 #undef REQ_
1185 };
1187 struct request_info {
1188 enum request request;
1189 const char *name;
1190 int namelen;
1191 const char *help;
1192 };
1194 static const struct request_info req_info[] = {
1195 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1196 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1197 REQ_INFO
1198 #undef REQ_GROUP
1199 #undef REQ_
1200 };
1202 static enum request
1203 get_request(const char *name)
1204 {
1205 int namelen = strlen(name);
1206 int i;
1208 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1209 if (enum_equals(req_info[i], name, namelen))
1210 return req_info[i].request;
1212 return REQ_UNKNOWN;
1213 }
1216 /*
1217 * Options
1218 */
1220 /* Option and state variables. */
1221 static enum date opt_date = DATE_DEFAULT;
1222 static enum author opt_author = AUTHOR_DEFAULT;
1223 static bool opt_line_number = FALSE;
1224 static bool opt_line_graphics = TRUE;
1225 static bool opt_rev_graph = FALSE;
1226 static bool opt_show_refs = TRUE;
1227 static int opt_num_interval = 5;
1228 static double opt_hscroll = 0.50;
1229 static double opt_scale_split_view = 2.0 / 3.0;
1230 static int opt_tab_size = 8;
1231 static int opt_author_cols = AUTHOR_COLS;
1232 static char opt_path[SIZEOF_STR] = "";
1233 static char opt_file[SIZEOF_STR] = "";
1234 static char opt_ref[SIZEOF_REF] = "";
1235 static char opt_head[SIZEOF_REF] = "";
1236 static char opt_remote[SIZEOF_REF] = "";
1237 static char opt_encoding[20] = "UTF-8";
1238 static iconv_t opt_iconv_in = ICONV_NONE;
1239 static iconv_t opt_iconv_out = ICONV_NONE;
1240 static char opt_search[SIZEOF_STR] = "";
1241 static char opt_cdup[SIZEOF_STR] = "";
1242 static char opt_prefix[SIZEOF_STR] = "";
1243 static char opt_git_dir[SIZEOF_STR] = "";
1244 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1245 static char opt_editor[SIZEOF_STR] = "";
1246 static FILE *opt_tty = NULL;
1247 static const char **opt_diff_args = NULL;
1248 static const char **opt_rev_args = NULL;
1249 static const char **opt_file_args = NULL;
1251 #define is_initial_commit() (!get_ref_head())
1252 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1255 /*
1256 * Line-oriented content detection.
1257 */
1259 #define LINE_INFO \
1260 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1274 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1275 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1276 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1281 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1283 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1285 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1291 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1292 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1293 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1294 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1295 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1298 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1299 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1301 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1307 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1308 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1309 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1310 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1311 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1312 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1313 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1315 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1317 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1318 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1320 enum line_type {
1321 #define LINE(type, line, fg, bg, attr) \
1322 LINE_##type
1323 LINE_INFO,
1324 LINE_NONE
1325 #undef LINE
1326 };
1328 struct line_info {
1329 const char *name; /* Option name. */
1330 int namelen; /* Size of option name. */
1331 const char *line; /* The start of line to match. */
1332 int linelen; /* Size of string to match. */
1333 int fg, bg, attr; /* Color and text attributes for the lines. */
1334 };
1336 static struct line_info line_info[] = {
1337 #define LINE(type, line, fg, bg, attr) \
1338 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1339 LINE_INFO
1340 #undef LINE
1341 };
1343 static enum line_type
1344 get_line_type(const char *line)
1345 {
1346 int linelen = strlen(line);
1347 enum line_type type;
1349 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1350 /* Case insensitive search matches Signed-off-by lines better. */
1351 if (linelen >= line_info[type].linelen &&
1352 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1353 return type;
1355 return LINE_DEFAULT;
1356 }
1358 static inline int
1359 get_line_attr(enum line_type type)
1360 {
1361 assert(type < ARRAY_SIZE(line_info));
1362 return COLOR_PAIR(type) | line_info[type].attr;
1363 }
1365 static struct line_info *
1366 get_line_info(const char *name)
1367 {
1368 size_t namelen = strlen(name);
1369 enum line_type type;
1371 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1372 if (enum_equals(line_info[type], name, namelen))
1373 return &line_info[type];
1375 return NULL;
1376 }
1378 static void
1379 init_colors(void)
1380 {
1381 int default_bg = line_info[LINE_DEFAULT].bg;
1382 int default_fg = line_info[LINE_DEFAULT].fg;
1383 enum line_type type;
1385 start_color();
1387 if (assume_default_colors(default_fg, default_bg) == ERR) {
1388 default_bg = COLOR_BLACK;
1389 default_fg = COLOR_WHITE;
1390 }
1392 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1393 struct line_info *info = &line_info[type];
1394 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1395 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1397 init_pair(type, fg, bg);
1398 }
1399 }
1401 struct line {
1402 enum line_type type;
1404 /* State flags */
1405 unsigned int selected:1;
1406 unsigned int dirty:1;
1407 unsigned int cleareol:1;
1408 unsigned int other:16;
1410 void *data; /* User data */
1411 };
1414 /*
1415 * Keys
1416 */
1418 struct keybinding {
1419 int alias;
1420 enum request request;
1421 };
1423 static struct keybinding default_keybindings[] = {
1424 /* View switching */
1425 { 'm', REQ_VIEW_MAIN },
1426 { 'd', REQ_VIEW_DIFF },
1427 { 'l', REQ_VIEW_LOG },
1428 { 't', REQ_VIEW_TREE },
1429 { 'f', REQ_VIEW_BLOB },
1430 { 'B', REQ_VIEW_BLAME },
1431 { 'H', REQ_VIEW_BRANCH },
1432 { 'p', REQ_VIEW_PAGER },
1433 { 'h', REQ_VIEW_HELP },
1434 { 'S', REQ_VIEW_STATUS },
1435 { 'c', REQ_VIEW_STAGE },
1437 /* View manipulation */
1438 { 'q', REQ_VIEW_CLOSE },
1439 { KEY_TAB, REQ_VIEW_NEXT },
1440 { KEY_RETURN, REQ_ENTER },
1441 { KEY_UP, REQ_PREVIOUS },
1442 { KEY_CTL('P'), REQ_PREVIOUS },
1443 { KEY_DOWN, REQ_NEXT },
1444 { KEY_CTL('N'), REQ_NEXT },
1445 { 'R', REQ_REFRESH },
1446 { KEY_F(5), REQ_REFRESH },
1447 { 'O', REQ_MAXIMIZE },
1449 /* Cursor navigation */
1450 { 'k', REQ_MOVE_UP },
1451 { 'j', REQ_MOVE_DOWN },
1452 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1453 { KEY_END, REQ_MOVE_LAST_LINE },
1454 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1455 { ' ', REQ_MOVE_PAGE_DOWN },
1456 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1457 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1458 { 'b', REQ_MOVE_PAGE_UP },
1459 { '-', REQ_MOVE_PAGE_UP },
1461 /* Scrolling */
1462 { '|', REQ_SCROLL_FIRST_COL },
1463 { KEY_LEFT, REQ_SCROLL_LEFT },
1464 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1465 { KEY_IC, REQ_SCROLL_LINE_UP },
1466 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1467 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1468 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1469 { 'w', REQ_SCROLL_PAGE_UP },
1470 { 's', REQ_SCROLL_PAGE_DOWN },
1472 /* Searching */
1473 { '/', REQ_SEARCH },
1474 { '?', REQ_SEARCH_BACK },
1475 { 'n', REQ_FIND_NEXT },
1476 { 'N', REQ_FIND_PREV },
1478 /* Misc */
1479 { 'Q', REQ_QUIT },
1480 { 'z', REQ_STOP_LOADING },
1481 { 'v', REQ_SHOW_VERSION },
1482 { 'r', REQ_SCREEN_REDRAW },
1483 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1484 { 'o', REQ_OPTIONS },
1485 { '.', REQ_TOGGLE_LINENO },
1486 { 'D', REQ_TOGGLE_DATE },
1487 { 'A', REQ_TOGGLE_AUTHOR },
1488 { 'g', REQ_TOGGLE_REV_GRAPH },
1489 { 'F', REQ_TOGGLE_REFS },
1490 { 'I', REQ_TOGGLE_SORT_ORDER },
1491 { 'i', REQ_TOGGLE_SORT_FIELD },
1492 { ':', REQ_PROMPT },
1493 { 'u', REQ_STATUS_UPDATE },
1494 { '!', REQ_STATUS_REVERT },
1495 { 'M', REQ_STATUS_MERGE },
1496 { '@', REQ_STAGE_NEXT },
1497 { ',', REQ_PARENT },
1498 { 'e', REQ_EDIT },
1499 };
1501 #define KEYMAP_INFO \
1502 KEYMAP_(GENERIC), \
1503 KEYMAP_(MAIN), \
1504 KEYMAP_(DIFF), \
1505 KEYMAP_(LOG), \
1506 KEYMAP_(TREE), \
1507 KEYMAP_(BLOB), \
1508 KEYMAP_(BLAME), \
1509 KEYMAP_(BRANCH), \
1510 KEYMAP_(PAGER), \
1511 KEYMAP_(HELP), \
1512 KEYMAP_(STATUS), \
1513 KEYMAP_(STAGE)
1515 enum keymap {
1516 #define KEYMAP_(name) KEYMAP_##name
1517 KEYMAP_INFO
1518 #undef KEYMAP_
1519 };
1521 static const struct enum_map keymap_table[] = {
1522 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1523 KEYMAP_INFO
1524 #undef KEYMAP_
1525 };
1527 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1529 struct keybinding_table {
1530 struct keybinding *data;
1531 size_t size;
1532 };
1534 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1536 static void
1537 add_keybinding(enum keymap keymap, enum request request, int key)
1538 {
1539 struct keybinding_table *table = &keybindings[keymap];
1540 size_t i;
1542 for (i = 0; i < keybindings[keymap].size; i++) {
1543 if (keybindings[keymap].data[i].alias == key) {
1544 keybindings[keymap].data[i].request = request;
1545 return;
1546 }
1547 }
1549 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1550 if (!table->data)
1551 die("Failed to allocate keybinding");
1552 table->data[table->size].alias = key;
1553 table->data[table->size++].request = request;
1555 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1556 int i;
1558 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1559 if (default_keybindings[i].alias == key)
1560 default_keybindings[i].request = REQ_NONE;
1561 }
1562 }
1564 /* Looks for a key binding first in the given map, then in the generic map, and
1565 * lastly in the default keybindings. */
1566 static enum request
1567 get_keybinding(enum keymap keymap, int key)
1568 {
1569 size_t i;
1571 for (i = 0; i < keybindings[keymap].size; i++)
1572 if (keybindings[keymap].data[i].alias == key)
1573 return keybindings[keymap].data[i].request;
1575 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1576 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1577 return keybindings[KEYMAP_GENERIC].data[i].request;
1579 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1580 if (default_keybindings[i].alias == key)
1581 return default_keybindings[i].request;
1583 return (enum request) key;
1584 }
1587 struct key {
1588 const char *name;
1589 int value;
1590 };
1592 static const struct key key_table[] = {
1593 { "Enter", KEY_RETURN },
1594 { "Space", ' ' },
1595 { "Backspace", KEY_BACKSPACE },
1596 { "Tab", KEY_TAB },
1597 { "Escape", KEY_ESC },
1598 { "Left", KEY_LEFT },
1599 { "Right", KEY_RIGHT },
1600 { "Up", KEY_UP },
1601 { "Down", KEY_DOWN },
1602 { "Insert", KEY_IC },
1603 { "Delete", KEY_DC },
1604 { "Hash", '#' },
1605 { "Home", KEY_HOME },
1606 { "End", KEY_END },
1607 { "PageUp", KEY_PPAGE },
1608 { "PageDown", KEY_NPAGE },
1609 { "F1", KEY_F(1) },
1610 { "F2", KEY_F(2) },
1611 { "F3", KEY_F(3) },
1612 { "F4", KEY_F(4) },
1613 { "F5", KEY_F(5) },
1614 { "F6", KEY_F(6) },
1615 { "F7", KEY_F(7) },
1616 { "F8", KEY_F(8) },
1617 { "F9", KEY_F(9) },
1618 { "F10", KEY_F(10) },
1619 { "F11", KEY_F(11) },
1620 { "F12", KEY_F(12) },
1621 };
1623 static int
1624 get_key_value(const char *name)
1625 {
1626 int i;
1628 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1629 if (!strcasecmp(key_table[i].name, name))
1630 return key_table[i].value;
1632 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1633 return (int)name[1] & 0x1f;
1634 if (strlen(name) == 1 && isprint(*name))
1635 return (int) *name;
1636 return ERR;
1637 }
1639 static const char *
1640 get_key_name(int key_value)
1641 {
1642 static char key_char[] = "'X'\0";
1643 const char *seq = NULL;
1644 int key;
1646 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1647 if (key_table[key].value == key_value)
1648 seq = key_table[key].name;
1650 if (seq == NULL && key_value < 0x7f) {
1651 char *s = key_char + 1;
1653 if (key_value >= 0x20) {
1654 *s++ = key_value;
1655 } else {
1656 *s++ = '^';
1657 *s++ = 0x40 | (key_value & 0x1f);
1658 }
1659 *s++ = '\'';
1660 *s++ = '\0';
1661 seq = key_char;
1662 }
1664 return seq ? seq : "(no key)";
1665 }
1667 static bool
1668 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1669 {
1670 const char *sep = *pos > 0 ? ", " : "";
1671 const char *keyname = get_key_name(keybinding->alias);
1673 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1674 }
1676 static bool
1677 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1678 enum keymap keymap, bool all)
1679 {
1680 int i;
1682 for (i = 0; i < keybindings[keymap].size; i++) {
1683 if (keybindings[keymap].data[i].request == request) {
1684 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1685 return FALSE;
1686 if (!all)
1687 break;
1688 }
1689 }
1691 return TRUE;
1692 }
1694 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1696 static const char *
1697 get_keys(enum keymap keymap, enum request request, bool all)
1698 {
1699 static char buf[BUFSIZ];
1700 size_t pos = 0;
1701 int i;
1703 buf[pos] = 0;
1705 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1706 return "Too many keybindings!";
1707 if (pos > 0 && !all)
1708 return buf;
1710 if (keymap != KEYMAP_GENERIC) {
1711 /* Only the generic keymap includes the default keybindings when
1712 * listing all keys. */
1713 if (all)
1714 return buf;
1716 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1717 return "Too many keybindings!";
1718 if (pos)
1719 return buf;
1720 }
1722 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1723 if (default_keybindings[i].request == request) {
1724 if (!append_key(buf, &pos, &default_keybindings[i]))
1725 return "Too many keybindings!";
1726 if (!all)
1727 return buf;
1728 }
1729 }
1731 return buf;
1732 }
1734 struct run_request {
1735 enum keymap keymap;
1736 int key;
1737 const char **argv;
1738 };
1740 static struct run_request *run_request;
1741 static size_t run_requests;
1743 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1745 static enum request
1746 add_run_request(enum keymap keymap, int key, const char **argv)
1747 {
1748 struct run_request *req;
1750 if (!realloc_run_requests(&run_request, run_requests, 1))
1751 return REQ_NONE;
1753 req = &run_request[run_requests];
1754 req->keymap = keymap;
1755 req->key = key;
1756 req->argv = NULL;
1758 if (!argv_copy(&req->argv, argv))
1759 return REQ_NONE;
1761 return REQ_NONE + ++run_requests;
1762 }
1764 static struct run_request *
1765 get_run_request(enum request request)
1766 {
1767 if (request <= REQ_NONE)
1768 return NULL;
1769 return &run_request[request - REQ_NONE - 1];
1770 }
1772 static void
1773 add_builtin_run_requests(void)
1774 {
1775 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1776 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1777 const char *commit[] = { "git", "commit", NULL };
1778 const char *gc[] = { "git", "gc", NULL };
1779 struct run_request reqs[] = {
1780 { KEYMAP_MAIN, 'C', cherry_pick },
1781 { KEYMAP_STATUS, 'C', commit },
1782 { KEYMAP_BRANCH, 'C', checkout },
1783 { KEYMAP_GENERIC, 'G', gc },
1784 };
1785 int i;
1787 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1788 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1790 if (req != reqs[i].key)
1791 continue;
1792 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1793 if (req != REQ_NONE)
1794 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1795 }
1796 }
1798 /*
1799 * User config file handling.
1800 */
1802 static int config_lineno;
1803 static bool config_errors;
1804 static const char *config_msg;
1806 static const struct enum_map color_map[] = {
1807 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1808 COLOR_MAP(DEFAULT),
1809 COLOR_MAP(BLACK),
1810 COLOR_MAP(BLUE),
1811 COLOR_MAP(CYAN),
1812 COLOR_MAP(GREEN),
1813 COLOR_MAP(MAGENTA),
1814 COLOR_MAP(RED),
1815 COLOR_MAP(WHITE),
1816 COLOR_MAP(YELLOW),
1817 };
1819 static const struct enum_map attr_map[] = {
1820 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1821 ATTR_MAP(NORMAL),
1822 ATTR_MAP(BLINK),
1823 ATTR_MAP(BOLD),
1824 ATTR_MAP(DIM),
1825 ATTR_MAP(REVERSE),
1826 ATTR_MAP(STANDOUT),
1827 ATTR_MAP(UNDERLINE),
1828 };
1830 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1832 static int parse_step(double *opt, const char *arg)
1833 {
1834 *opt = atoi(arg);
1835 if (!strchr(arg, '%'))
1836 return OK;
1838 /* "Shift down" so 100% and 1 does not conflict. */
1839 *opt = (*opt - 1) / 100;
1840 if (*opt >= 1.0) {
1841 *opt = 0.99;
1842 config_msg = "Step value larger than 100%";
1843 return ERR;
1844 }
1845 if (*opt < 0.0) {
1846 *opt = 1;
1847 config_msg = "Invalid step value";
1848 return ERR;
1849 }
1850 return OK;
1851 }
1853 static int
1854 parse_int(int *opt, const char *arg, int min, int max)
1855 {
1856 int value = atoi(arg);
1858 if (min <= value && value <= max) {
1859 *opt = value;
1860 return OK;
1861 }
1863 config_msg = "Integer value out of bound";
1864 return ERR;
1865 }
1867 static bool
1868 set_color(int *color, const char *name)
1869 {
1870 if (map_enum(color, color_map, name))
1871 return TRUE;
1872 if (!prefixcmp(name, "color"))
1873 return parse_int(color, name + 5, 0, 255) == OK;
1874 return FALSE;
1875 }
1877 /* Wants: object fgcolor bgcolor [attribute] */
1878 static int
1879 option_color_command(int argc, const char *argv[])
1880 {
1881 struct line_info *info;
1883 if (argc < 3) {
1884 config_msg = "Wrong number of arguments given to color command";
1885 return ERR;
1886 }
1888 info = get_line_info(argv[0]);
1889 if (!info) {
1890 static const struct enum_map obsolete[] = {
1891 ENUM_MAP("main-delim", LINE_DELIMITER),
1892 ENUM_MAP("main-date", LINE_DATE),
1893 ENUM_MAP("main-author", LINE_AUTHOR),
1894 };
1895 int index;
1897 if (!map_enum(&index, obsolete, argv[0])) {
1898 config_msg = "Unknown color name";
1899 return ERR;
1900 }
1901 info = &line_info[index];
1902 }
1904 if (!set_color(&info->fg, argv[1]) ||
1905 !set_color(&info->bg, argv[2])) {
1906 config_msg = "Unknown color";
1907 return ERR;
1908 }
1910 info->attr = 0;
1911 while (argc-- > 3) {
1912 int attr;
1914 if (!set_attribute(&attr, argv[argc])) {
1915 config_msg = "Unknown attribute";
1916 return ERR;
1917 }
1918 info->attr |= attr;
1919 }
1921 return OK;
1922 }
1924 static int parse_bool(bool *opt, const char *arg)
1925 {
1926 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1927 ? TRUE : FALSE;
1928 return OK;
1929 }
1931 static int parse_enum_do(unsigned int *opt, const char *arg,
1932 const struct enum_map *map, size_t map_size)
1933 {
1934 bool is_true;
1936 assert(map_size > 1);
1938 if (map_enum_do(map, map_size, (int *) opt, arg))
1939 return OK;
1941 if (parse_bool(&is_true, arg) != OK)
1942 return ERR;
1944 *opt = is_true ? map[1].value : map[0].value;
1945 return OK;
1946 }
1948 #define parse_enum(opt, arg, map) \
1949 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1951 static int
1952 parse_string(char *opt, const char *arg, size_t optsize)
1953 {
1954 int arglen = strlen(arg);
1956 switch (arg[0]) {
1957 case '\"':
1958 case '\'':
1959 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1960 config_msg = "Unmatched quotation";
1961 return ERR;
1962 }
1963 arg += 1; arglen -= 2;
1964 default:
1965 string_ncopy_do(opt, optsize, arg, arglen);
1966 return OK;
1967 }
1968 }
1970 /* Wants: name = value */
1971 static int
1972 option_set_command(int argc, const char *argv[])
1973 {
1974 if (argc != 3) {
1975 config_msg = "Wrong number of arguments given to set command";
1976 return ERR;
1977 }
1979 if (strcmp(argv[1], "=")) {
1980 config_msg = "No value assigned";
1981 return ERR;
1982 }
1984 if (!strcmp(argv[0], "show-author"))
1985 return parse_enum(&opt_author, argv[2], author_map);
1987 if (!strcmp(argv[0], "show-date"))
1988 return parse_enum(&opt_date, argv[2], date_map);
1990 if (!strcmp(argv[0], "show-rev-graph"))
1991 return parse_bool(&opt_rev_graph, argv[2]);
1993 if (!strcmp(argv[0], "show-refs"))
1994 return parse_bool(&opt_show_refs, argv[2]);
1996 if (!strcmp(argv[0], "show-line-numbers"))
1997 return parse_bool(&opt_line_number, argv[2]);
1999 if (!strcmp(argv[0], "line-graphics"))
2000 return parse_bool(&opt_line_graphics, argv[2]);
2002 if (!strcmp(argv[0], "line-number-interval"))
2003 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2005 if (!strcmp(argv[0], "author-width"))
2006 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2008 if (!strcmp(argv[0], "horizontal-scroll"))
2009 return parse_step(&opt_hscroll, argv[2]);
2011 if (!strcmp(argv[0], "split-view-height"))
2012 return parse_step(&opt_scale_split_view, argv[2]);
2014 if (!strcmp(argv[0], "tab-size"))
2015 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2017 if (!strcmp(argv[0], "commit-encoding"))
2018 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2020 config_msg = "Unknown variable name";
2021 return ERR;
2022 }
2024 /* Wants: mode request key */
2025 static int
2026 option_bind_command(int argc, const char *argv[])
2027 {
2028 enum request request;
2029 int keymap = -1;
2030 int key;
2032 if (argc < 3) {
2033 config_msg = "Wrong number of arguments given to bind command";
2034 return ERR;
2035 }
2037 if (!set_keymap(&keymap, argv[0])) {
2038 config_msg = "Unknown key map";
2039 return ERR;
2040 }
2042 key = get_key_value(argv[1]);
2043 if (key == ERR) {
2044 config_msg = "Unknown key";
2045 return ERR;
2046 }
2048 request = get_request(argv[2]);
2049 if (request == REQ_UNKNOWN) {
2050 static const struct enum_map obsolete[] = {
2051 ENUM_MAP("cherry-pick", REQ_NONE),
2052 ENUM_MAP("screen-resize", REQ_NONE),
2053 ENUM_MAP("tree-parent", REQ_PARENT),
2054 };
2055 int alias;
2057 if (map_enum(&alias, obsolete, argv[2])) {
2058 if (alias != REQ_NONE)
2059 add_keybinding(keymap, alias, key);
2060 config_msg = "Obsolete request name";
2061 return ERR;
2062 }
2063 }
2064 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2065 request = add_run_request(keymap, key, argv + 2);
2066 if (request == REQ_UNKNOWN) {
2067 config_msg = "Unknown request name";
2068 return ERR;
2069 }
2071 add_keybinding(keymap, request, key);
2073 return OK;
2074 }
2076 static int
2077 set_option(const char *opt, char *value)
2078 {
2079 const char *argv[SIZEOF_ARG];
2080 int argc = 0;
2082 if (!argv_from_string(argv, &argc, value)) {
2083 config_msg = "Too many option arguments";
2084 return ERR;
2085 }
2087 if (!strcmp(opt, "color"))
2088 return option_color_command(argc, argv);
2090 if (!strcmp(opt, "set"))
2091 return option_set_command(argc, argv);
2093 if (!strcmp(opt, "bind"))
2094 return option_bind_command(argc, argv);
2096 config_msg = "Unknown option command";
2097 return ERR;
2098 }
2100 static int
2101 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2102 {
2103 int status = OK;
2105 config_lineno++;
2106 config_msg = "Internal error";
2108 /* Check for comment markers, since read_properties() will
2109 * only ensure opt and value are split at first " \t". */
2110 optlen = strcspn(opt, "#");
2111 if (optlen == 0)
2112 return OK;
2114 if (opt[optlen] != 0) {
2115 config_msg = "No option value";
2116 status = ERR;
2118 } else {
2119 /* Look for comment endings in the value. */
2120 size_t len = strcspn(value, "#");
2122 if (len < valuelen) {
2123 valuelen = len;
2124 value[valuelen] = 0;
2125 }
2127 status = set_option(opt, value);
2128 }
2130 if (status == ERR) {
2131 warn("Error on line %d, near '%.*s': %s",
2132 config_lineno, (int) optlen, opt, config_msg);
2133 config_errors = TRUE;
2134 }
2136 /* Always keep going if errors are encountered. */
2137 return OK;
2138 }
2140 static void
2141 load_option_file(const char *path)
2142 {
2143 struct io io;
2145 /* It's OK that the file doesn't exist. */
2146 if (!io_open(&io, "%s", path))
2147 return;
2149 config_lineno = 0;
2150 config_errors = FALSE;
2152 if (io_load(&io, " \t", read_option) == ERR ||
2153 config_errors == TRUE)
2154 warn("Errors while loading %s.", path);
2155 }
2157 static int
2158 load_options(void)
2159 {
2160 const char *home = getenv("HOME");
2161 const char *tigrc_user = getenv("TIGRC_USER");
2162 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2163 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2164 char buf[SIZEOF_STR];
2166 if (!tigrc_system)
2167 tigrc_system = SYSCONFDIR "/tigrc";
2168 load_option_file(tigrc_system);
2170 if (!tigrc_user) {
2171 if (!home || !string_format(buf, "%s/.tigrc", home))
2172 return ERR;
2173 tigrc_user = buf;
2174 }
2175 load_option_file(tigrc_user);
2177 /* Add _after_ loading config files to avoid adding run requests
2178 * that conflict with keybindings. */
2179 add_builtin_run_requests();
2181 if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2182 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2183 int argc = 0;
2185 if (!string_format(buf, "%s", tig_diff_opts) ||
2186 !argv_from_string(diff_opts, &argc, buf))
2187 die("TIG_DIFF_OPTS contains too many arguments");
2188 else if (!argv_copy(&opt_diff_args, diff_opts))
2189 die("Failed to format TIG_DIFF_OPTS arguments");
2190 }
2192 return OK;
2193 }
2196 /*
2197 * The viewer
2198 */
2200 struct view;
2201 struct view_ops;
2203 /* The display array of active views and the index of the current view. */
2204 static struct view *display[2];
2205 static unsigned int current_view;
2207 #define foreach_displayed_view(view, i) \
2208 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2210 #define displayed_views() (display[1] != NULL ? 2 : 1)
2212 /* Current head and commit ID */
2213 static char ref_blob[SIZEOF_REF] = "";
2214 static char ref_commit[SIZEOF_REF] = "HEAD";
2215 static char ref_head[SIZEOF_REF] = "HEAD";
2216 static char ref_branch[SIZEOF_REF] = "";
2218 enum view_type {
2219 VIEW_MAIN,
2220 VIEW_DIFF,
2221 VIEW_LOG,
2222 VIEW_TREE,
2223 VIEW_BLOB,
2224 VIEW_BLAME,
2225 VIEW_BRANCH,
2226 VIEW_HELP,
2227 VIEW_PAGER,
2228 VIEW_STATUS,
2229 VIEW_STAGE,
2230 };
2232 struct view {
2233 enum view_type type; /* View type */
2234 const char *name; /* View name */
2235 const char *cmd_env; /* Command line set via environment */
2236 const char *id; /* Points to either of ref_{head,commit,blob} */
2238 struct view_ops *ops; /* View operations */
2240 enum keymap keymap; /* What keymap does this view have */
2241 bool git_dir; /* Whether the view requires a git directory. */
2243 char ref[SIZEOF_REF]; /* Hovered commit reference */
2244 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2246 int height, width; /* The width and height of the main window */
2247 WINDOW *win; /* The main window */
2248 WINDOW *title; /* The title window living below the main window */
2250 /* Navigation */
2251 unsigned long offset; /* Offset of the window top */
2252 unsigned long yoffset; /* Offset from the window side. */
2253 unsigned long lineno; /* Current line number */
2254 unsigned long p_offset; /* Previous offset of the window top */
2255 unsigned long p_yoffset;/* Previous offset from the window side */
2256 unsigned long p_lineno; /* Previous current line number */
2257 bool p_restore; /* Should the previous position be restored. */
2259 /* Searching */
2260 char grep[SIZEOF_STR]; /* Search string */
2261 regex_t *regex; /* Pre-compiled regexp */
2263 /* If non-NULL, points to the view that opened this view. If this view
2264 * is closed tig will switch back to the parent view. */
2265 struct view *parent;
2266 struct view *prev;
2268 /* Buffering */
2269 size_t lines; /* Total number of lines */
2270 struct line *line; /* Line index */
2271 unsigned int digits; /* Number of digits in the lines member. */
2273 /* Drawing */
2274 struct line *curline; /* Line currently being drawn. */
2275 enum line_type curtype; /* Attribute currently used for drawing. */
2276 unsigned long col; /* Column when drawing. */
2277 bool has_scrolled; /* View was scrolled. */
2279 /* Loading */
2280 const char **argv; /* Shell command arguments. */
2281 const char *dir; /* Directory from which to execute. */
2282 struct io io;
2283 struct io *pipe;
2284 time_t start_time;
2285 time_t update_secs;
2286 };
2288 struct view_ops {
2289 /* What type of content being displayed. Used in the title bar. */
2290 const char *type;
2291 /* Default command arguments. */
2292 const char **argv;
2293 /* Open and reads in all view content. */
2294 bool (*open)(struct view *view);
2295 /* Read one line; updates view->line. */
2296 bool (*read)(struct view *view, char *data);
2297 /* Draw one line; @lineno must be < view->height. */
2298 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2299 /* Depending on view handle a special requests. */
2300 enum request (*request)(struct view *view, enum request request, struct line *line);
2301 /* Search for regexp in a line. */
2302 bool (*grep)(struct view *view, struct line *line);
2303 /* Select line */
2304 void (*select)(struct view *view, struct line *line);
2305 /* Prepare view for loading */
2306 bool (*prepare)(struct view *view);
2307 };
2309 static struct view_ops blame_ops;
2310 static struct view_ops blob_ops;
2311 static struct view_ops diff_ops;
2312 static struct view_ops help_ops;
2313 static struct view_ops log_ops;
2314 static struct view_ops main_ops;
2315 static struct view_ops pager_ops;
2316 static struct view_ops stage_ops;
2317 static struct view_ops status_ops;
2318 static struct view_ops tree_ops;
2319 static struct view_ops branch_ops;
2321 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2322 { type, name, #env, ref, ops, map, git }
2324 #define VIEW_(id, name, ops, git, ref) \
2325 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2327 static struct view views[] = {
2328 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2329 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2330 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2331 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2332 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2333 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2334 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2335 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2336 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2337 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2338 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2339 };
2341 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2343 #define foreach_view(view, i) \
2344 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2346 #define view_is_displayed(view) \
2347 (view == display[0] || view == display[1])
2349 static enum request
2350 view_request(struct view *view, enum request request)
2351 {
2352 if (!view || !view->lines)
2353 return request;
2354 return view->ops->request(view, request, &view->line[view->lineno]);
2355 }
2358 /*
2359 * View drawing.
2360 */
2362 static inline void
2363 set_view_attr(struct view *view, enum line_type type)
2364 {
2365 if (!view->curline->selected && view->curtype != type) {
2366 (void) wattrset(view->win, get_line_attr(type));
2367 wchgat(view->win, -1, 0, type, NULL);
2368 view->curtype = type;
2369 }
2370 }
2372 static int
2373 draw_chars(struct view *view, enum line_type type, const char *string,
2374 int max_len, bool use_tilde)
2375 {
2376 static char out_buffer[BUFSIZ * 2];
2377 int len = 0;
2378 int col = 0;
2379 int trimmed = FALSE;
2380 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2382 if (max_len <= 0)
2383 return 0;
2385 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2387 set_view_attr(view, type);
2388 if (len > 0) {
2389 if (opt_iconv_out != ICONV_NONE) {
2390 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2391 size_t inlen = len + 1;
2393 char *outbuf = out_buffer;
2394 size_t outlen = sizeof(out_buffer);
2396 size_t ret;
2398 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2399 if (ret != (size_t) -1) {
2400 string = out_buffer;
2401 len = sizeof(out_buffer) - outlen;
2402 }
2403 }
2405 waddnstr(view->win, string, len);
2406 }
2407 if (trimmed && use_tilde) {
2408 set_view_attr(view, LINE_DELIMITER);
2409 waddch(view->win, '~');
2410 col++;
2411 }
2413 return col;
2414 }
2416 static int
2417 draw_space(struct view *view, enum line_type type, int max, int spaces)
2418 {
2419 static char space[] = " ";
2420 int col = 0;
2422 spaces = MIN(max, spaces);
2424 while (spaces > 0) {
2425 int len = MIN(spaces, sizeof(space) - 1);
2427 col += draw_chars(view, type, space, len, FALSE);
2428 spaces -= len;
2429 }
2431 return col;
2432 }
2434 static bool
2435 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2436 {
2437 char text[SIZEOF_STR];
2439 do {
2440 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2442 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2443 string += pos;
2444 } while (*string && view->width + view->yoffset > view->col);
2446 return view->width + view->yoffset <= view->col;
2447 }
2449 static bool
2450 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2451 {
2452 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2453 int max = view->width + view->yoffset - view->col;
2454 int i;
2456 if (max < size)
2457 size = max;
2459 set_view_attr(view, type);
2460 /* Using waddch() instead of waddnstr() ensures that
2461 * they'll be rendered correctly for the cursor line. */
2462 for (i = skip; i < size; i++)
2463 waddch(view->win, graphic[i]);
2465 view->col += size;
2466 if (size < max && skip <= size)
2467 waddch(view->win, ' ');
2468 view->col++;
2470 return view->width + view->yoffset <= view->col;
2471 }
2473 static bool
2474 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2475 {
2476 int max = MIN(view->width + view->yoffset - view->col, len);
2477 int col;
2479 if (text)
2480 col = draw_chars(view, type, text, max - 1, trim);
2481 else
2482 col = draw_space(view, type, max - 1, max - 1);
2484 view->col += col;
2485 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2486 return view->width + view->yoffset <= view->col;
2487 }
2489 static bool
2490 draw_date(struct view *view, struct time *time)
2491 {
2492 const char *date = mkdate(time, opt_date);
2493 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2495 return draw_field(view, LINE_DATE, date, cols, FALSE);
2496 }
2498 static bool
2499 draw_author(struct view *view, const char *author)
2500 {
2501 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2502 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2504 if (abbreviate && author)
2505 author = get_author_initials(author);
2507 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2508 }
2510 static bool
2511 draw_mode(struct view *view, mode_t mode)
2512 {
2513 const char *str;
2515 if (S_ISDIR(mode))
2516 str = "drwxr-xr-x";
2517 else if (S_ISLNK(mode))
2518 str = "lrwxrwxrwx";
2519 else if (S_ISGITLINK(mode))
2520 str = "m---------";
2521 else if (S_ISREG(mode) && mode & S_IXUSR)
2522 str = "-rwxr-xr-x";
2523 else if (S_ISREG(mode))
2524 str = "-rw-r--r--";
2525 else
2526 str = "----------";
2528 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2529 }
2531 static bool
2532 draw_lineno(struct view *view, unsigned int lineno)
2533 {
2534 char number[10];
2535 int digits3 = view->digits < 3 ? 3 : view->digits;
2536 int max = MIN(view->width + view->yoffset - view->col, digits3);
2537 char *text = NULL;
2538 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2540 lineno += view->offset + 1;
2541 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2542 static char fmt[] = "%1ld";
2544 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2545 if (string_format(number, fmt, lineno))
2546 text = number;
2547 }
2548 if (text)
2549 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2550 else
2551 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2552 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2553 }
2555 static bool
2556 draw_view_line(struct view *view, unsigned int lineno)
2557 {
2558 struct line *line;
2559 bool selected = (view->offset + lineno == view->lineno);
2561 assert(view_is_displayed(view));
2563 if (view->offset + lineno >= view->lines)
2564 return FALSE;
2566 line = &view->line[view->offset + lineno];
2568 wmove(view->win, lineno, 0);
2569 if (line->cleareol)
2570 wclrtoeol(view->win);
2571 view->col = 0;
2572 view->curline = line;
2573 view->curtype = LINE_NONE;
2574 line->selected = FALSE;
2575 line->dirty = line->cleareol = 0;
2577 if (selected) {
2578 set_view_attr(view, LINE_CURSOR);
2579 line->selected = TRUE;
2580 view->ops->select(view, line);
2581 }
2583 return view->ops->draw(view, line, lineno);
2584 }
2586 static void
2587 redraw_view_dirty(struct view *view)
2588 {
2589 bool dirty = FALSE;
2590 int lineno;
2592 for (lineno = 0; lineno < view->height; lineno++) {
2593 if (view->offset + lineno >= view->lines)
2594 break;
2595 if (!view->line[view->offset + lineno].dirty)
2596 continue;
2597 dirty = TRUE;
2598 if (!draw_view_line(view, lineno))
2599 break;
2600 }
2602 if (!dirty)
2603 return;
2604 wnoutrefresh(view->win);
2605 }
2607 static void
2608 redraw_view_from(struct view *view, int lineno)
2609 {
2610 assert(0 <= lineno && lineno < view->height);
2612 for (; lineno < view->height; lineno++) {
2613 if (!draw_view_line(view, lineno))
2614 break;
2615 }
2617 wnoutrefresh(view->win);
2618 }
2620 static void
2621 redraw_view(struct view *view)
2622 {
2623 werase(view->win);
2624 redraw_view_from(view, 0);
2625 }
2628 static void
2629 update_view_title(struct view *view)
2630 {
2631 char buf[SIZEOF_STR];
2632 char state[SIZEOF_STR];
2633 size_t bufpos = 0, statelen = 0;
2635 assert(view_is_displayed(view));
2637 if (view->type != VIEW_STATUS && view->lines) {
2638 unsigned int view_lines = view->offset + view->height;
2639 unsigned int lines = view->lines
2640 ? MIN(view_lines, view->lines) * 100 / view->lines
2641 : 0;
2643 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2644 view->ops->type,
2645 view->lineno + 1,
2646 view->lines,
2647 lines);
2649 }
2651 if (view->pipe) {
2652 time_t secs = time(NULL) - view->start_time;
2654 /* Three git seconds are a long time ... */
2655 if (secs > 2)
2656 string_format_from(state, &statelen, " loading %lds", secs);
2657 }
2659 string_format_from(buf, &bufpos, "[%s]", view->name);
2660 if (*view->ref && bufpos < view->width) {
2661 size_t refsize = strlen(view->ref);
2662 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2664 if (minsize < view->width)
2665 refsize = view->width - minsize + 7;
2666 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2667 }
2669 if (statelen && bufpos < view->width) {
2670 string_format_from(buf, &bufpos, "%s", state);
2671 }
2673 if (view == display[current_view])
2674 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2675 else
2676 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2678 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2679 wclrtoeol(view->title);
2680 wnoutrefresh(view->title);
2681 }
2683 static int
2684 apply_step(double step, int value)
2685 {
2686 if (step >= 1)
2687 return (int) step;
2688 value *= step + 0.01;
2689 return value ? value : 1;
2690 }
2692 static void
2693 resize_display(void)
2694 {
2695 int offset, i;
2696 struct view *base = display[0];
2697 struct view *view = display[1] ? display[1] : display[0];
2699 /* Setup window dimensions */
2701 getmaxyx(stdscr, base->height, base->width);
2703 /* Make room for the status window. */
2704 base->height -= 1;
2706 if (view != base) {
2707 /* Horizontal split. */
2708 view->width = base->width;
2709 view->height = apply_step(opt_scale_split_view, base->height);
2710 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2711 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2712 base->height -= view->height;
2714 /* Make room for the title bar. */
2715 view->height -= 1;
2716 }
2718 /* Make room for the title bar. */
2719 base->height -= 1;
2721 offset = 0;
2723 foreach_displayed_view (view, i) {
2724 if (!view->win) {
2725 view->win = newwin(view->height, 0, offset, 0);
2726 if (!view->win)
2727 die("Failed to create %s view", view->name);
2729 scrollok(view->win, FALSE);
2731 view->title = newwin(1, 0, offset + view->height, 0);
2732 if (!view->title)
2733 die("Failed to create title window");
2735 } else {
2736 wresize(view->win, view->height, view->width);
2737 mvwin(view->win, offset, 0);
2738 mvwin(view->title, offset + view->height, 0);
2739 }
2741 offset += view->height + 1;
2742 }
2743 }
2745 static void
2746 redraw_display(bool clear)
2747 {
2748 struct view *view;
2749 int i;
2751 foreach_displayed_view (view, i) {
2752 if (clear)
2753 wclear(view->win);
2754 redraw_view(view);
2755 update_view_title(view);
2756 }
2757 }
2760 /*
2761 * Option management
2762 */
2764 static void
2765 toggle_enum_option_do(unsigned int *opt, const char *help,
2766 const struct enum_map *map, size_t size)
2767 {
2768 *opt = (*opt + 1) % size;
2769 redraw_display(FALSE);
2770 report("Displaying %s %s", enum_name(map[*opt]), help);
2771 }
2773 #define toggle_enum_option(opt, help, map) \
2774 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2776 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2777 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2779 static void
2780 toggle_view_option(bool *option, const char *help)
2781 {
2782 *option = !*option;
2783 redraw_display(FALSE);
2784 report("%sabling %s", *option ? "En" : "Dis", help);
2785 }
2787 static void
2788 open_option_menu(void)
2789 {
2790 const struct menu_item menu[] = {
2791 { '.', "line numbers", &opt_line_number },
2792 { 'D', "date display", &opt_date },
2793 { 'A', "author display", &opt_author },
2794 { 'g', "revision graph display", &opt_rev_graph },
2795 { 'F', "reference display", &opt_show_refs },
2796 { 0 }
2797 };
2798 int selected = 0;
2800 if (prompt_menu("Toggle option", menu, &selected)) {
2801 if (menu[selected].data == &opt_date)
2802 toggle_date();
2803 else if (menu[selected].data == &opt_author)
2804 toggle_author();
2805 else
2806 toggle_view_option(menu[selected].data, menu[selected].text);
2807 }
2808 }
2810 static void
2811 maximize_view(struct view *view)
2812 {
2813 memset(display, 0, sizeof(display));
2814 current_view = 0;
2815 display[current_view] = view;
2816 resize_display();
2817 redraw_display(FALSE);
2818 report("");
2819 }
2822 /*
2823 * Navigation
2824 */
2826 static bool
2827 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2828 {
2829 if (lineno >= view->lines)
2830 lineno = view->lines > 0 ? view->lines - 1 : 0;
2832 if (offset > lineno || offset + view->height <= lineno) {
2833 unsigned long half = view->height / 2;
2835 if (lineno > half)
2836 offset = lineno - half;
2837 else
2838 offset = 0;
2839 }
2841 if (offset != view->offset || lineno != view->lineno) {
2842 view->offset = offset;
2843 view->lineno = lineno;
2844 return TRUE;
2845 }
2847 return FALSE;
2848 }
2850 /* Scrolling backend */
2851 static void
2852 do_scroll_view(struct view *view, int lines)
2853 {
2854 bool redraw_current_line = FALSE;
2856 /* The rendering expects the new offset. */
2857 view->offset += lines;
2859 assert(0 <= view->offset && view->offset < view->lines);
2860 assert(lines);
2862 /* Move current line into the view. */
2863 if (view->lineno < view->offset) {
2864 view->lineno = view->offset;
2865 redraw_current_line = TRUE;
2866 } else if (view->lineno >= view->offset + view->height) {
2867 view->lineno = view->offset + view->height - 1;
2868 redraw_current_line = TRUE;
2869 }
2871 assert(view->offset <= view->lineno && view->lineno < view->lines);
2873 /* Redraw the whole screen if scrolling is pointless. */
2874 if (view->height < ABS(lines)) {
2875 redraw_view(view);
2877 } else {
2878 int line = lines > 0 ? view->height - lines : 0;
2879 int end = line + ABS(lines);
2881 scrollok(view->win, TRUE);
2882 wscrl(view->win, lines);
2883 scrollok(view->win, FALSE);
2885 while (line < end && draw_view_line(view, line))
2886 line++;
2888 if (redraw_current_line)
2889 draw_view_line(view, view->lineno - view->offset);
2890 wnoutrefresh(view->win);
2891 }
2893 view->has_scrolled = TRUE;
2894 report("");
2895 }
2897 /* Scroll frontend */
2898 static void
2899 scroll_view(struct view *view, enum request request)
2900 {
2901 int lines = 1;
2903 assert(view_is_displayed(view));
2905 switch (request) {
2906 case REQ_SCROLL_FIRST_COL:
2907 view->yoffset = 0;
2908 redraw_view_from(view, 0);
2909 report("");
2910 return;
2911 case REQ_SCROLL_LEFT:
2912 if (view->yoffset == 0) {
2913 report("Cannot scroll beyond the first column");
2914 return;
2915 }
2916 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2917 view->yoffset = 0;
2918 else
2919 view->yoffset -= apply_step(opt_hscroll, view->width);
2920 redraw_view_from(view, 0);
2921 report("");
2922 return;
2923 case REQ_SCROLL_RIGHT:
2924 view->yoffset += apply_step(opt_hscroll, view->width);
2925 redraw_view(view);
2926 report("");
2927 return;
2928 case REQ_SCROLL_PAGE_DOWN:
2929 lines = view->height;
2930 case REQ_SCROLL_LINE_DOWN:
2931 if (view->offset + lines > view->lines)
2932 lines = view->lines - view->offset;
2934 if (lines == 0 || view->offset + view->height >= view->lines) {
2935 report("Cannot scroll beyond the last line");
2936 return;
2937 }
2938 break;
2940 case REQ_SCROLL_PAGE_UP:
2941 lines = view->height;
2942 case REQ_SCROLL_LINE_UP:
2943 if (lines > view->offset)
2944 lines = view->offset;
2946 if (lines == 0) {
2947 report("Cannot scroll beyond the first line");
2948 return;
2949 }
2951 lines = -lines;
2952 break;
2954 default:
2955 die("request %d not handled in switch", request);
2956 }
2958 do_scroll_view(view, lines);
2959 }
2961 /* Cursor moving */
2962 static void
2963 move_view(struct view *view, enum request request)
2964 {
2965 int scroll_steps = 0;
2966 int steps;
2968 switch (request) {
2969 case REQ_MOVE_FIRST_LINE:
2970 steps = -view->lineno;
2971 break;
2973 case REQ_MOVE_LAST_LINE:
2974 steps = view->lines - view->lineno - 1;
2975 break;
2977 case REQ_MOVE_PAGE_UP:
2978 steps = view->height > view->lineno
2979 ? -view->lineno : -view->height;
2980 break;
2982 case REQ_MOVE_PAGE_DOWN:
2983 steps = view->lineno + view->height >= view->lines
2984 ? view->lines - view->lineno - 1 : view->height;
2985 break;
2987 case REQ_MOVE_UP:
2988 steps = -1;
2989 break;
2991 case REQ_MOVE_DOWN:
2992 steps = 1;
2993 break;
2995 default:
2996 die("request %d not handled in switch", request);
2997 }
2999 if (steps <= 0 && view->lineno == 0) {
3000 report("Cannot move beyond the first line");
3001 return;
3003 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3004 report("Cannot move beyond the last line");
3005 return;
3006 }
3008 /* Move the current line */
3009 view->lineno += steps;
3010 assert(0 <= view->lineno && view->lineno < view->lines);
3012 /* Check whether the view needs to be scrolled */
3013 if (view->lineno < view->offset ||
3014 view->lineno >= view->offset + view->height) {
3015 scroll_steps = steps;
3016 if (steps < 0 && -steps > view->offset) {
3017 scroll_steps = -view->offset;
3019 } else if (steps > 0) {
3020 if (view->lineno == view->lines - 1 &&
3021 view->lines > view->height) {
3022 scroll_steps = view->lines - view->offset - 1;
3023 if (scroll_steps >= view->height)
3024 scroll_steps -= view->height - 1;
3025 }
3026 }
3027 }
3029 if (!view_is_displayed(view)) {
3030 view->offset += scroll_steps;
3031 assert(0 <= view->offset && view->offset < view->lines);
3032 view->ops->select(view, &view->line[view->lineno]);
3033 return;
3034 }
3036 /* Repaint the old "current" line if we be scrolling */
3037 if (ABS(steps) < view->height)
3038 draw_view_line(view, view->lineno - steps - view->offset);
3040 if (scroll_steps) {
3041 do_scroll_view(view, scroll_steps);
3042 return;
3043 }
3045 /* Draw the current line */
3046 draw_view_line(view, view->lineno - view->offset);
3048 wnoutrefresh(view->win);
3049 report("");
3050 }
3053 /*
3054 * Searching
3055 */
3057 static void search_view(struct view *view, enum request request);
3059 static bool
3060 grep_text(struct view *view, const char *text[])
3061 {
3062 regmatch_t pmatch;
3063 size_t i;
3065 for (i = 0; text[i]; i++)
3066 if (*text[i] &&
3067 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3068 return TRUE;
3069 return FALSE;
3070 }
3072 static void
3073 select_view_line(struct view *view, unsigned long lineno)
3074 {
3075 unsigned long old_lineno = view->lineno;
3076 unsigned long old_offset = view->offset;
3078 if (goto_view_line(view, view->offset, lineno)) {
3079 if (view_is_displayed(view)) {
3080 if (old_offset != view->offset) {
3081 redraw_view(view);
3082 } else {
3083 draw_view_line(view, old_lineno - view->offset);
3084 draw_view_line(view, view->lineno - view->offset);
3085 wnoutrefresh(view->win);
3086 }
3087 } else {
3088 view->ops->select(view, &view->line[view->lineno]);
3089 }
3090 }
3091 }
3093 static void
3094 find_next(struct view *view, enum request request)
3095 {
3096 unsigned long lineno = view->lineno;
3097 int direction;
3099 if (!*view->grep) {
3100 if (!*opt_search)
3101 report("No previous search");
3102 else
3103 search_view(view, request);
3104 return;
3105 }
3107 switch (request) {
3108 case REQ_SEARCH:
3109 case REQ_FIND_NEXT:
3110 direction = 1;
3111 break;
3113 case REQ_SEARCH_BACK:
3114 case REQ_FIND_PREV:
3115 direction = -1;
3116 break;
3118 default:
3119 return;
3120 }
3122 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3123 lineno += direction;
3125 /* Note, lineno is unsigned long so will wrap around in which case it
3126 * will become bigger than view->lines. */
3127 for (; lineno < view->lines; lineno += direction) {
3128 if (view->ops->grep(view, &view->line[lineno])) {
3129 select_view_line(view, lineno);
3130 report("Line %ld matches '%s'", lineno + 1, view->grep);
3131 return;
3132 }
3133 }
3135 report("No match found for '%s'", view->grep);
3136 }
3138 static void
3139 search_view(struct view *view, enum request request)
3140 {
3141 int regex_err;
3143 if (view->regex) {
3144 regfree(view->regex);
3145 *view->grep = 0;
3146 } else {
3147 view->regex = calloc(1, sizeof(*view->regex));
3148 if (!view->regex)
3149 return;
3150 }
3152 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3153 if (regex_err != 0) {
3154 char buf[SIZEOF_STR] = "unknown error";
3156 regerror(regex_err, view->regex, buf, sizeof(buf));
3157 report("Search failed: %s", buf);
3158 return;
3159 }
3161 string_copy(view->grep, opt_search);
3163 find_next(view, request);
3164 }
3166 /*
3167 * Incremental updating
3168 */
3170 static void
3171 reset_view(struct view *view)
3172 {
3173 int i;
3175 for (i = 0; i < view->lines; i++)
3176 free(view->line[i].data);
3177 free(view->line);
3179 view->p_offset = view->offset;
3180 view->p_yoffset = view->yoffset;
3181 view->p_lineno = view->lineno;
3183 view->line = NULL;
3184 view->offset = 0;
3185 view->yoffset = 0;
3186 view->lines = 0;
3187 view->lineno = 0;
3188 view->vid[0] = 0;
3189 view->update_secs = 0;
3190 }
3192 static const char *
3193 format_arg(const char *name)
3194 {
3195 static struct {
3196 const char *name;
3197 size_t namelen;
3198 const char *value;
3199 const char *value_if_empty;
3200 } vars[] = {
3201 #define FORMAT_VAR(name, value, value_if_empty) \
3202 { name, STRING_SIZE(name), value, value_if_empty }
3203 FORMAT_VAR("%(directory)", opt_path, ""),
3204 FORMAT_VAR("%(file)", opt_file, ""),
3205 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3206 FORMAT_VAR("%(head)", ref_head, ""),
3207 FORMAT_VAR("%(commit)", ref_commit, ""),
3208 FORMAT_VAR("%(blob)", ref_blob, ""),
3209 FORMAT_VAR("%(branch)", ref_branch, ""),
3210 };
3211 int i;
3213 for (i = 0; i < ARRAY_SIZE(vars); i++)
3214 if (!strncmp(name, vars[i].name, vars[i].namelen))
3215 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3217 report("Unknown replacement: `%s`", name);
3218 return NULL;
3219 }
3221 static bool
3222 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3223 {
3224 char buf[SIZEOF_STR];
3225 int argc;
3227 argv_free(*dst_argv);
3229 for (argc = 0; src_argv[argc]; argc++) {
3230 const char *arg = src_argv[argc];
3231 size_t bufpos = 0;
3233 if (!strcmp(arg, "%(fileargs)")) {
3234 if (!argv_append_array(dst_argv, opt_file_args))
3235 break;
3236 continue;
3238 } else if (!strcmp(arg, "%(diffargs)")) {
3239 if (!argv_append_array(dst_argv, opt_diff_args))
3240 break;
3241 continue;
3243 } else if (!strcmp(arg, "%(revargs)")) {
3244 if (!argv_append_array(dst_argv, opt_rev_args))
3245 break;
3246 continue;
3247 }
3249 while (arg) {
3250 char *next = strstr(arg, "%(");
3251 int len = next - arg;
3252 const char *value;
3254 if (!next || !replace) {
3255 len = strlen(arg);
3256 value = "";
3258 } else {
3259 value = format_arg(next);
3261 if (!value) {
3262 return FALSE;
3263 }
3264 }
3266 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3267 return FALSE;
3269 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3270 }
3272 if (!argv_append(dst_argv, buf))
3273 break;
3274 }
3276 return src_argv[argc] == NULL;
3277 }
3279 static bool
3280 restore_view_position(struct view *view)
3281 {
3282 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3283 return FALSE;
3285 /* Changing the view position cancels the restoring. */
3286 /* FIXME: Changing back to the first line is not detected. */
3287 if (view->offset != 0 || view->lineno != 0) {
3288 view->p_restore = FALSE;
3289 return FALSE;
3290 }
3292 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3293 view_is_displayed(view))
3294 werase(view->win);
3296 view->yoffset = view->p_yoffset;
3297 view->p_restore = FALSE;
3299 return TRUE;
3300 }
3302 static void
3303 end_update(struct view *view, bool force)
3304 {
3305 if (!view->pipe)
3306 return;
3307 while (!view->ops->read(view, NULL))
3308 if (!force)
3309 return;
3310 if (force)
3311 io_kill(view->pipe);
3312 io_done(view->pipe);
3313 view->pipe = NULL;
3314 }
3316 static void
3317 setup_update(struct view *view, const char *vid)
3318 {
3319 reset_view(view);
3320 string_copy_rev(view->vid, vid);
3321 view->pipe = &view->io;
3322 view->start_time = time(NULL);
3323 }
3325 static bool
3326 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3327 {
3328 view->dir = dir;
3329 return format_argv(&view->argv, argv, replace);
3330 }
3332 static bool
3333 prepare_update(struct view *view, const char *argv[], const char *dir)
3334 {
3335 if (view->pipe)
3336 end_update(view, TRUE);
3337 return prepare_io(view, dir, argv, FALSE);
3338 }
3340 static bool
3341 start_update(struct view *view, const char **argv, const char *dir)
3342 {
3343 if (view->pipe)
3344 io_done(view->pipe);
3345 return prepare_io(view, dir, argv, FALSE) &&
3346 io_run(&view->io, IO_RD, dir, view->argv);
3347 }
3349 static bool
3350 prepare_update_file(struct view *view, const char *name)
3351 {
3352 if (view->pipe)
3353 end_update(view, TRUE);
3354 argv_free(view->argv);
3355 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3356 }
3358 static bool
3359 begin_update(struct view *view, bool refresh)
3360 {
3361 if (view->pipe)
3362 end_update(view, TRUE);
3364 if (!refresh) {
3365 if (view->ops->prepare) {
3366 if (!view->ops->prepare(view))
3367 return FALSE;
3368 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3369 return FALSE;
3370 }
3372 /* Put the current ref_* value to the view title ref
3373 * member. This is needed by the blob view. Most other
3374 * views sets it automatically after loading because the
3375 * first line is a commit line. */
3376 string_copy_rev(view->ref, view->id);
3377 }
3379 if (view->argv && view->argv[0] &&
3380 !io_run(&view->io, IO_RD, view->dir, view->argv))
3381 return FALSE;
3383 setup_update(view, view->id);
3385 return TRUE;
3386 }
3388 static bool
3389 update_view(struct view *view)
3390 {
3391 char out_buffer[BUFSIZ * 2];
3392 char *line;
3393 /* Clear the view and redraw everything since the tree sorting
3394 * might have rearranged things. */
3395 bool redraw = view->lines == 0;
3396 bool can_read = TRUE;
3398 if (!view->pipe)
3399 return TRUE;
3401 if (!io_can_read(view->pipe)) {
3402 if (view->lines == 0 && view_is_displayed(view)) {
3403 time_t secs = time(NULL) - view->start_time;
3405 if (secs > 1 && secs > view->update_secs) {
3406 if (view->update_secs == 0)
3407 redraw_view(view);
3408 update_view_title(view);
3409 view->update_secs = secs;
3410 }
3411 }
3412 return TRUE;
3413 }
3415 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3416 if (opt_iconv_in != ICONV_NONE) {
3417 ICONV_CONST char *inbuf = line;
3418 size_t inlen = strlen(line) + 1;
3420 char *outbuf = out_buffer;
3421 size_t outlen = sizeof(out_buffer);
3423 size_t ret;
3425 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3426 if (ret != (size_t) -1)
3427 line = out_buffer;
3428 }
3430 if (!view->ops->read(view, line)) {
3431 report("Allocation failure");
3432 end_update(view, TRUE);
3433 return FALSE;
3434 }
3435 }
3437 {
3438 unsigned long lines = view->lines;
3439 int digits;
3441 for (digits = 0; lines; digits++)
3442 lines /= 10;
3444 /* Keep the displayed view in sync with line number scaling. */
3445 if (digits != view->digits) {
3446 view->digits = digits;
3447 if (opt_line_number || view->type == VIEW_BLAME)
3448 redraw = TRUE;
3449 }
3450 }
3452 if (io_error(view->pipe)) {
3453 report("Failed to read: %s", io_strerror(view->pipe));
3454 end_update(view, TRUE);
3456 } else if (io_eof(view->pipe)) {
3457 if (view_is_displayed(view))
3458 report("");
3459 end_update(view, FALSE);
3460 }
3462 if (restore_view_position(view))
3463 redraw = TRUE;
3465 if (!view_is_displayed(view))
3466 return TRUE;
3468 if (redraw)
3469 redraw_view_from(view, 0);
3470 else
3471 redraw_view_dirty(view);
3473 /* Update the title _after_ the redraw so that if the redraw picks up a
3474 * commit reference in view->ref it'll be available here. */
3475 update_view_title(view);
3476 return TRUE;
3477 }
3479 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3481 static struct line *
3482 add_line_data(struct view *view, void *data, enum line_type type)
3483 {
3484 struct line *line;
3486 if (!realloc_lines(&view->line, view->lines, 1))
3487 return NULL;
3489 line = &view->line[view->lines++];
3490 memset(line, 0, sizeof(*line));
3491 line->type = type;
3492 line->data = data;
3493 line->dirty = 1;
3495 return line;
3496 }
3498 static struct line *
3499 add_line_text(struct view *view, const char *text, enum line_type type)
3500 {
3501 char *data = text ? strdup(text) : NULL;
3503 return data ? add_line_data(view, data, type) : NULL;
3504 }
3506 static struct line *
3507 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3508 {
3509 char buf[SIZEOF_STR];
3510 va_list args;
3512 va_start(args, fmt);
3513 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3514 buf[0] = 0;
3515 va_end(args);
3517 return buf[0] ? add_line_text(view, buf, type) : NULL;
3518 }
3520 /*
3521 * View opening
3522 */
3524 enum open_flags {
3525 OPEN_DEFAULT = 0, /* Use default view switching. */
3526 OPEN_SPLIT = 1, /* Split current view. */
3527 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3528 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3529 OPEN_PREPARED = 32, /* Open already prepared command. */
3530 };
3532 static void
3533 open_view(struct view *prev, enum request request, enum open_flags flags)
3534 {
3535 bool split = !!(flags & OPEN_SPLIT);
3536 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3537 bool nomaximize = !!(flags & OPEN_REFRESH);
3538 struct view *view = VIEW(request);
3539 int nviews = displayed_views();
3540 struct view *base_view = display[0];
3542 if (view == prev && nviews == 1 && !reload) {
3543 report("Already in %s view", view->name);
3544 return;
3545 }
3547 if (view->git_dir && !opt_git_dir[0]) {
3548 report("The %s view is disabled in pager view", view->name);
3549 return;
3550 }
3552 if (split) {
3553 display[1] = view;
3554 current_view = 1;
3555 view->parent = prev;
3556 } else if (!nomaximize) {
3557 /* Maximize the current view. */
3558 memset(display, 0, sizeof(display));
3559 current_view = 0;
3560 display[current_view] = view;
3561 }
3563 /* No prev signals that this is the first loaded view. */
3564 if (prev && view != prev) {
3565 view->prev = prev;
3566 }
3568 /* Resize the view when switching between split- and full-screen,
3569 * or when switching between two different full-screen views. */
3570 if (nviews != displayed_views() ||
3571 (nviews == 1 && base_view != display[0]))
3572 resize_display();
3574 if (view->ops->open) {
3575 if (view->pipe)
3576 end_update(view, TRUE);
3577 if (!view->ops->open(view)) {
3578 report("Failed to load %s view", view->name);
3579 return;
3580 }
3581 restore_view_position(view);
3583 } else if ((reload || strcmp(view->vid, view->id)) &&
3584 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3585 report("Failed to load %s view", view->name);
3586 return;
3587 }
3589 if (split && prev->lineno - prev->offset >= prev->height) {
3590 /* Take the title line into account. */
3591 int lines = prev->lineno - prev->offset - prev->height + 1;
3593 /* Scroll the view that was split if the current line is
3594 * outside the new limited view. */
3595 do_scroll_view(prev, lines);
3596 }
3598 if (prev && view != prev && split && view_is_displayed(prev)) {
3599 /* "Blur" the previous view. */
3600 update_view_title(prev);
3601 }
3603 if (view->pipe && view->lines == 0) {
3604 /* Clear the old view and let the incremental updating refill
3605 * the screen. */
3606 werase(view->win);
3607 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3608 report("");
3609 } else if (view_is_displayed(view)) {
3610 redraw_view(view);
3611 report("");
3612 }
3613 }
3615 static void
3616 open_external_viewer(const char *argv[], const char *dir)
3617 {
3618 def_prog_mode(); /* save current tty modes */
3619 endwin(); /* restore original tty modes */
3620 io_run_fg(argv, dir);
3621 fprintf(stderr, "Press Enter to continue");
3622 getc(opt_tty);
3623 reset_prog_mode();
3624 redraw_display(TRUE);
3625 }
3627 static void
3628 open_mergetool(const char *file)
3629 {
3630 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3632 open_external_viewer(mergetool_argv, opt_cdup);
3633 }
3635 static void
3636 open_editor(const char *file)
3637 {
3638 const char *editor_argv[] = { "vi", file, NULL };
3639 const char *editor;
3641 editor = getenv("GIT_EDITOR");
3642 if (!editor && *opt_editor)
3643 editor = opt_editor;
3644 if (!editor)
3645 editor = getenv("VISUAL");
3646 if (!editor)
3647 editor = getenv("EDITOR");
3648 if (!editor)
3649 editor = "vi";
3651 editor_argv[0] = editor;
3652 open_external_viewer(editor_argv, opt_cdup);
3653 }
3655 static void
3656 open_run_request(enum request request)
3657 {
3658 struct run_request *req = get_run_request(request);
3659 const char **argv = NULL;
3661 if (!req) {
3662 report("Unknown run request");
3663 return;
3664 }
3666 if (format_argv(&argv, req->argv, TRUE))
3667 open_external_viewer(argv, NULL);
3668 if (argv)
3669 argv_free(argv);
3670 free(argv);
3671 }
3673 /*
3674 * User request switch noodle
3675 */
3677 static int
3678 view_driver(struct view *view, enum request request)
3679 {
3680 int i;
3682 if (request == REQ_NONE)
3683 return TRUE;
3685 if (request > REQ_NONE) {
3686 open_run_request(request);
3687 view_request(view, REQ_REFRESH);
3688 return TRUE;
3689 }
3691 request = view_request(view, request);
3692 if (request == REQ_NONE)
3693 return TRUE;
3695 switch (request) {
3696 case REQ_MOVE_UP:
3697 case REQ_MOVE_DOWN:
3698 case REQ_MOVE_PAGE_UP:
3699 case REQ_MOVE_PAGE_DOWN:
3700 case REQ_MOVE_FIRST_LINE:
3701 case REQ_MOVE_LAST_LINE:
3702 move_view(view, request);
3703 break;
3705 case REQ_SCROLL_FIRST_COL:
3706 case REQ_SCROLL_LEFT:
3707 case REQ_SCROLL_RIGHT:
3708 case REQ_SCROLL_LINE_DOWN:
3709 case REQ_SCROLL_LINE_UP:
3710 case REQ_SCROLL_PAGE_DOWN:
3711 case REQ_SCROLL_PAGE_UP:
3712 scroll_view(view, request);
3713 break;
3715 case REQ_VIEW_BLAME:
3716 if (!opt_file[0]) {
3717 report("No file chosen, press %s to open tree view",
3718 get_key(view->keymap, REQ_VIEW_TREE));
3719 break;
3720 }
3721 open_view(view, request, OPEN_DEFAULT);
3722 break;
3724 case REQ_VIEW_BLOB:
3725 if (!ref_blob[0]) {
3726 report("No file chosen, press %s to open tree view",
3727 get_key(view->keymap, REQ_VIEW_TREE));
3728 break;
3729 }
3730 open_view(view, request, OPEN_DEFAULT);
3731 break;
3733 case REQ_VIEW_PAGER:
3734 if (view == NULL) {
3735 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3736 die("Failed to open stdin");
3737 open_view(view, request, OPEN_PREPARED);
3738 break;
3739 }
3741 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3742 report("No pager content, press %s to run command from prompt",
3743 get_key(view->keymap, REQ_PROMPT));
3744 break;
3745 }
3746 open_view(view, request, OPEN_DEFAULT);
3747 break;
3749 case REQ_VIEW_STAGE:
3750 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3751 report("No stage content, press %s to open the status view and choose file",
3752 get_key(view->keymap, REQ_VIEW_STATUS));
3753 break;
3754 }
3755 open_view(view, request, OPEN_DEFAULT);
3756 break;
3758 case REQ_VIEW_STATUS:
3759 if (opt_is_inside_work_tree == FALSE) {
3760 report("The status view requires a working tree");
3761 break;
3762 }
3763 open_view(view, request, OPEN_DEFAULT);
3764 break;
3766 case REQ_VIEW_MAIN:
3767 case REQ_VIEW_DIFF:
3768 case REQ_VIEW_LOG:
3769 case REQ_VIEW_TREE:
3770 case REQ_VIEW_HELP:
3771 case REQ_VIEW_BRANCH:
3772 open_view(view, request, OPEN_DEFAULT);
3773 break;
3775 case REQ_NEXT:
3776 case REQ_PREVIOUS:
3777 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3779 if (view->parent) {
3780 int line;
3782 view = view->parent;
3783 line = view->lineno;
3784 move_view(view, request);
3785 if (view_is_displayed(view))
3786 update_view_title(view);
3787 if (line != view->lineno)
3788 view_request(view, REQ_ENTER);
3789 } else {
3790 move_view(view, request);
3791 }
3792 break;
3794 case REQ_VIEW_NEXT:
3795 {
3796 int nviews = displayed_views();
3797 int next_view = (current_view + 1) % nviews;
3799 if (next_view == current_view) {
3800 report("Only one view is displayed");
3801 break;
3802 }
3804 current_view = next_view;
3805 /* Blur out the title of the previous view. */
3806 update_view_title(view);
3807 report("");
3808 break;
3809 }
3810 case REQ_REFRESH:
3811 report("Refreshing is not yet supported for the %s view", view->name);
3812 break;
3814 case REQ_MAXIMIZE:
3815 if (displayed_views() == 2)
3816 maximize_view(view);
3817 break;
3819 case REQ_OPTIONS:
3820 open_option_menu();
3821 break;
3823 case REQ_TOGGLE_LINENO:
3824 toggle_view_option(&opt_line_number, "line numbers");
3825 break;
3827 case REQ_TOGGLE_DATE:
3828 toggle_date();
3829 break;
3831 case REQ_TOGGLE_AUTHOR:
3832 toggle_author();
3833 break;
3835 case REQ_TOGGLE_REV_GRAPH:
3836 toggle_view_option(&opt_rev_graph, "revision graph display");
3837 break;
3839 case REQ_TOGGLE_REFS:
3840 toggle_view_option(&opt_show_refs, "reference display");
3841 break;
3843 case REQ_TOGGLE_SORT_FIELD:
3844 case REQ_TOGGLE_SORT_ORDER:
3845 report("Sorting is not yet supported for the %s view", view->name);
3846 break;
3848 case REQ_SEARCH:
3849 case REQ_SEARCH_BACK:
3850 search_view(view, request);
3851 break;
3853 case REQ_FIND_NEXT:
3854 case REQ_FIND_PREV:
3855 find_next(view, request);
3856 break;
3858 case REQ_STOP_LOADING:
3859 foreach_view(view, i) {
3860 if (view->pipe)
3861 report("Stopped loading the %s view", view->name),
3862 end_update(view, TRUE);
3863 }
3864 break;
3866 case REQ_SHOW_VERSION:
3867 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3868 return TRUE;
3870 case REQ_SCREEN_REDRAW:
3871 redraw_display(TRUE);
3872 break;
3874 case REQ_EDIT:
3875 report("Nothing to edit");
3876 break;
3878 case REQ_ENTER:
3879 report("Nothing to enter");
3880 break;
3882 case REQ_VIEW_CLOSE:
3883 /* XXX: Mark closed views by letting view->prev point to the
3884 * view itself. Parents to closed view should never be
3885 * followed. */
3886 if (view->prev && view->prev != view) {
3887 maximize_view(view->prev);
3888 view->prev = view;
3889 break;
3890 }
3891 /* Fall-through */
3892 case REQ_QUIT:
3893 return FALSE;
3895 default:
3896 report("Unknown key, press %s for help",
3897 get_key(view->keymap, REQ_VIEW_HELP));
3898 return TRUE;
3899 }
3901 return TRUE;
3902 }
3905 /*
3906 * View backend utilities
3907 */
3909 enum sort_field {
3910 ORDERBY_NAME,
3911 ORDERBY_DATE,
3912 ORDERBY_AUTHOR,
3913 };
3915 struct sort_state {
3916 const enum sort_field *fields;
3917 size_t size, current;
3918 bool reverse;
3919 };
3921 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3922 #define get_sort_field(state) ((state).fields[(state).current])
3923 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3925 static void
3926 sort_view(struct view *view, enum request request, struct sort_state *state,
3927 int (*compare)(const void *, const void *))
3928 {
3929 switch (request) {
3930 case REQ_TOGGLE_SORT_FIELD:
3931 state->current = (state->current + 1) % state->size;
3932 break;
3934 case REQ_TOGGLE_SORT_ORDER:
3935 state->reverse = !state->reverse;
3936 break;
3937 default:
3938 die("Not a sort request");
3939 }
3941 qsort(view->line, view->lines, sizeof(*view->line), compare);
3942 redraw_view(view);
3943 }
3945 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3947 /* Small author cache to reduce memory consumption. It uses binary
3948 * search to lookup or find place to position new entries. No entries
3949 * are ever freed. */
3950 static const char *
3951 get_author(const char *name)
3952 {
3953 static const char **authors;
3954 static size_t authors_size;
3955 int from = 0, to = authors_size - 1;
3957 while (from <= to) {
3958 size_t pos = (to + from) / 2;
3959 int cmp = strcmp(name, authors[pos]);
3961 if (!cmp)
3962 return authors[pos];
3964 if (cmp < 0)
3965 to = pos - 1;
3966 else
3967 from = pos + 1;
3968 }
3970 if (!realloc_authors(&authors, authors_size, 1))
3971 return NULL;
3972 name = strdup(name);
3973 if (!name)
3974 return NULL;
3976 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3977 authors[from] = name;
3978 authors_size++;
3980 return name;
3981 }
3983 static void
3984 parse_timesec(struct time *time, const char *sec)
3985 {
3986 time->sec = (time_t) atol(sec);
3987 }
3989 static void
3990 parse_timezone(struct time *time, const char *zone)
3991 {
3992 long tz;
3994 tz = ('0' - zone[1]) * 60 * 60 * 10;
3995 tz += ('0' - zone[2]) * 60 * 60;
3996 tz += ('0' - zone[3]) * 60 * 10;
3997 tz += ('0' - zone[4]) * 60;
3999 if (zone[0] == '-')
4000 tz = -tz;
4002 time->tz = tz;
4003 time->sec -= tz;
4004 }
4006 /* Parse author lines where the name may be empty:
4007 * author <email@address.tld> 1138474660 +0100
4008 */
4009 static void
4010 parse_author_line(char *ident, const char **author, struct time *time)
4011 {
4012 char *nameend = strchr(ident, '<');
4013 char *emailend = strchr(ident, '>');
4015 if (nameend && emailend)
4016 *nameend = *emailend = 0;
4017 ident = chomp_string(ident);
4018 if (!*ident) {
4019 if (nameend)
4020 ident = chomp_string(nameend + 1);
4021 if (!*ident)
4022 ident = "Unknown";
4023 }
4025 *author = get_author(ident);
4027 /* Parse epoch and timezone */
4028 if (emailend && emailend[1] == ' ') {
4029 char *secs = emailend + 2;
4030 char *zone = strchr(secs, ' ');
4032 parse_timesec(time, secs);
4034 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4035 parse_timezone(time, zone + 1);
4036 }
4037 }
4039 /*
4040 * Pager backend
4041 */
4043 static bool
4044 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4045 {
4046 if (opt_line_number && draw_lineno(view, lineno))
4047 return TRUE;
4049 draw_text(view, line->type, line->data, TRUE);
4050 return TRUE;
4051 }
4053 static bool
4054 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4055 {
4056 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4057 char ref[SIZEOF_STR];
4059 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4060 return TRUE;
4062 /* This is the only fatal call, since it can "corrupt" the buffer. */
4063 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4064 return FALSE;
4066 return TRUE;
4067 }
4069 static void
4070 add_pager_refs(struct view *view, struct line *line)
4071 {
4072 char buf[SIZEOF_STR];
4073 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4074 struct ref_list *list;
4075 size_t bufpos = 0, i;
4076 const char *sep = "Refs: ";
4077 bool is_tag = FALSE;
4079 assert(line->type == LINE_COMMIT);
4081 list = get_ref_list(commit_id);
4082 if (!list) {
4083 if (view->type == VIEW_DIFF)
4084 goto try_add_describe_ref;
4085 return;
4086 }
4088 for (i = 0; i < list->size; i++) {
4089 struct ref *ref = list->refs[i];
4090 const char *fmt = ref->tag ? "%s[%s]" :
4091 ref->remote ? "%s<%s>" : "%s%s";
4093 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4094 return;
4095 sep = ", ";
4096 if (ref->tag)
4097 is_tag = TRUE;
4098 }
4100 if (!is_tag && view->type == VIEW_DIFF) {
4101 try_add_describe_ref:
4102 /* Add <tag>-g<commit_id> "fake" reference. */
4103 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4104 return;
4105 }
4107 if (bufpos == 0)
4108 return;
4110 add_line_text(view, buf, LINE_PP_REFS);
4111 }
4113 static bool
4114 pager_read(struct view *view, char *data)
4115 {
4116 struct line *line;
4118 if (!data)
4119 return TRUE;
4121 line = add_line_text(view, data, get_line_type(data));
4122 if (!line)
4123 return FALSE;
4125 if (line->type == LINE_COMMIT &&
4126 (view->type == VIEW_DIFF ||
4127 view->type == VIEW_LOG))
4128 add_pager_refs(view, line);
4130 return TRUE;
4131 }
4133 static enum request
4134 pager_request(struct view *view, enum request request, struct line *line)
4135 {
4136 int split = 0;
4138 if (request != REQ_ENTER)
4139 return request;
4141 if (line->type == LINE_COMMIT &&
4142 (view->type == VIEW_LOG ||
4143 view->type == VIEW_PAGER)) {
4144 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4145 split = 1;
4146 }
4148 /* Always scroll the view even if it was split. That way
4149 * you can use Enter to scroll through the log view and
4150 * split open each commit diff. */
4151 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4153 /* FIXME: A minor workaround. Scrolling the view will call report("")
4154 * but if we are scrolling a non-current view this won't properly
4155 * update the view title. */
4156 if (split)
4157 update_view_title(view);
4159 return REQ_NONE;
4160 }
4162 static bool
4163 pager_grep(struct view *view, struct line *line)
4164 {
4165 const char *text[] = { line->data, NULL };
4167 return grep_text(view, text);
4168 }
4170 static void
4171 pager_select(struct view *view, struct line *line)
4172 {
4173 if (line->type == LINE_COMMIT) {
4174 char *text = (char *)line->data + STRING_SIZE("commit ");
4176 if (view->type != VIEW_PAGER)
4177 string_copy_rev(view->ref, text);
4178 string_copy_rev(ref_commit, text);
4179 }
4180 }
4182 static struct view_ops pager_ops = {
4183 "line",
4184 NULL,
4185 NULL,
4186 pager_read,
4187 pager_draw,
4188 pager_request,
4189 pager_grep,
4190 pager_select,
4191 };
4193 static const char *log_argv[SIZEOF_ARG] = {
4194 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4195 };
4197 static enum request
4198 log_request(struct view *view, enum request request, struct line *line)
4199 {
4200 switch (request) {
4201 case REQ_REFRESH:
4202 load_refs();
4203 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4204 return REQ_NONE;
4205 default:
4206 return pager_request(view, request, line);
4207 }
4208 }
4210 static struct view_ops log_ops = {
4211 "line",
4212 log_argv,
4213 NULL,
4214 pager_read,
4215 pager_draw,
4216 log_request,
4217 pager_grep,
4218 pager_select,
4219 };
4221 static const char *diff_argv[SIZEOF_ARG] = {
4222 "git", "show", "--pretty=fuller", "--no-color", "--root",
4223 "--patch-with-stat", "--find-copies-harder", "-C",
4224 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4225 };
4227 static bool
4228 diff_read(struct view *view, char *data)
4229 {
4230 if (!data) {
4231 /* Fall back to retry if no diff will be shown. */
4232 if (view->lines == 0 && opt_file_args) {
4233 int pos = argv_size(view->argv)
4234 - argv_size(opt_file_args) - 1;
4236 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4237 for (; view->argv[pos]; pos++) {
4238 free((void *) view->argv[pos]);
4239 view->argv[pos] = NULL;
4240 }
4242 if (view->pipe)
4243 io_done(view->pipe);
4244 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4245 return FALSE;
4246 }
4247 }
4248 return TRUE;
4249 }
4251 return pager_read(view, data);
4252 }
4254 static struct view_ops diff_ops = {
4255 "line",
4256 diff_argv,
4257 NULL,
4258 diff_read,
4259 pager_draw,
4260 pager_request,
4261 pager_grep,
4262 pager_select,
4263 };
4265 /*
4266 * Help backend
4267 */
4269 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4271 static bool
4272 help_open_keymap_title(struct view *view, enum keymap keymap)
4273 {
4274 struct line *line;
4276 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4277 help_keymap_hidden[keymap] ? '+' : '-',
4278 enum_name(keymap_table[keymap]));
4279 if (line)
4280 line->other = keymap;
4282 return help_keymap_hidden[keymap];
4283 }
4285 static void
4286 help_open_keymap(struct view *view, enum keymap keymap)
4287 {
4288 const char *group = NULL;
4289 char buf[SIZEOF_STR];
4290 size_t bufpos;
4291 bool add_title = TRUE;
4292 int i;
4294 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4295 const char *key = NULL;
4297 if (req_info[i].request == REQ_NONE)
4298 continue;
4300 if (!req_info[i].request) {
4301 group = req_info[i].help;
4302 continue;
4303 }
4305 key = get_keys(keymap, req_info[i].request, TRUE);
4306 if (!key || !*key)
4307 continue;
4309 if (add_title && help_open_keymap_title(view, keymap))
4310 return;
4311 add_title = FALSE;
4313 if (group) {
4314 add_line_text(view, group, LINE_HELP_GROUP);
4315 group = NULL;
4316 }
4318 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4319 enum_name(req_info[i]), req_info[i].help);
4320 }
4322 group = "External commands:";
4324 for (i = 0; i < run_requests; i++) {
4325 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4326 const char *key;
4327 int argc;
4329 if (!req || req->keymap != keymap)
4330 continue;
4332 key = get_key_name(req->key);
4333 if (!*key)
4334 key = "(no key defined)";
4336 if (add_title && help_open_keymap_title(view, keymap))
4337 return;
4338 if (group) {
4339 add_line_text(view, group, LINE_HELP_GROUP);
4340 group = NULL;
4341 }
4343 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4344 if (!string_format_from(buf, &bufpos, "%s%s",
4345 argc ? " " : "", req->argv[argc]))
4346 return;
4348 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4349 }
4350 }
4352 static bool
4353 help_open(struct view *view)
4354 {
4355 enum keymap keymap;
4357 reset_view(view);
4358 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4359 add_line_text(view, "", LINE_DEFAULT);
4361 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4362 help_open_keymap(view, keymap);
4364 return TRUE;
4365 }
4367 static enum request
4368 help_request(struct view *view, enum request request, struct line *line)
4369 {
4370 switch (request) {
4371 case REQ_ENTER:
4372 if (line->type == LINE_HELP_KEYMAP) {
4373 help_keymap_hidden[line->other] =
4374 !help_keymap_hidden[line->other];
4375 view->p_restore = TRUE;
4376 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4377 }
4379 return REQ_NONE;
4380 default:
4381 return pager_request(view, request, line);
4382 }
4383 }
4385 static struct view_ops help_ops = {
4386 "line",
4387 NULL,
4388 help_open,
4389 NULL,
4390 pager_draw,
4391 help_request,
4392 pager_grep,
4393 pager_select,
4394 };
4397 /*
4398 * Tree backend
4399 */
4401 struct tree_stack_entry {
4402 struct tree_stack_entry *prev; /* Entry below this in the stack */
4403 unsigned long lineno; /* Line number to restore */
4404 char *name; /* Position of name in opt_path */
4405 };
4407 /* The top of the path stack. */
4408 static struct tree_stack_entry *tree_stack = NULL;
4409 unsigned long tree_lineno = 0;
4411 static void
4412 pop_tree_stack_entry(void)
4413 {
4414 struct tree_stack_entry *entry = tree_stack;
4416 tree_lineno = entry->lineno;
4417 entry->name[0] = 0;
4418 tree_stack = entry->prev;
4419 free(entry);
4420 }
4422 static void
4423 push_tree_stack_entry(const char *name, unsigned long lineno)
4424 {
4425 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4426 size_t pathlen = strlen(opt_path);
4428 if (!entry)
4429 return;
4431 entry->prev = tree_stack;
4432 entry->name = opt_path + pathlen;
4433 tree_stack = entry;
4435 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4436 pop_tree_stack_entry();
4437 return;
4438 }
4440 /* Move the current line to the first tree entry. */
4441 tree_lineno = 1;
4442 entry->lineno = lineno;
4443 }
4445 /* Parse output from git-ls-tree(1):
4446 *
4447 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4448 */
4450 #define SIZEOF_TREE_ATTR \
4451 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4453 #define SIZEOF_TREE_MODE \
4454 STRING_SIZE("100644 ")
4456 #define TREE_ID_OFFSET \
4457 STRING_SIZE("100644 blob ")
4459 struct tree_entry {
4460 char id[SIZEOF_REV];
4461 mode_t mode;
4462 struct time time; /* Date from the author ident. */
4463 const char *author; /* Author of the commit. */
4464 char name[1];
4465 };
4467 static const char *
4468 tree_path(const struct line *line)
4469 {
4470 return ((struct tree_entry *) line->data)->name;
4471 }
4473 static int
4474 tree_compare_entry(const struct line *line1, const struct line *line2)
4475 {
4476 if (line1->type != line2->type)
4477 return line1->type == LINE_TREE_DIR ? -1 : 1;
4478 return strcmp(tree_path(line1), tree_path(line2));
4479 }
4481 static const enum sort_field tree_sort_fields[] = {
4482 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4483 };
4484 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4486 static int
4487 tree_compare(const void *l1, const void *l2)
4488 {
4489 const struct line *line1 = (const struct line *) l1;
4490 const struct line *line2 = (const struct line *) l2;
4491 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4492 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4494 if (line1->type == LINE_TREE_HEAD)
4495 return -1;
4496 if (line2->type == LINE_TREE_HEAD)
4497 return 1;
4499 switch (get_sort_field(tree_sort_state)) {
4500 case ORDERBY_DATE:
4501 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4503 case ORDERBY_AUTHOR:
4504 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4506 case ORDERBY_NAME:
4507 default:
4508 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4509 }
4510 }
4513 static struct line *
4514 tree_entry(struct view *view, enum line_type type, const char *path,
4515 const char *mode, const char *id)
4516 {
4517 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4518 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4520 if (!entry || !line) {
4521 free(entry);
4522 return NULL;
4523 }
4525 strncpy(entry->name, path, strlen(path));
4526 if (mode)
4527 entry->mode = strtoul(mode, NULL, 8);
4528 if (id)
4529 string_copy_rev(entry->id, id);
4531 return line;
4532 }
4534 static bool
4535 tree_read_date(struct view *view, char *text, bool *read_date)
4536 {
4537 static const char *author_name;
4538 static struct time author_time;
4540 if (!text && *read_date) {
4541 *read_date = FALSE;
4542 return TRUE;
4544 } else if (!text) {
4545 char *path = *opt_path ? opt_path : ".";
4546 /* Find next entry to process */
4547 const char *log_file[] = {
4548 "git", "log", "--no-color", "--pretty=raw",
4549 "--cc", "--raw", view->id, "--", path, NULL
4550 };
4552 if (!view->lines) {
4553 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4554 report("Tree is empty");
4555 return TRUE;
4556 }
4558 if (!start_update(view, log_file, opt_cdup)) {
4559 report("Failed to load tree data");
4560 return TRUE;
4561 }
4563 *read_date = TRUE;
4564 return FALSE;
4566 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4567 parse_author_line(text + STRING_SIZE("author "),
4568 &author_name, &author_time);
4570 } else if (*text == ':') {
4571 char *pos;
4572 size_t annotated = 1;
4573 size_t i;
4575 pos = strchr(text, '\t');
4576 if (!pos)
4577 return TRUE;
4578 text = pos + 1;
4579 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4580 text += strlen(opt_path);
4581 pos = strchr(text, '/');
4582 if (pos)
4583 *pos = 0;
4585 for (i = 1; i < view->lines; i++) {
4586 struct line *line = &view->line[i];
4587 struct tree_entry *entry = line->data;
4589 annotated += !!entry->author;
4590 if (entry->author || strcmp(entry->name, text))
4591 continue;
4593 entry->author = author_name;
4594 entry->time = author_time;
4595 line->dirty = 1;
4596 break;
4597 }
4599 if (annotated == view->lines)
4600 io_kill(view->pipe);
4601 }
4602 return TRUE;
4603 }
4605 static bool
4606 tree_read(struct view *view, char *text)
4607 {
4608 static bool read_date = FALSE;
4609 struct tree_entry *data;
4610 struct line *entry, *line;
4611 enum line_type type;
4612 size_t textlen = text ? strlen(text) : 0;
4613 char *path = text + SIZEOF_TREE_ATTR;
4615 if (read_date || !text)
4616 return tree_read_date(view, text, &read_date);
4618 if (textlen <= SIZEOF_TREE_ATTR)
4619 return FALSE;
4620 if (view->lines == 0 &&
4621 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4622 return FALSE;
4624 /* Strip the path part ... */
4625 if (*opt_path) {
4626 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4627 size_t striplen = strlen(opt_path);
4629 if (pathlen > striplen)
4630 memmove(path, path + striplen,
4631 pathlen - striplen + 1);
4633 /* Insert "link" to parent directory. */
4634 if (view->lines == 1 &&
4635 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4636 return FALSE;
4637 }
4639 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4640 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4641 if (!entry)
4642 return FALSE;
4643 data = entry->data;
4645 /* Skip "Directory ..." and ".." line. */
4646 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4647 if (tree_compare_entry(line, entry) <= 0)
4648 continue;
4650 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4652 line->data = data;
4653 line->type = type;
4654 for (; line <= entry; line++)
4655 line->dirty = line->cleareol = 1;
4656 return TRUE;
4657 }
4659 if (tree_lineno > view->lineno) {
4660 view->lineno = tree_lineno;
4661 tree_lineno = 0;
4662 }
4664 return TRUE;
4665 }
4667 static bool
4668 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4669 {
4670 struct tree_entry *entry = line->data;
4672 if (line->type == LINE_TREE_HEAD) {
4673 if (draw_text(view, line->type, "Directory path /", TRUE))
4674 return TRUE;
4675 } else {
4676 if (draw_mode(view, entry->mode))
4677 return TRUE;
4679 if (opt_author && draw_author(view, entry->author))
4680 return TRUE;
4682 if (opt_date && draw_date(view, &entry->time))
4683 return TRUE;
4684 }
4685 if (draw_text(view, line->type, entry->name, TRUE))
4686 return TRUE;
4687 return TRUE;
4688 }
4690 static void
4691 open_blob_editor(const char *id)
4692 {
4693 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4694 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4695 int fd = mkstemp(file);
4697 if (fd == -1)
4698 report("Failed to create temporary file");
4699 else if (!io_run_append(blob_argv, fd))
4700 report("Failed to save blob data to file");
4701 else
4702 open_editor(file);
4703 if (fd != -1)
4704 unlink(file);
4705 }
4707 static enum request
4708 tree_request(struct view *view, enum request request, struct line *line)
4709 {
4710 enum open_flags flags;
4711 struct tree_entry *entry = line->data;
4713 switch (request) {
4714 case REQ_VIEW_BLAME:
4715 if (line->type != LINE_TREE_FILE) {
4716 report("Blame only supported for files");
4717 return REQ_NONE;
4718 }
4720 string_copy(opt_ref, view->vid);
4721 return request;
4723 case REQ_EDIT:
4724 if (line->type != LINE_TREE_FILE) {
4725 report("Edit only supported for files");
4726 } else if (!is_head_commit(view->vid)) {
4727 open_blob_editor(entry->id);
4728 } else {
4729 open_editor(opt_file);
4730 }
4731 return REQ_NONE;
4733 case REQ_TOGGLE_SORT_FIELD:
4734 case REQ_TOGGLE_SORT_ORDER:
4735 sort_view(view, request, &tree_sort_state, tree_compare);
4736 return REQ_NONE;
4738 case REQ_PARENT:
4739 if (!*opt_path) {
4740 /* quit view if at top of tree */
4741 return REQ_VIEW_CLOSE;
4742 }
4743 /* fake 'cd ..' */
4744 line = &view->line[1];
4745 break;
4747 case REQ_ENTER:
4748 break;
4750 default:
4751 return request;
4752 }
4754 /* Cleanup the stack if the tree view is at a different tree. */
4755 while (!*opt_path && tree_stack)
4756 pop_tree_stack_entry();
4758 switch (line->type) {
4759 case LINE_TREE_DIR:
4760 /* Depending on whether it is a subdirectory or parent link
4761 * mangle the path buffer. */
4762 if (line == &view->line[1] && *opt_path) {
4763 pop_tree_stack_entry();
4765 } else {
4766 const char *basename = tree_path(line);
4768 push_tree_stack_entry(basename, view->lineno);
4769 }
4771 /* Trees and subtrees share the same ID, so they are not not
4772 * unique like blobs. */
4773 flags = OPEN_RELOAD;
4774 request = REQ_VIEW_TREE;
4775 break;
4777 case LINE_TREE_FILE:
4778 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4779 request = REQ_VIEW_BLOB;
4780 break;
4782 default:
4783 return REQ_NONE;
4784 }
4786 open_view(view, request, flags);
4787 if (request == REQ_VIEW_TREE)
4788 view->lineno = tree_lineno;
4790 return REQ_NONE;
4791 }
4793 static bool
4794 tree_grep(struct view *view, struct line *line)
4795 {
4796 struct tree_entry *entry = line->data;
4797 const char *text[] = {
4798 entry->name,
4799 opt_author ? entry->author : "",
4800 mkdate(&entry->time, opt_date),
4801 NULL
4802 };
4804 return grep_text(view, text);
4805 }
4807 static void
4808 tree_select(struct view *view, struct line *line)
4809 {
4810 struct tree_entry *entry = line->data;
4812 if (line->type == LINE_TREE_FILE) {
4813 string_copy_rev(ref_blob, entry->id);
4814 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4816 } else if (line->type != LINE_TREE_DIR) {
4817 return;
4818 }
4820 string_copy_rev(view->ref, entry->id);
4821 }
4823 static bool
4824 tree_prepare(struct view *view)
4825 {
4826 if (view->lines == 0 && opt_prefix[0]) {
4827 char *pos = opt_prefix;
4829 while (pos && *pos) {
4830 char *end = strchr(pos, '/');
4832 if (end)
4833 *end = 0;
4834 push_tree_stack_entry(pos, 0);
4835 pos = end;
4836 if (end) {
4837 *end = '/';
4838 pos++;
4839 }
4840 }
4842 } else if (strcmp(view->vid, view->id)) {
4843 opt_path[0] = 0;
4844 }
4846 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4847 }
4849 static const char *tree_argv[SIZEOF_ARG] = {
4850 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4851 };
4853 static struct view_ops tree_ops = {
4854 "file",
4855 tree_argv,
4856 NULL,
4857 tree_read,
4858 tree_draw,
4859 tree_request,
4860 tree_grep,
4861 tree_select,
4862 tree_prepare,
4863 };
4865 static bool
4866 blob_read(struct view *view, char *line)
4867 {
4868 if (!line)
4869 return TRUE;
4870 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4871 }
4873 static enum request
4874 blob_request(struct view *view, enum request request, struct line *line)
4875 {
4876 switch (request) {
4877 case REQ_EDIT:
4878 open_blob_editor(view->vid);
4879 return REQ_NONE;
4880 default:
4881 return pager_request(view, request, line);
4882 }
4883 }
4885 static const char *blob_argv[SIZEOF_ARG] = {
4886 "git", "cat-file", "blob", "%(blob)", NULL
4887 };
4889 static struct view_ops blob_ops = {
4890 "line",
4891 blob_argv,
4892 NULL,
4893 blob_read,
4894 pager_draw,
4895 blob_request,
4896 pager_grep,
4897 pager_select,
4898 };
4900 /*
4901 * Blame backend
4902 *
4903 * Loading the blame view is a two phase job:
4904 *
4905 * 1. File content is read either using opt_file from the
4906 * filesystem or using git-cat-file.
4907 * 2. Then blame information is incrementally added by
4908 * reading output from git-blame.
4909 */
4911 struct blame_commit {
4912 char id[SIZEOF_REV]; /* SHA1 ID. */
4913 char title[128]; /* First line of the commit message. */
4914 const char *author; /* Author of the commit. */
4915 struct time time; /* Date from the author ident. */
4916 char filename[128]; /* Name of file. */
4917 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4918 char parent_filename[128]; /* Parent/previous name of file. */
4919 };
4921 struct blame {
4922 struct blame_commit *commit;
4923 unsigned long lineno;
4924 char text[1];
4925 };
4927 static bool
4928 blame_open(struct view *view)
4929 {
4930 char path[SIZEOF_STR];
4931 size_t i;
4933 if (!view->prev && *opt_prefix) {
4934 string_copy(path, opt_file);
4935 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4936 return FALSE;
4937 }
4939 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4940 const char *blame_cat_file_argv[] = {
4941 "git", "cat-file", "blob", path, NULL
4942 };
4944 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4945 !start_update(view, blame_cat_file_argv, opt_cdup))
4946 return FALSE;
4947 }
4949 /* First pass: remove multiple references to the same commit. */
4950 for (i = 0; i < view->lines; i++) {
4951 struct blame *blame = view->line[i].data;
4953 if (blame->commit && blame->commit->id[0])
4954 blame->commit->id[0] = 0;
4955 else
4956 blame->commit = NULL;
4957 }
4959 /* Second pass: free existing references. */
4960 for (i = 0; i < view->lines; i++) {
4961 struct blame *blame = view->line[i].data;
4963 if (blame->commit)
4964 free(blame->commit);
4965 }
4967 setup_update(view, opt_file);
4968 string_format(view->ref, "%s ...", opt_file);
4970 return TRUE;
4971 }
4973 static struct blame_commit *
4974 get_blame_commit(struct view *view, const char *id)
4975 {
4976 size_t i;
4978 for (i = 0; i < view->lines; i++) {
4979 struct blame *blame = view->line[i].data;
4981 if (!blame->commit)
4982 continue;
4984 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4985 return blame->commit;
4986 }
4988 {
4989 struct blame_commit *commit = calloc(1, sizeof(*commit));
4991 if (commit)
4992 string_ncopy(commit->id, id, SIZEOF_REV);
4993 return commit;
4994 }
4995 }
4997 static bool
4998 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4999 {
5000 const char *pos = *posref;
5002 *posref = NULL;
5003 pos = strchr(pos + 1, ' ');
5004 if (!pos || !isdigit(pos[1]))
5005 return FALSE;
5006 *number = atoi(pos + 1);
5007 if (*number < min || *number > max)
5008 return FALSE;
5010 *posref = pos;
5011 return TRUE;
5012 }
5014 static struct blame_commit *
5015 parse_blame_commit(struct view *view, const char *text, int *blamed)
5016 {
5017 struct blame_commit *commit;
5018 struct blame *blame;
5019 const char *pos = text + SIZEOF_REV - 2;
5020 size_t orig_lineno = 0;
5021 size_t lineno;
5022 size_t group;
5024 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5025 return NULL;
5027 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5028 !parse_number(&pos, &lineno, 1, view->lines) ||
5029 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5030 return NULL;
5032 commit = get_blame_commit(view, text);
5033 if (!commit)
5034 return NULL;
5036 *blamed += group;
5037 while (group--) {
5038 struct line *line = &view->line[lineno + group - 1];
5040 blame = line->data;
5041 blame->commit = commit;
5042 blame->lineno = orig_lineno + group - 1;
5043 line->dirty = 1;
5044 }
5046 return commit;
5047 }
5049 static bool
5050 blame_read_file(struct view *view, const char *line, bool *read_file)
5051 {
5052 if (!line) {
5053 const char *blame_argv[] = {
5054 "git", "blame", "--incremental",
5055 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5056 };
5058 if (view->lines == 0 && !view->prev)
5059 die("No blame exist for %s", view->vid);
5061 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5062 report("Failed to load blame data");
5063 return TRUE;
5064 }
5066 *read_file = FALSE;
5067 return FALSE;
5069 } else {
5070 size_t linelen = strlen(line);
5071 struct blame *blame = malloc(sizeof(*blame) + linelen);
5073 if (!blame)
5074 return FALSE;
5076 blame->commit = NULL;
5077 strncpy(blame->text, line, linelen);
5078 blame->text[linelen] = 0;
5079 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5080 }
5081 }
5083 static bool
5084 match_blame_header(const char *name, char **line)
5085 {
5086 size_t namelen = strlen(name);
5087 bool matched = !strncmp(name, *line, namelen);
5089 if (matched)
5090 *line += namelen;
5092 return matched;
5093 }
5095 static bool
5096 blame_read(struct view *view, char *line)
5097 {
5098 static struct blame_commit *commit = NULL;
5099 static int blamed = 0;
5100 static bool read_file = TRUE;
5102 if (read_file)
5103 return blame_read_file(view, line, &read_file);
5105 if (!line) {
5106 /* Reset all! */
5107 commit = NULL;
5108 blamed = 0;
5109 read_file = TRUE;
5110 string_format(view->ref, "%s", view->vid);
5111 if (view_is_displayed(view)) {
5112 update_view_title(view);
5113 redraw_view_from(view, 0);
5114 }
5115 return TRUE;
5116 }
5118 if (!commit) {
5119 commit = parse_blame_commit(view, line, &blamed);
5120 string_format(view->ref, "%s %2d%%", view->vid,
5121 view->lines ? blamed * 100 / view->lines : 0);
5123 } else if (match_blame_header("author ", &line)) {
5124 commit->author = get_author(line);
5126 } else if (match_blame_header("author-time ", &line)) {
5127 parse_timesec(&commit->time, line);
5129 } else if (match_blame_header("author-tz ", &line)) {
5130 parse_timezone(&commit->time, line);
5132 } else if (match_blame_header("summary ", &line)) {
5133 string_ncopy(commit->title, line, strlen(line));
5135 } else if (match_blame_header("previous ", &line)) {
5136 if (strlen(line) <= SIZEOF_REV)
5137 return FALSE;
5138 string_copy_rev(commit->parent_id, line);
5139 line += SIZEOF_REV;
5140 string_ncopy(commit->parent_filename, line, strlen(line));
5142 } else if (match_blame_header("filename ", &line)) {
5143 string_ncopy(commit->filename, line, strlen(line));
5144 commit = NULL;
5145 }
5147 return TRUE;
5148 }
5150 static bool
5151 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5152 {
5153 struct blame *blame = line->data;
5154 struct time *time = NULL;
5155 const char *id = NULL, *author = NULL;
5157 if (blame->commit && *blame->commit->filename) {
5158 id = blame->commit->id;
5159 author = blame->commit->author;
5160 time = &blame->commit->time;
5161 }
5163 if (opt_date && draw_date(view, time))
5164 return TRUE;
5166 if (opt_author && draw_author(view, author))
5167 return TRUE;
5169 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5170 return TRUE;
5172 if (draw_lineno(view, lineno))
5173 return TRUE;
5175 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5176 return TRUE;
5177 }
5179 static bool
5180 check_blame_commit(struct blame *blame, bool check_null_id)
5181 {
5182 if (!blame->commit)
5183 report("Commit data not loaded yet");
5184 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5185 report("No commit exist for the selected line");
5186 else
5187 return TRUE;
5188 return FALSE;
5189 }
5191 static void
5192 setup_blame_parent_line(struct view *view, struct blame *blame)
5193 {
5194 char from[SIZEOF_REF + SIZEOF_STR];
5195 char to[SIZEOF_REF + SIZEOF_STR];
5196 const char *diff_tree_argv[] = {
5197 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5198 "-U0", from, to, "--", NULL
5199 };
5200 struct io io;
5201 int parent_lineno = -1;
5202 int blamed_lineno = -1;
5203 char *line;
5205 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5206 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5207 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5208 return;
5210 while ((line = io_get(&io, '\n', TRUE))) {
5211 if (*line == '@') {
5212 char *pos = strchr(line, '+');
5214 parent_lineno = atoi(line + 4);
5215 if (pos)
5216 blamed_lineno = atoi(pos + 1);
5218 } else if (*line == '+' && parent_lineno != -1) {
5219 if (blame->lineno == blamed_lineno - 1 &&
5220 !strcmp(blame->text, line + 1)) {
5221 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5222 break;
5223 }
5224 blamed_lineno++;
5225 }
5226 }
5228 io_done(&io);
5229 }
5231 static enum request
5232 blame_request(struct view *view, enum request request, struct line *line)
5233 {
5234 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5235 struct blame *blame = line->data;
5237 switch (request) {
5238 case REQ_VIEW_BLAME:
5239 if (check_blame_commit(blame, TRUE)) {
5240 string_copy(opt_ref, blame->commit->id);
5241 string_copy(opt_file, blame->commit->filename);
5242 if (blame->lineno)
5243 view->lineno = blame->lineno;
5244 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5245 }
5246 break;
5248 case REQ_PARENT:
5249 if (!check_blame_commit(blame, TRUE))
5250 break;
5251 if (!*blame->commit->parent_id) {
5252 report("The selected commit has no parents");
5253 } else {
5254 string_copy_rev(opt_ref, blame->commit->parent_id);
5255 string_copy(opt_file, blame->commit->parent_filename);
5256 setup_blame_parent_line(view, blame);
5257 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5258 }
5259 break;
5261 case REQ_ENTER:
5262 if (!check_blame_commit(blame, FALSE))
5263 break;
5265 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5266 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5267 break;
5269 if (!strcmp(blame->commit->id, NULL_ID)) {
5270 struct view *diff = VIEW(REQ_VIEW_DIFF);
5271 const char *diff_index_argv[] = {
5272 "git", "diff-index", "--root", "--patch-with-stat",
5273 "-C", "-M", "HEAD", "--", view->vid, NULL
5274 };
5276 if (!*blame->commit->parent_id) {
5277 diff_index_argv[1] = "diff";
5278 diff_index_argv[2] = "--no-color";
5279 diff_index_argv[6] = "--";
5280 diff_index_argv[7] = "/dev/null";
5281 }
5283 if (!prepare_update(diff, diff_index_argv, NULL)) {
5284 report("Failed to allocate diff command");
5285 break;
5286 }
5287 flags |= OPEN_PREPARED;
5288 }
5290 open_view(view, REQ_VIEW_DIFF, flags);
5291 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5292 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5293 break;
5295 default:
5296 return request;
5297 }
5299 return REQ_NONE;
5300 }
5302 static bool
5303 blame_grep(struct view *view, struct line *line)
5304 {
5305 struct blame *blame = line->data;
5306 struct blame_commit *commit = blame->commit;
5307 const char *text[] = {
5308 blame->text,
5309 commit ? commit->title : "",
5310 commit ? commit->id : "",
5311 commit && opt_author ? commit->author : "",
5312 commit ? mkdate(&commit->time, opt_date) : "",
5313 NULL
5314 };
5316 return grep_text(view, text);
5317 }
5319 static void
5320 blame_select(struct view *view, struct line *line)
5321 {
5322 struct blame *blame = line->data;
5323 struct blame_commit *commit = blame->commit;
5325 if (!commit)
5326 return;
5328 if (!strcmp(commit->id, NULL_ID))
5329 string_ncopy(ref_commit, "HEAD", 4);
5330 else
5331 string_copy_rev(ref_commit, commit->id);
5332 }
5334 static struct view_ops blame_ops = {
5335 "line",
5336 NULL,
5337 blame_open,
5338 blame_read,
5339 blame_draw,
5340 blame_request,
5341 blame_grep,
5342 blame_select,
5343 };
5345 /*
5346 * Branch backend
5347 */
5349 struct branch {
5350 const char *author; /* Author of the last commit. */
5351 struct time time; /* Date of the last activity. */
5352 const struct ref *ref; /* Name and commit ID information. */
5353 };
5355 static const struct ref branch_all;
5357 static const enum sort_field branch_sort_fields[] = {
5358 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5359 };
5360 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5362 static int
5363 branch_compare(const void *l1, const void *l2)
5364 {
5365 const struct branch *branch1 = ((const struct line *) l1)->data;
5366 const struct branch *branch2 = ((const struct line *) l2)->data;
5368 switch (get_sort_field(branch_sort_state)) {
5369 case ORDERBY_DATE:
5370 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5372 case ORDERBY_AUTHOR:
5373 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5375 case ORDERBY_NAME:
5376 default:
5377 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5378 }
5379 }
5381 static bool
5382 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5383 {
5384 struct branch *branch = line->data;
5385 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5387 if (opt_date && draw_date(view, &branch->time))
5388 return TRUE;
5390 if (opt_author && draw_author(view, branch->author))
5391 return TRUE;
5393 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5394 return TRUE;
5395 }
5397 static enum request
5398 branch_request(struct view *view, enum request request, struct line *line)
5399 {
5400 struct branch *branch = line->data;
5402 switch (request) {
5403 case REQ_REFRESH:
5404 load_refs();
5405 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5406 return REQ_NONE;
5408 case REQ_TOGGLE_SORT_FIELD:
5409 case REQ_TOGGLE_SORT_ORDER:
5410 sort_view(view, request, &branch_sort_state, branch_compare);
5411 return REQ_NONE;
5413 case REQ_ENTER:
5414 {
5415 const struct ref *ref = branch->ref;
5416 const char *all_branches_argv[] = {
5417 "git", "log", "--no-color", "--pretty=raw", "--parents",
5418 "--topo-order",
5419 ref == &branch_all ? "--all" : ref->name, NULL
5420 };
5421 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5423 if (!prepare_update(main_view, all_branches_argv, NULL))
5424 report("Failed to load view of all branches");
5425 else
5426 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5427 return REQ_NONE;
5428 }
5429 default:
5430 return request;
5431 }
5432 }
5434 static bool
5435 branch_read(struct view *view, char *line)
5436 {
5437 static char id[SIZEOF_REV];
5438 struct branch *reference;
5439 size_t i;
5441 if (!line)
5442 return TRUE;
5444 switch (get_line_type(line)) {
5445 case LINE_COMMIT:
5446 string_copy_rev(id, line + STRING_SIZE("commit "));
5447 return TRUE;
5449 case LINE_AUTHOR:
5450 for (i = 0, reference = NULL; i < view->lines; i++) {
5451 struct branch *branch = view->line[i].data;
5453 if (strcmp(branch->ref->id, id))
5454 continue;
5456 view->line[i].dirty = TRUE;
5457 if (reference) {
5458 branch->author = reference->author;
5459 branch->time = reference->time;
5460 continue;
5461 }
5463 parse_author_line(line + STRING_SIZE("author "),
5464 &branch->author, &branch->time);
5465 reference = branch;
5466 }
5467 return TRUE;
5469 default:
5470 return TRUE;
5471 }
5473 }
5475 static bool
5476 branch_open_visitor(void *data, const struct ref *ref)
5477 {
5478 struct view *view = data;
5479 struct branch *branch;
5481 if (ref->tag || ref->ltag || ref->remote)
5482 return TRUE;
5484 branch = calloc(1, sizeof(*branch));
5485 if (!branch)
5486 return FALSE;
5488 branch->ref = ref;
5489 return !!add_line_data(view, branch, LINE_DEFAULT);
5490 }
5492 static bool
5493 branch_open(struct view *view)
5494 {
5495 const char *branch_log[] = {
5496 "git", "log", "--no-color", "--pretty=raw",
5497 "--simplify-by-decoration", "--all", NULL
5498 };
5500 if (!start_update(view, branch_log, NULL)) {
5501 report("Failed to load branch data");
5502 return TRUE;
5503 }
5505 setup_update(view, view->id);
5506 branch_open_visitor(view, &branch_all);
5507 foreach_ref(branch_open_visitor, view);
5508 view->p_restore = TRUE;
5510 return TRUE;
5511 }
5513 static bool
5514 branch_grep(struct view *view, struct line *line)
5515 {
5516 struct branch *branch = line->data;
5517 const char *text[] = {
5518 branch->ref->name,
5519 branch->author,
5520 NULL
5521 };
5523 return grep_text(view, text);
5524 }
5526 static void
5527 branch_select(struct view *view, struct line *line)
5528 {
5529 struct branch *branch = line->data;
5531 string_copy_rev(view->ref, branch->ref->id);
5532 string_copy_rev(ref_commit, branch->ref->id);
5533 string_copy_rev(ref_head, branch->ref->id);
5534 string_copy_rev(ref_branch, branch->ref->name);
5535 }
5537 static struct view_ops branch_ops = {
5538 "branch",
5539 NULL,
5540 branch_open,
5541 branch_read,
5542 branch_draw,
5543 branch_request,
5544 branch_grep,
5545 branch_select,
5546 };
5548 /*
5549 * Status backend
5550 */
5552 struct status {
5553 char status;
5554 struct {
5555 mode_t mode;
5556 char rev[SIZEOF_REV];
5557 char name[SIZEOF_STR];
5558 } old;
5559 struct {
5560 mode_t mode;
5561 char rev[SIZEOF_REV];
5562 char name[SIZEOF_STR];
5563 } new;
5564 };
5566 static char status_onbranch[SIZEOF_STR];
5567 static struct status stage_status;
5568 static enum line_type stage_line_type;
5569 static size_t stage_chunks;
5570 static int *stage_chunk;
5572 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5574 /* This should work even for the "On branch" line. */
5575 static inline bool
5576 status_has_none(struct view *view, struct line *line)
5577 {
5578 return line < view->line + view->lines && !line[1].data;
5579 }
5581 /* Get fields from the diff line:
5582 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5583 */
5584 static inline bool
5585 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5586 {
5587 const char *old_mode = buf + 1;
5588 const char *new_mode = buf + 8;
5589 const char *old_rev = buf + 15;
5590 const char *new_rev = buf + 56;
5591 const char *status = buf + 97;
5593 if (bufsize < 98 ||
5594 old_mode[-1] != ':' ||
5595 new_mode[-1] != ' ' ||
5596 old_rev[-1] != ' ' ||
5597 new_rev[-1] != ' ' ||
5598 status[-1] != ' ')
5599 return FALSE;
5601 file->status = *status;
5603 string_copy_rev(file->old.rev, old_rev);
5604 string_copy_rev(file->new.rev, new_rev);
5606 file->old.mode = strtoul(old_mode, NULL, 8);
5607 file->new.mode = strtoul(new_mode, NULL, 8);
5609 file->old.name[0] = file->new.name[0] = 0;
5611 return TRUE;
5612 }
5614 static bool
5615 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5616 {
5617 struct status *unmerged = NULL;
5618 char *buf;
5619 struct io io;
5621 if (!io_run(&io, IO_RD, opt_cdup, argv))
5622 return FALSE;
5624 add_line_data(view, NULL, type);
5626 while ((buf = io_get(&io, 0, TRUE))) {
5627 struct status *file = unmerged;
5629 if (!file) {
5630 file = calloc(1, sizeof(*file));
5631 if (!file || !add_line_data(view, file, type))
5632 goto error_out;
5633 }
5635 /* Parse diff info part. */
5636 if (status) {
5637 file->status = status;
5638 if (status == 'A')
5639 string_copy(file->old.rev, NULL_ID);
5641 } else if (!file->status || file == unmerged) {
5642 if (!status_get_diff(file, buf, strlen(buf)))
5643 goto error_out;
5645 buf = io_get(&io, 0, TRUE);
5646 if (!buf)
5647 break;
5649 /* Collapse all modified entries that follow an
5650 * associated unmerged entry. */
5651 if (unmerged == file) {
5652 unmerged->status = 'U';
5653 unmerged = NULL;
5654 } else if (file->status == 'U') {
5655 unmerged = file;
5656 }
5657 }
5659 /* Grab the old name for rename/copy. */
5660 if (!*file->old.name &&
5661 (file->status == 'R' || file->status == 'C')) {
5662 string_ncopy(file->old.name, buf, strlen(buf));
5664 buf = io_get(&io, 0, TRUE);
5665 if (!buf)
5666 break;
5667 }
5669 /* git-ls-files just delivers a NUL separated list of
5670 * file names similar to the second half of the
5671 * git-diff-* output. */
5672 string_ncopy(file->new.name, buf, strlen(buf));
5673 if (!*file->old.name)
5674 string_copy(file->old.name, file->new.name);
5675 file = NULL;
5676 }
5678 if (io_error(&io)) {
5679 error_out:
5680 io_done(&io);
5681 return FALSE;
5682 }
5684 if (!view->line[view->lines - 1].data)
5685 add_line_data(view, NULL, LINE_STAT_NONE);
5687 io_done(&io);
5688 return TRUE;
5689 }
5691 /* Don't show unmerged entries in the staged section. */
5692 static const char *status_diff_index_argv[] = {
5693 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5694 "--cached", "-M", "HEAD", NULL
5695 };
5697 static const char *status_diff_files_argv[] = {
5698 "git", "diff-files", "-z", NULL
5699 };
5701 static const char *status_list_other_argv[] = {
5702 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5703 };
5705 static const char *status_list_no_head_argv[] = {
5706 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5707 };
5709 static const char *update_index_argv[] = {
5710 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5711 };
5713 /* Restore the previous line number to stay in the context or select a
5714 * line with something that can be updated. */
5715 static void
5716 status_restore(struct view *view)
5717 {
5718 if (view->p_lineno >= view->lines)
5719 view->p_lineno = view->lines - 1;
5720 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5721 view->p_lineno++;
5722 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5723 view->p_lineno--;
5725 /* If the above fails, always skip the "On branch" line. */
5726 if (view->p_lineno < view->lines)
5727 view->lineno = view->p_lineno;
5728 else
5729 view->lineno = 1;
5731 if (view->lineno < view->offset)
5732 view->offset = view->lineno;
5733 else if (view->offset + view->height <= view->lineno)
5734 view->offset = view->lineno - view->height + 1;
5736 view->p_restore = FALSE;
5737 }
5739 static void
5740 status_update_onbranch(void)
5741 {
5742 static const char *paths[][2] = {
5743 { "rebase-apply/rebasing", "Rebasing" },
5744 { "rebase-apply/applying", "Applying mailbox" },
5745 { "rebase-apply/", "Rebasing mailbox" },
5746 { "rebase-merge/interactive", "Interactive rebase" },
5747 { "rebase-merge/", "Rebase merge" },
5748 { "MERGE_HEAD", "Merging" },
5749 { "BISECT_LOG", "Bisecting" },
5750 { "HEAD", "On branch" },
5751 };
5752 char buf[SIZEOF_STR];
5753 struct stat stat;
5754 int i;
5756 if (is_initial_commit()) {
5757 string_copy(status_onbranch, "Initial commit");
5758 return;
5759 }
5761 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5762 char *head = opt_head;
5764 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5765 lstat(buf, &stat) < 0)
5766 continue;
5768 if (!*opt_head) {
5769 struct io io;
5771 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5772 io_read_buf(&io, buf, sizeof(buf))) {
5773 head = buf;
5774 if (!prefixcmp(head, "refs/heads/"))
5775 head += STRING_SIZE("refs/heads/");
5776 }
5777 }
5779 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5780 string_copy(status_onbranch, opt_head);
5781 return;
5782 }
5784 string_copy(status_onbranch, "Not currently on any branch");
5785 }
5787 /* First parse staged info using git-diff-index(1), then parse unstaged
5788 * info using git-diff-files(1), and finally untracked files using
5789 * git-ls-files(1). */
5790 static bool
5791 status_open(struct view *view)
5792 {
5793 reset_view(view);
5795 add_line_data(view, NULL, LINE_STAT_HEAD);
5796 status_update_onbranch();
5798 io_run_bg(update_index_argv);
5800 if (is_initial_commit()) {
5801 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5802 return FALSE;
5803 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5804 return FALSE;
5805 }
5807 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5808 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5809 return FALSE;
5811 /* Restore the exact position or use the specialized restore
5812 * mode? */
5813 if (!view->p_restore)
5814 status_restore(view);
5815 return TRUE;
5816 }
5818 static bool
5819 status_draw(struct view *view, struct line *line, unsigned int lineno)
5820 {
5821 struct status *status = line->data;
5822 enum line_type type;
5823 const char *text;
5825 if (!status) {
5826 switch (line->type) {
5827 case LINE_STAT_STAGED:
5828 type = LINE_STAT_SECTION;
5829 text = "Changes to be committed:";
5830 break;
5832 case LINE_STAT_UNSTAGED:
5833 type = LINE_STAT_SECTION;
5834 text = "Changed but not updated:";
5835 break;
5837 case LINE_STAT_UNTRACKED:
5838 type = LINE_STAT_SECTION;
5839 text = "Untracked files:";
5840 break;
5842 case LINE_STAT_NONE:
5843 type = LINE_DEFAULT;
5844 text = " (no files)";
5845 break;
5847 case LINE_STAT_HEAD:
5848 type = LINE_STAT_HEAD;
5849 text = status_onbranch;
5850 break;
5852 default:
5853 return FALSE;
5854 }
5855 } else {
5856 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5858 buf[0] = status->status;
5859 if (draw_text(view, line->type, buf, TRUE))
5860 return TRUE;
5861 type = LINE_DEFAULT;
5862 text = status->new.name;
5863 }
5865 draw_text(view, type, text, TRUE);
5866 return TRUE;
5867 }
5869 static enum request
5870 status_load_error(struct view *view, struct view *stage, const char *path)
5871 {
5872 if (displayed_views() == 2 || display[current_view] != view)
5873 maximize_view(view);
5874 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5875 return REQ_NONE;
5876 }
5878 static enum request
5879 status_enter(struct view *view, struct line *line)
5880 {
5881 struct status *status = line->data;
5882 const char *oldpath = status ? status->old.name : NULL;
5883 /* Diffs for unmerged entries are empty when passing the new
5884 * path, so leave it empty. */
5885 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5886 const char *info;
5887 enum open_flags split;
5888 struct view *stage = VIEW(REQ_VIEW_STAGE);
5890 if (line->type == LINE_STAT_NONE ||
5891 (!status && line[1].type == LINE_STAT_NONE)) {
5892 report("No file to diff");
5893 return REQ_NONE;
5894 }
5896 switch (line->type) {
5897 case LINE_STAT_STAGED:
5898 if (is_initial_commit()) {
5899 const char *no_head_diff_argv[] = {
5900 "git", "diff", "--no-color", "--patch-with-stat",
5901 "--", "/dev/null", newpath, NULL
5902 };
5904 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5905 return status_load_error(view, stage, newpath);
5906 } else {
5907 const char *index_show_argv[] = {
5908 "git", "diff-index", "--root", "--patch-with-stat",
5909 "-C", "-M", "--cached", "HEAD", "--",
5910 oldpath, newpath, NULL
5911 };
5913 if (!prepare_update(stage, index_show_argv, opt_cdup))
5914 return status_load_error(view, stage, newpath);
5915 }
5917 if (status)
5918 info = "Staged changes to %s";
5919 else
5920 info = "Staged changes";
5921 break;
5923 case LINE_STAT_UNSTAGED:
5924 {
5925 const char *files_show_argv[] = {
5926 "git", "diff-files", "--root", "--patch-with-stat",
5927 "-C", "-M", "--", oldpath, newpath, NULL
5928 };
5930 if (!prepare_update(stage, files_show_argv, opt_cdup))
5931 return status_load_error(view, stage, newpath);
5932 if (status)
5933 info = "Unstaged changes to %s";
5934 else
5935 info = "Unstaged changes";
5936 break;
5937 }
5938 case LINE_STAT_UNTRACKED:
5939 if (!newpath) {
5940 report("No file to show");
5941 return REQ_NONE;
5942 }
5944 if (!suffixcmp(status->new.name, -1, "/")) {
5945 report("Cannot display a directory");
5946 return REQ_NONE;
5947 }
5949 if (!prepare_update_file(stage, newpath))
5950 return status_load_error(view, stage, newpath);
5951 info = "Untracked file %s";
5952 break;
5954 case LINE_STAT_HEAD:
5955 return REQ_NONE;
5957 default:
5958 die("line type %d not handled in switch", line->type);
5959 }
5961 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5962 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5963 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5964 if (status) {
5965 stage_status = *status;
5966 } else {
5967 memset(&stage_status, 0, sizeof(stage_status));
5968 }
5970 stage_line_type = line->type;
5971 stage_chunks = 0;
5972 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5973 }
5975 return REQ_NONE;
5976 }
5978 static bool
5979 status_exists(struct status *status, enum line_type type)
5980 {
5981 struct view *view = VIEW(REQ_VIEW_STATUS);
5982 unsigned long lineno;
5984 for (lineno = 0; lineno < view->lines; lineno++) {
5985 struct line *line = &view->line[lineno];
5986 struct status *pos = line->data;
5988 if (line->type != type)
5989 continue;
5990 if (!pos && (!status || !status->status) && line[1].data) {
5991 select_view_line(view, lineno);
5992 return TRUE;
5993 }
5994 if (pos && !strcmp(status->new.name, pos->new.name)) {
5995 select_view_line(view, lineno);
5996 return TRUE;
5997 }
5998 }
6000 return FALSE;
6001 }
6004 static bool
6005 status_update_prepare(struct io *io, enum line_type type)
6006 {
6007 const char *staged_argv[] = {
6008 "git", "update-index", "-z", "--index-info", NULL
6009 };
6010 const char *others_argv[] = {
6011 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6012 };
6014 switch (type) {
6015 case LINE_STAT_STAGED:
6016 return io_run(io, IO_WR, opt_cdup, staged_argv);
6018 case LINE_STAT_UNSTAGED:
6019 case LINE_STAT_UNTRACKED:
6020 return io_run(io, IO_WR, opt_cdup, others_argv);
6022 default:
6023 die("line type %d not handled in switch", type);
6024 return FALSE;
6025 }
6026 }
6028 static bool
6029 status_update_write(struct io *io, struct status *status, enum line_type type)
6030 {
6031 char buf[SIZEOF_STR];
6032 size_t bufsize = 0;
6034 switch (type) {
6035 case LINE_STAT_STAGED:
6036 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6037 status->old.mode,
6038 status->old.rev,
6039 status->old.name, 0))
6040 return FALSE;
6041 break;
6043 case LINE_STAT_UNSTAGED:
6044 case LINE_STAT_UNTRACKED:
6045 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6046 return FALSE;
6047 break;
6049 default:
6050 die("line type %d not handled in switch", type);
6051 }
6053 return io_write(io, buf, bufsize);
6054 }
6056 static bool
6057 status_update_file(struct status *status, enum line_type type)
6058 {
6059 struct io io;
6060 bool result;
6062 if (!status_update_prepare(&io, type))
6063 return FALSE;
6065 result = status_update_write(&io, status, type);
6066 return io_done(&io) && result;
6067 }
6069 static bool
6070 status_update_files(struct view *view, struct line *line)
6071 {
6072 char buf[sizeof(view->ref)];
6073 struct io io;
6074 bool result = TRUE;
6075 struct line *pos = view->line + view->lines;
6076 int files = 0;
6077 int file, done;
6078 int cursor_y = -1, cursor_x = -1;
6080 if (!status_update_prepare(&io, line->type))
6081 return FALSE;
6083 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6084 files++;
6086 string_copy(buf, view->ref);
6087 getsyx(cursor_y, cursor_x);
6088 for (file = 0, done = 5; result && file < files; line++, file++) {
6089 int almost_done = file * 100 / files;
6091 if (almost_done > done) {
6092 done = almost_done;
6093 string_format(view->ref, "updating file %u of %u (%d%% done)",
6094 file, files, done);
6095 update_view_title(view);
6096 setsyx(cursor_y, cursor_x);
6097 doupdate();
6098 }
6099 result = status_update_write(&io, line->data, line->type);
6100 }
6101 string_copy(view->ref, buf);
6103 return io_done(&io) && result;
6104 }
6106 static bool
6107 status_update(struct view *view)
6108 {
6109 struct line *line = &view->line[view->lineno];
6111 assert(view->lines);
6113 if (!line->data) {
6114 /* This should work even for the "On branch" line. */
6115 if (line < view->line + view->lines && !line[1].data) {
6116 report("Nothing to update");
6117 return FALSE;
6118 }
6120 if (!status_update_files(view, line + 1)) {
6121 report("Failed to update file status");
6122 return FALSE;
6123 }
6125 } else if (!status_update_file(line->data, line->type)) {
6126 report("Failed to update file status");
6127 return FALSE;
6128 }
6130 return TRUE;
6131 }
6133 static bool
6134 status_revert(struct status *status, enum line_type type, bool has_none)
6135 {
6136 if (!status || type != LINE_STAT_UNSTAGED) {
6137 if (type == LINE_STAT_STAGED) {
6138 report("Cannot revert changes to staged files");
6139 } else if (type == LINE_STAT_UNTRACKED) {
6140 report("Cannot revert changes to untracked files");
6141 } else if (has_none) {
6142 report("Nothing to revert");
6143 } else {
6144 report("Cannot revert changes to multiple files");
6145 }
6147 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6148 char mode[10] = "100644";
6149 const char *reset_argv[] = {
6150 "git", "update-index", "--cacheinfo", mode,
6151 status->old.rev, status->old.name, NULL
6152 };
6153 const char *checkout_argv[] = {
6154 "git", "checkout", "--", status->old.name, NULL
6155 };
6157 if (status->status == 'U') {
6158 string_format(mode, "%5o", status->old.mode);
6160 if (status->old.mode == 0 && status->new.mode == 0) {
6161 reset_argv[2] = "--force-remove";
6162 reset_argv[3] = status->old.name;
6163 reset_argv[4] = NULL;
6164 }
6166 if (!io_run_fg(reset_argv, opt_cdup))
6167 return FALSE;
6168 if (status->old.mode == 0 && status->new.mode == 0)
6169 return TRUE;
6170 }
6172 return io_run_fg(checkout_argv, opt_cdup);
6173 }
6175 return FALSE;
6176 }
6178 static enum request
6179 status_request(struct view *view, enum request request, struct line *line)
6180 {
6181 struct status *status = line->data;
6183 switch (request) {
6184 case REQ_STATUS_UPDATE:
6185 if (!status_update(view))
6186 return REQ_NONE;
6187 break;
6189 case REQ_STATUS_REVERT:
6190 if (!status_revert(status, line->type, status_has_none(view, line)))
6191 return REQ_NONE;
6192 break;
6194 case REQ_STATUS_MERGE:
6195 if (!status || status->status != 'U') {
6196 report("Merging only possible for files with unmerged status ('U').");
6197 return REQ_NONE;
6198 }
6199 open_mergetool(status->new.name);
6200 break;
6202 case REQ_EDIT:
6203 if (!status)
6204 return request;
6205 if (status->status == 'D') {
6206 report("File has been deleted.");
6207 return REQ_NONE;
6208 }
6210 open_editor(status->new.name);
6211 break;
6213 case REQ_VIEW_BLAME:
6214 if (status)
6215 opt_ref[0] = 0;
6216 return request;
6218 case REQ_ENTER:
6219 /* After returning the status view has been split to
6220 * show the stage view. No further reloading is
6221 * necessary. */
6222 return status_enter(view, line);
6224 case REQ_REFRESH:
6225 /* Simply reload the view. */
6226 break;
6228 default:
6229 return request;
6230 }
6232 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6234 return REQ_NONE;
6235 }
6237 static void
6238 status_select(struct view *view, struct line *line)
6239 {
6240 struct status *status = line->data;
6241 char file[SIZEOF_STR] = "all files";
6242 const char *text;
6243 const char *key;
6245 if (status && !string_format(file, "'%s'", status->new.name))
6246 return;
6248 if (!status && line[1].type == LINE_STAT_NONE)
6249 line++;
6251 switch (line->type) {
6252 case LINE_STAT_STAGED:
6253 text = "Press %s to unstage %s for commit";
6254 break;
6256 case LINE_STAT_UNSTAGED:
6257 text = "Press %s to stage %s for commit";
6258 break;
6260 case LINE_STAT_UNTRACKED:
6261 text = "Press %s to stage %s for addition";
6262 break;
6264 case LINE_STAT_HEAD:
6265 case LINE_STAT_NONE:
6266 text = "Nothing to update";
6267 break;
6269 default:
6270 die("line type %d not handled in switch", line->type);
6271 }
6273 if (status && status->status == 'U') {
6274 text = "Press %s to resolve conflict in %s";
6275 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6277 } else {
6278 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6279 }
6281 string_format(view->ref, text, key, file);
6282 if (status)
6283 string_copy(opt_file, status->new.name);
6284 }
6286 static bool
6287 status_grep(struct view *view, struct line *line)
6288 {
6289 struct status *status = line->data;
6291 if (status) {
6292 const char buf[2] = { status->status, 0 };
6293 const char *text[] = { status->new.name, buf, NULL };
6295 return grep_text(view, text);
6296 }
6298 return FALSE;
6299 }
6301 static struct view_ops status_ops = {
6302 "file",
6303 NULL,
6304 status_open,
6305 NULL,
6306 status_draw,
6307 status_request,
6308 status_grep,
6309 status_select,
6310 };
6313 static bool
6314 stage_diff_write(struct io *io, struct line *line, struct line *end)
6315 {
6316 while (line < end) {
6317 if (!io_write(io, line->data, strlen(line->data)) ||
6318 !io_write(io, "\n", 1))
6319 return FALSE;
6320 line++;
6321 if (line->type == LINE_DIFF_CHUNK ||
6322 line->type == LINE_DIFF_HEADER)
6323 break;
6324 }
6326 return TRUE;
6327 }
6329 static struct line *
6330 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6331 {
6332 for (; view->line < line; line--)
6333 if (line->type == type)
6334 return line;
6336 return NULL;
6337 }
6339 static bool
6340 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6341 {
6342 const char *apply_argv[SIZEOF_ARG] = {
6343 "git", "apply", "--whitespace=nowarn", NULL
6344 };
6345 struct line *diff_hdr;
6346 struct io io;
6347 int argc = 3;
6349 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6350 if (!diff_hdr)
6351 return FALSE;
6353 if (!revert)
6354 apply_argv[argc++] = "--cached";
6355 if (revert || stage_line_type == LINE_STAT_STAGED)
6356 apply_argv[argc++] = "-R";
6357 apply_argv[argc++] = "-";
6358 apply_argv[argc++] = NULL;
6359 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6360 return FALSE;
6362 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6363 !stage_diff_write(&io, chunk, view->line + view->lines))
6364 chunk = NULL;
6366 io_done(&io);
6367 io_run_bg(update_index_argv);
6369 return chunk ? TRUE : FALSE;
6370 }
6372 static bool
6373 stage_update(struct view *view, struct line *line)
6374 {
6375 struct line *chunk = NULL;
6377 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6378 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6380 if (chunk) {
6381 if (!stage_apply_chunk(view, chunk, FALSE)) {
6382 report("Failed to apply chunk");
6383 return FALSE;
6384 }
6386 } else if (!stage_status.status) {
6387 view = VIEW(REQ_VIEW_STATUS);
6389 for (line = view->line; line < view->line + view->lines; line++)
6390 if (line->type == stage_line_type)
6391 break;
6393 if (!status_update_files(view, line + 1)) {
6394 report("Failed to update files");
6395 return FALSE;
6396 }
6398 } else if (!status_update_file(&stage_status, stage_line_type)) {
6399 report("Failed to update file");
6400 return FALSE;
6401 }
6403 return TRUE;
6404 }
6406 static bool
6407 stage_revert(struct view *view, struct line *line)
6408 {
6409 struct line *chunk = NULL;
6411 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6412 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6414 if (chunk) {
6415 if (!prompt_yesno("Are you sure you want to revert changes?"))
6416 return FALSE;
6418 if (!stage_apply_chunk(view, chunk, TRUE)) {
6419 report("Failed to revert chunk");
6420 return FALSE;
6421 }
6422 return TRUE;
6424 } else {
6425 return status_revert(stage_status.status ? &stage_status : NULL,
6426 stage_line_type, FALSE);
6427 }
6428 }
6431 static void
6432 stage_next(struct view *view, struct line *line)
6433 {
6434 int i;
6436 if (!stage_chunks) {
6437 for (line = view->line; line < view->line + view->lines; line++) {
6438 if (line->type != LINE_DIFF_CHUNK)
6439 continue;
6441 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6442 report("Allocation failure");
6443 return;
6444 }
6446 stage_chunk[stage_chunks++] = line - view->line;
6447 }
6448 }
6450 for (i = 0; i < stage_chunks; i++) {
6451 if (stage_chunk[i] > view->lineno) {
6452 do_scroll_view(view, stage_chunk[i] - view->lineno);
6453 report("Chunk %d of %d", i + 1, stage_chunks);
6454 return;
6455 }
6456 }
6458 report("No next chunk found");
6459 }
6461 static enum request
6462 stage_request(struct view *view, enum request request, struct line *line)
6463 {
6464 switch (request) {
6465 case REQ_STATUS_UPDATE:
6466 if (!stage_update(view, line))
6467 return REQ_NONE;
6468 break;
6470 case REQ_STATUS_REVERT:
6471 if (!stage_revert(view, line))
6472 return REQ_NONE;
6473 break;
6475 case REQ_STAGE_NEXT:
6476 if (stage_line_type == LINE_STAT_UNTRACKED) {
6477 report("File is untracked; press %s to add",
6478 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6479 return REQ_NONE;
6480 }
6481 stage_next(view, line);
6482 return REQ_NONE;
6484 case REQ_EDIT:
6485 if (!stage_status.new.name[0])
6486 return request;
6487 if (stage_status.status == 'D') {
6488 report("File has been deleted.");
6489 return REQ_NONE;
6490 }
6492 open_editor(stage_status.new.name);
6493 break;
6495 case REQ_REFRESH:
6496 /* Reload everything ... */
6497 break;
6499 case REQ_VIEW_BLAME:
6500 if (stage_status.new.name[0]) {
6501 string_copy(opt_file, stage_status.new.name);
6502 opt_ref[0] = 0;
6503 }
6504 return request;
6506 case REQ_ENTER:
6507 return pager_request(view, request, line);
6509 default:
6510 return request;
6511 }
6513 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6514 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6516 /* Check whether the staged entry still exists, and close the
6517 * stage view if it doesn't. */
6518 if (!status_exists(&stage_status, stage_line_type)) {
6519 status_restore(VIEW(REQ_VIEW_STATUS));
6520 return REQ_VIEW_CLOSE;
6521 }
6523 if (stage_line_type == LINE_STAT_UNTRACKED) {
6524 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6525 report("Cannot display a directory");
6526 return REQ_NONE;
6527 }
6529 if (!prepare_update_file(view, stage_status.new.name)) {
6530 report("Failed to open file: %s", strerror(errno));
6531 return REQ_NONE;
6532 }
6533 }
6534 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6536 return REQ_NONE;
6537 }
6539 static struct view_ops stage_ops = {
6540 "line",
6541 NULL,
6542 NULL,
6543 pager_read,
6544 pager_draw,
6545 stage_request,
6546 pager_grep,
6547 pager_select,
6548 };
6551 /*
6552 * Revision graph
6553 */
6555 struct commit {
6556 char id[SIZEOF_REV]; /* SHA1 ID. */
6557 char title[128]; /* First line of the commit message. */
6558 const char *author; /* Author of the commit. */
6559 struct time time; /* Date from the author ident. */
6560 struct ref_list *refs; /* Repository references. */
6561 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6562 size_t graph_size; /* The width of the graph array. */
6563 bool has_parents; /* Rewritten --parents seen. */
6564 };
6566 /* Size of rev graph with no "padding" columns */
6567 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6569 struct rev_graph {
6570 struct rev_graph *prev, *next, *parents;
6571 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6572 size_t size;
6573 struct commit *commit;
6574 size_t pos;
6575 unsigned int boundary:1;
6576 };
6578 /* Parents of the commit being visualized. */
6579 static struct rev_graph graph_parents[4];
6581 /* The current stack of revisions on the graph. */
6582 static struct rev_graph graph_stacks[4] = {
6583 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6584 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6585 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6586 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6587 };
6589 static inline bool
6590 graph_parent_is_merge(struct rev_graph *graph)
6591 {
6592 return graph->parents->size > 1;
6593 }
6595 static inline void
6596 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6597 {
6598 struct commit *commit = graph->commit;
6600 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6601 commit->graph[commit->graph_size++] = symbol;
6602 }
6604 static void
6605 clear_rev_graph(struct rev_graph *graph)
6606 {
6607 graph->boundary = 0;
6608 graph->size = graph->pos = 0;
6609 graph->commit = NULL;
6610 memset(graph->parents, 0, sizeof(*graph->parents));
6611 }
6613 static void
6614 done_rev_graph(struct rev_graph *graph)
6615 {
6616 if (graph_parent_is_merge(graph) &&
6617 graph->pos < graph->size - 1 &&
6618 graph->next->size == graph->size + graph->parents->size - 1) {
6619 size_t i = graph->pos + graph->parents->size - 1;
6621 graph->commit->graph_size = i * 2;
6622 while (i < graph->next->size - 1) {
6623 append_to_rev_graph(graph, ' ');
6624 append_to_rev_graph(graph, '\\');
6625 i++;
6626 }
6627 }
6629 clear_rev_graph(graph);
6630 }
6632 static void
6633 push_rev_graph(struct rev_graph *graph, const char *parent)
6634 {
6635 int i;
6637 /* "Collapse" duplicate parents lines.
6638 *
6639 * FIXME: This needs to also update update the drawn graph but
6640 * for now it just serves as a method for pruning graph lines. */
6641 for (i = 0; i < graph->size; i++)
6642 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6643 return;
6645 if (graph->size < SIZEOF_REVITEMS) {
6646 string_copy_rev(graph->rev[graph->size++], parent);
6647 }
6648 }
6650 static chtype
6651 get_rev_graph_symbol(struct rev_graph *graph)
6652 {
6653 chtype symbol;
6655 if (graph->boundary)
6656 symbol = REVGRAPH_BOUND;
6657 else if (graph->parents->size == 0)
6658 symbol = REVGRAPH_INIT;
6659 else if (graph_parent_is_merge(graph))
6660 symbol = REVGRAPH_MERGE;
6661 else if (graph->pos >= graph->size)
6662 symbol = REVGRAPH_BRANCH;
6663 else
6664 symbol = REVGRAPH_COMMIT;
6666 return symbol;
6667 }
6669 static void
6670 draw_rev_graph(struct rev_graph *graph)
6671 {
6672 struct rev_filler {
6673 chtype separator, line;
6674 };
6675 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6676 static struct rev_filler fillers[] = {
6677 { ' ', '|' },
6678 { '`', '.' },
6679 { '\'', ' ' },
6680 { '/', ' ' },
6681 };
6682 chtype symbol = get_rev_graph_symbol(graph);
6683 struct rev_filler *filler;
6684 size_t i;
6686 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6687 filler = &fillers[DEFAULT];
6689 for (i = 0; i < graph->pos; i++) {
6690 append_to_rev_graph(graph, filler->line);
6691 if (graph_parent_is_merge(graph->prev) &&
6692 graph->prev->pos == i)
6693 filler = &fillers[RSHARP];
6695 append_to_rev_graph(graph, filler->separator);
6696 }
6698 /* Place the symbol for this revision. */
6699 append_to_rev_graph(graph, symbol);
6701 if (graph->prev->size > graph->size)
6702 filler = &fillers[RDIAG];
6703 else
6704 filler = &fillers[DEFAULT];
6706 i++;
6708 for (; i < graph->size; i++) {
6709 append_to_rev_graph(graph, filler->separator);
6710 append_to_rev_graph(graph, filler->line);
6711 if (graph_parent_is_merge(graph->prev) &&
6712 i < graph->prev->pos + graph->parents->size)
6713 filler = &fillers[RSHARP];
6714 if (graph->prev->size > graph->size)
6715 filler = &fillers[LDIAG];
6716 }
6718 if (graph->prev->size > graph->size) {
6719 append_to_rev_graph(graph, filler->separator);
6720 if (filler->line != ' ')
6721 append_to_rev_graph(graph, filler->line);
6722 }
6723 }
6725 /* Prepare the next rev graph */
6726 static void
6727 prepare_rev_graph(struct rev_graph *graph)
6728 {
6729 size_t i;
6731 /* First, traverse all lines of revisions up to the active one. */
6732 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6733 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6734 break;
6736 push_rev_graph(graph->next, graph->rev[graph->pos]);
6737 }
6739 /* Interleave the new revision parent(s). */
6740 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6741 push_rev_graph(graph->next, graph->parents->rev[i]);
6743 /* Lastly, put any remaining revisions. */
6744 for (i = graph->pos + 1; i < graph->size; i++)
6745 push_rev_graph(graph->next, graph->rev[i]);
6746 }
6748 static void
6749 update_rev_graph(struct view *view, struct rev_graph *graph)
6750 {
6751 /* If this is the finalizing update ... */
6752 if (graph->commit)
6753 prepare_rev_graph(graph);
6755 /* Graph visualization needs a one rev look-ahead,
6756 * so the first update doesn't visualize anything. */
6757 if (!graph->prev->commit)
6758 return;
6760 if (view->lines > 2)
6761 view->line[view->lines - 3].dirty = 1;
6762 if (view->lines > 1)
6763 view->line[view->lines - 2].dirty = 1;
6764 draw_rev_graph(graph->prev);
6765 done_rev_graph(graph->prev->prev);
6766 }
6769 /*
6770 * Main view backend
6771 */
6773 static const char *main_argv[SIZEOF_ARG] = {
6774 "git", "log", "--no-color", "--pretty=raw", "--parents",
6775 "--topo-order", "%(diffargs)", "%(revargs)",
6776 "--", "%(fileargs)", NULL
6777 };
6779 static bool
6780 main_draw(struct view *view, struct line *line, unsigned int lineno)
6781 {
6782 struct commit *commit = line->data;
6784 if (!commit->author)
6785 return FALSE;
6787 if (opt_date && draw_date(view, &commit->time))
6788 return TRUE;
6790 if (opt_author && draw_author(view, commit->author))
6791 return TRUE;
6793 if (opt_rev_graph && commit->graph_size &&
6794 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6795 return TRUE;
6797 if (opt_show_refs && commit->refs) {
6798 size_t i;
6800 for (i = 0; i < commit->refs->size; i++) {
6801 struct ref *ref = commit->refs->refs[i];
6802 enum line_type type;
6804 if (ref->head)
6805 type = LINE_MAIN_HEAD;
6806 else if (ref->ltag)
6807 type = LINE_MAIN_LOCAL_TAG;
6808 else if (ref->tag)
6809 type = LINE_MAIN_TAG;
6810 else if (ref->tracked)
6811 type = LINE_MAIN_TRACKED;
6812 else if (ref->remote)
6813 type = LINE_MAIN_REMOTE;
6814 else
6815 type = LINE_MAIN_REF;
6817 if (draw_text(view, type, "[", TRUE) ||
6818 draw_text(view, type, ref->name, TRUE) ||
6819 draw_text(view, type, "]", TRUE))
6820 return TRUE;
6822 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6823 return TRUE;
6824 }
6825 }
6827 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6828 return TRUE;
6829 }
6831 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6832 static bool
6833 main_read(struct view *view, char *line)
6834 {
6835 static struct rev_graph *graph = graph_stacks;
6836 enum line_type type;
6837 struct commit *commit;
6839 if (!line) {
6840 int i;
6842 if (!view->lines && !view->prev)
6843 die("No revisions match the given arguments.");
6844 if (view->lines > 0) {
6845 commit = view->line[view->lines - 1].data;
6846 view->line[view->lines - 1].dirty = 1;
6847 if (!commit->author) {
6848 view->lines--;
6849 free(commit);
6850 graph->commit = NULL;
6851 }
6852 }
6853 update_rev_graph(view, graph);
6855 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6856 clear_rev_graph(&graph_stacks[i]);
6857 return TRUE;
6858 }
6860 type = get_line_type(line);
6861 if (type == LINE_COMMIT) {
6862 commit = calloc(1, sizeof(struct commit));
6863 if (!commit)
6864 return FALSE;
6866 line += STRING_SIZE("commit ");
6867 if (*line == '-') {
6868 graph->boundary = 1;
6869 line++;
6870 }
6872 string_copy_rev(commit->id, line);
6873 commit->refs = get_ref_list(commit->id);
6874 graph->commit = commit;
6875 add_line_data(view, commit, LINE_MAIN_COMMIT);
6877 while ((line = strchr(line, ' '))) {
6878 line++;
6879 push_rev_graph(graph->parents, line);
6880 commit->has_parents = TRUE;
6881 }
6882 return TRUE;
6883 }
6885 if (!view->lines)
6886 return TRUE;
6887 commit = view->line[view->lines - 1].data;
6889 switch (type) {
6890 case LINE_PARENT:
6891 if (commit->has_parents)
6892 break;
6893 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6894 break;
6896 case LINE_AUTHOR:
6897 parse_author_line(line + STRING_SIZE("author "),
6898 &commit->author, &commit->time);
6899 update_rev_graph(view, graph);
6900 graph = graph->next;
6901 break;
6903 default:
6904 /* Fill in the commit title if it has not already been set. */
6905 if (commit->title[0])
6906 break;
6908 /* Require titles to start with a non-space character at the
6909 * offset used by git log. */
6910 if (strncmp(line, " ", 4))
6911 break;
6912 line += 4;
6913 /* Well, if the title starts with a whitespace character,
6914 * try to be forgiving. Otherwise we end up with no title. */
6915 while (isspace(*line))
6916 line++;
6917 if (*line == '\0')
6918 break;
6919 /* FIXME: More graceful handling of titles; append "..." to
6920 * shortened titles, etc. */
6922 string_expand(commit->title, sizeof(commit->title), line, 1);
6923 view->line[view->lines - 1].dirty = 1;
6924 }
6926 return TRUE;
6927 }
6929 static enum request
6930 main_request(struct view *view, enum request request, struct line *line)
6931 {
6932 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6934 switch (request) {
6935 case REQ_ENTER:
6936 open_view(view, REQ_VIEW_DIFF, flags);
6937 break;
6938 case REQ_REFRESH:
6939 load_refs();
6940 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6941 break;
6942 default:
6943 return request;
6944 }
6946 return REQ_NONE;
6947 }
6949 static bool
6950 grep_refs(struct ref_list *list, regex_t *regex)
6951 {
6952 regmatch_t pmatch;
6953 size_t i;
6955 if (!opt_show_refs || !list)
6956 return FALSE;
6958 for (i = 0; i < list->size; i++) {
6959 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6960 return TRUE;
6961 }
6963 return FALSE;
6964 }
6966 static bool
6967 main_grep(struct view *view, struct line *line)
6968 {
6969 struct commit *commit = line->data;
6970 const char *text[] = {
6971 commit->title,
6972 opt_author ? commit->author : "",
6973 mkdate(&commit->time, opt_date),
6974 NULL
6975 };
6977 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6978 }
6980 static void
6981 main_select(struct view *view, struct line *line)
6982 {
6983 struct commit *commit = line->data;
6985 string_copy_rev(view->ref, commit->id);
6986 string_copy_rev(ref_commit, view->ref);
6987 }
6989 static struct view_ops main_ops = {
6990 "commit",
6991 main_argv,
6992 NULL,
6993 main_read,
6994 main_draw,
6995 main_request,
6996 main_grep,
6997 main_select,
6998 };
7001 /*
7002 * Status management
7003 */
7005 /* Whether or not the curses interface has been initialized. */
7006 static bool cursed = FALSE;
7008 /* Terminal hacks and workarounds. */
7009 static bool use_scroll_redrawwin;
7010 static bool use_scroll_status_wclear;
7012 /* The status window is used for polling keystrokes. */
7013 static WINDOW *status_win;
7015 /* Reading from the prompt? */
7016 static bool input_mode = FALSE;
7018 static bool status_empty = FALSE;
7020 /* Update status and title window. */
7021 static void
7022 report(const char *msg, ...)
7023 {
7024 struct view *view = display[current_view];
7026 if (input_mode)
7027 return;
7029 if (!view) {
7030 char buf[SIZEOF_STR];
7031 va_list args;
7033 va_start(args, msg);
7034 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7035 buf[sizeof(buf) - 1] = 0;
7036 buf[sizeof(buf) - 2] = '.';
7037 buf[sizeof(buf) - 3] = '.';
7038 buf[sizeof(buf) - 4] = '.';
7039 }
7040 va_end(args);
7041 die("%s", buf);
7042 }
7044 if (!status_empty || *msg) {
7045 va_list args;
7047 va_start(args, msg);
7049 wmove(status_win, 0, 0);
7050 if (view->has_scrolled && use_scroll_status_wclear)
7051 wclear(status_win);
7052 if (*msg) {
7053 vwprintw(status_win, msg, args);
7054 status_empty = FALSE;
7055 } else {
7056 status_empty = TRUE;
7057 }
7058 wclrtoeol(status_win);
7059 wnoutrefresh(status_win);
7061 va_end(args);
7062 }
7064 update_view_title(view);
7065 }
7067 static void
7068 init_display(void)
7069 {
7070 const char *term;
7071 int x, y;
7073 /* Initialize the curses library */
7074 if (isatty(STDIN_FILENO)) {
7075 cursed = !!initscr();
7076 opt_tty = stdin;
7077 } else {
7078 /* Leave stdin and stdout alone when acting as a pager. */
7079 opt_tty = fopen("/dev/tty", "r+");
7080 if (!opt_tty)
7081 die("Failed to open /dev/tty");
7082 cursed = !!newterm(NULL, opt_tty, opt_tty);
7083 }
7085 if (!cursed)
7086 die("Failed to initialize curses");
7088 nonl(); /* Disable conversion and detect newlines from input. */
7089 cbreak(); /* Take input chars one at a time, no wait for \n */
7090 noecho(); /* Don't echo input */
7091 leaveok(stdscr, FALSE);
7093 if (has_colors())
7094 init_colors();
7096 getmaxyx(stdscr, y, x);
7097 status_win = newwin(1, 0, y - 1, 0);
7098 if (!status_win)
7099 die("Failed to create status window");
7101 /* Enable keyboard mapping */
7102 keypad(status_win, TRUE);
7103 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7105 TABSIZE = opt_tab_size;
7107 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7108 if (term && !strcmp(term, "gnome-terminal")) {
7109 /* In the gnome-terminal-emulator, the message from
7110 * scrolling up one line when impossible followed by
7111 * scrolling down one line causes corruption of the
7112 * status line. This is fixed by calling wclear. */
7113 use_scroll_status_wclear = TRUE;
7114 use_scroll_redrawwin = FALSE;
7116 } else if (term && !strcmp(term, "xrvt-xpm")) {
7117 /* No problems with full optimizations in xrvt-(unicode)
7118 * and aterm. */
7119 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7121 } else {
7122 /* When scrolling in (u)xterm the last line in the
7123 * scrolling direction will update slowly. */
7124 use_scroll_redrawwin = TRUE;
7125 use_scroll_status_wclear = FALSE;
7126 }
7127 }
7129 static int
7130 get_input(int prompt_position)
7131 {
7132 struct view *view;
7133 int i, key, cursor_y, cursor_x;
7135 if (prompt_position)
7136 input_mode = TRUE;
7138 while (TRUE) {
7139 bool loading = FALSE;
7141 foreach_view (view, i) {
7142 update_view(view);
7143 if (view_is_displayed(view) && view->has_scrolled &&
7144 use_scroll_redrawwin)
7145 redrawwin(view->win);
7146 view->has_scrolled = FALSE;
7147 if (view->pipe)
7148 loading = TRUE;
7149 }
7151 /* Update the cursor position. */
7152 if (prompt_position) {
7153 getbegyx(status_win, cursor_y, cursor_x);
7154 cursor_x = prompt_position;
7155 } else {
7156 view = display[current_view];
7157 getbegyx(view->win, cursor_y, cursor_x);
7158 cursor_x = view->width - 1;
7159 cursor_y += view->lineno - view->offset;
7160 }
7161 setsyx(cursor_y, cursor_x);
7163 /* Refresh, accept single keystroke of input */
7164 doupdate();
7165 nodelay(status_win, loading);
7166 key = wgetch(status_win);
7168 /* wgetch() with nodelay() enabled returns ERR when
7169 * there's no input. */
7170 if (key == ERR) {
7172 } else if (key == KEY_RESIZE) {
7173 int height, width;
7175 getmaxyx(stdscr, height, width);
7177 wresize(status_win, 1, width);
7178 mvwin(status_win, height - 1, 0);
7179 wnoutrefresh(status_win);
7180 resize_display();
7181 redraw_display(TRUE);
7183 } else {
7184 input_mode = FALSE;
7185 return key;
7186 }
7187 }
7188 }
7190 static char *
7191 prompt_input(const char *prompt, input_handler handler, void *data)
7192 {
7193 enum input_status status = INPUT_OK;
7194 static char buf[SIZEOF_STR];
7195 size_t pos = 0;
7197 buf[pos] = 0;
7199 while (status == INPUT_OK || status == INPUT_SKIP) {
7200 int key;
7202 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7203 wclrtoeol(status_win);
7205 key = get_input(pos + 1);
7206 switch (key) {
7207 case KEY_RETURN:
7208 case KEY_ENTER:
7209 case '\n':
7210 status = pos ? INPUT_STOP : INPUT_CANCEL;
7211 break;
7213 case KEY_BACKSPACE:
7214 if (pos > 0)
7215 buf[--pos] = 0;
7216 else
7217 status = INPUT_CANCEL;
7218 break;
7220 case KEY_ESC:
7221 status = INPUT_CANCEL;
7222 break;
7224 default:
7225 if (pos >= sizeof(buf)) {
7226 report("Input string too long");
7227 return NULL;
7228 }
7230 status = handler(data, buf, key);
7231 if (status == INPUT_OK)
7232 buf[pos++] = (char) key;
7233 }
7234 }
7236 /* Clear the status window */
7237 status_empty = FALSE;
7238 report("");
7240 if (status == INPUT_CANCEL)
7241 return NULL;
7243 buf[pos++] = 0;
7245 return buf;
7246 }
7248 static enum input_status
7249 prompt_yesno_handler(void *data, char *buf, int c)
7250 {
7251 if (c == 'y' || c == 'Y')
7252 return INPUT_STOP;
7253 if (c == 'n' || c == 'N')
7254 return INPUT_CANCEL;
7255 return INPUT_SKIP;
7256 }
7258 static bool
7259 prompt_yesno(const char *prompt)
7260 {
7261 char prompt2[SIZEOF_STR];
7263 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7264 return FALSE;
7266 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7267 }
7269 static enum input_status
7270 read_prompt_handler(void *data, char *buf, int c)
7271 {
7272 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7273 }
7275 static char *
7276 read_prompt(const char *prompt)
7277 {
7278 return prompt_input(prompt, read_prompt_handler, NULL);
7279 }
7281 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7282 {
7283 enum input_status status = INPUT_OK;
7284 int size = 0;
7286 while (items[size].text)
7287 size++;
7289 while (status == INPUT_OK) {
7290 const struct menu_item *item = &items[*selected];
7291 int key;
7292 int i;
7294 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7295 prompt, *selected + 1, size);
7296 if (item->hotkey)
7297 wprintw(status_win, "[%c] ", (char) item->hotkey);
7298 wprintw(status_win, "%s", item->text);
7299 wclrtoeol(status_win);
7301 key = get_input(COLS - 1);
7302 switch (key) {
7303 case KEY_RETURN:
7304 case KEY_ENTER:
7305 case '\n':
7306 status = INPUT_STOP;
7307 break;
7309 case KEY_LEFT:
7310 case KEY_UP:
7311 *selected = *selected - 1;
7312 if (*selected < 0)
7313 *selected = size - 1;
7314 break;
7316 case KEY_RIGHT:
7317 case KEY_DOWN:
7318 *selected = (*selected + 1) % size;
7319 break;
7321 case KEY_ESC:
7322 status = INPUT_CANCEL;
7323 break;
7325 default:
7326 for (i = 0; items[i].text; i++)
7327 if (items[i].hotkey == key) {
7328 *selected = i;
7329 status = INPUT_STOP;
7330 break;
7331 }
7332 }
7333 }
7335 /* Clear the status window */
7336 status_empty = FALSE;
7337 report("");
7339 return status != INPUT_CANCEL;
7340 }
7342 /*
7343 * Repository properties
7344 */
7346 static struct ref **refs = NULL;
7347 static size_t refs_size = 0;
7348 static struct ref *refs_head = NULL;
7350 static struct ref_list **ref_lists = NULL;
7351 static size_t ref_lists_size = 0;
7353 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7354 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7355 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7357 static int
7358 compare_refs(const void *ref1_, const void *ref2_)
7359 {
7360 const struct ref *ref1 = *(const struct ref **)ref1_;
7361 const struct ref *ref2 = *(const struct ref **)ref2_;
7363 if (ref1->tag != ref2->tag)
7364 return ref2->tag - ref1->tag;
7365 if (ref1->ltag != ref2->ltag)
7366 return ref2->ltag - ref2->ltag;
7367 if (ref1->head != ref2->head)
7368 return ref2->head - ref1->head;
7369 if (ref1->tracked != ref2->tracked)
7370 return ref2->tracked - ref1->tracked;
7371 if (ref1->remote != ref2->remote)
7372 return ref2->remote - ref1->remote;
7373 return strcmp(ref1->name, ref2->name);
7374 }
7376 static void
7377 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7378 {
7379 size_t i;
7381 for (i = 0; i < refs_size; i++)
7382 if (!visitor(data, refs[i]))
7383 break;
7384 }
7386 static struct ref *
7387 get_ref_head()
7388 {
7389 return refs_head;
7390 }
7392 static struct ref_list *
7393 get_ref_list(const char *id)
7394 {
7395 struct ref_list *list;
7396 size_t i;
7398 for (i = 0; i < ref_lists_size; i++)
7399 if (!strcmp(id, ref_lists[i]->id))
7400 return ref_lists[i];
7402 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7403 return NULL;
7404 list = calloc(1, sizeof(*list));
7405 if (!list)
7406 return NULL;
7408 for (i = 0; i < refs_size; i++) {
7409 if (!strcmp(id, refs[i]->id) &&
7410 realloc_refs_list(&list->refs, list->size, 1))
7411 list->refs[list->size++] = refs[i];
7412 }
7414 if (!list->refs) {
7415 free(list);
7416 return NULL;
7417 }
7419 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7420 ref_lists[ref_lists_size++] = list;
7421 return list;
7422 }
7424 static int
7425 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7426 {
7427 struct ref *ref = NULL;
7428 bool tag = FALSE;
7429 bool ltag = FALSE;
7430 bool remote = FALSE;
7431 bool tracked = FALSE;
7432 bool head = FALSE;
7433 int from = 0, to = refs_size - 1;
7435 if (!prefixcmp(name, "refs/tags/")) {
7436 if (!suffixcmp(name, namelen, "^{}")) {
7437 namelen -= 3;
7438 name[namelen] = 0;
7439 } else {
7440 ltag = TRUE;
7441 }
7443 tag = TRUE;
7444 namelen -= STRING_SIZE("refs/tags/");
7445 name += STRING_SIZE("refs/tags/");
7447 } else if (!prefixcmp(name, "refs/remotes/")) {
7448 remote = TRUE;
7449 namelen -= STRING_SIZE("refs/remotes/");
7450 name += STRING_SIZE("refs/remotes/");
7451 tracked = !strcmp(opt_remote, name);
7453 } else if (!prefixcmp(name, "refs/heads/")) {
7454 namelen -= STRING_SIZE("refs/heads/");
7455 name += STRING_SIZE("refs/heads/");
7456 if (!strncmp(opt_head, name, namelen))
7457 return OK;
7459 } else if (!strcmp(name, "HEAD")) {
7460 head = TRUE;
7461 if (*opt_head) {
7462 namelen = strlen(opt_head);
7463 name = opt_head;
7464 }
7465 }
7467 /* If we are reloading or it's an annotated tag, replace the
7468 * previous SHA1 with the resolved commit id; relies on the fact
7469 * git-ls-remote lists the commit id of an annotated tag right
7470 * before the commit id it points to. */
7471 while (from <= to) {
7472 size_t pos = (to + from) / 2;
7473 int cmp = strcmp(name, refs[pos]->name);
7475 if (!cmp) {
7476 ref = refs[pos];
7477 break;
7478 }
7480 if (cmp < 0)
7481 to = pos - 1;
7482 else
7483 from = pos + 1;
7484 }
7486 if (!ref) {
7487 if (!realloc_refs(&refs, refs_size, 1))
7488 return ERR;
7489 ref = calloc(1, sizeof(*ref) + namelen);
7490 if (!ref)
7491 return ERR;
7492 memmove(refs + from + 1, refs + from,
7493 (refs_size - from) * sizeof(*refs));
7494 refs[from] = ref;
7495 strncpy(ref->name, name, namelen);
7496 refs_size++;
7497 }
7499 ref->head = head;
7500 ref->tag = tag;
7501 ref->ltag = ltag;
7502 ref->remote = remote;
7503 ref->tracked = tracked;
7504 string_copy_rev(ref->id, id);
7506 if (head)
7507 refs_head = ref;
7508 return OK;
7509 }
7511 static int
7512 load_refs(void)
7513 {
7514 const char *head_argv[] = {
7515 "git", "symbolic-ref", "HEAD", NULL
7516 };
7517 static const char *ls_remote_argv[SIZEOF_ARG] = {
7518 "git", "ls-remote", opt_git_dir, NULL
7519 };
7520 static bool init = FALSE;
7521 size_t i;
7523 if (!init) {
7524 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7525 die("TIG_LS_REMOTE contains too many arguments");
7526 init = TRUE;
7527 }
7529 if (!*opt_git_dir)
7530 return OK;
7532 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7533 !prefixcmp(opt_head, "refs/heads/")) {
7534 char *offset = opt_head + STRING_SIZE("refs/heads/");
7536 memmove(opt_head, offset, strlen(offset) + 1);
7537 }
7539 refs_head = NULL;
7540 for (i = 0; i < refs_size; i++)
7541 refs[i]->id[0] = 0;
7543 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7544 return ERR;
7546 /* Update the ref lists to reflect changes. */
7547 for (i = 0; i < ref_lists_size; i++) {
7548 struct ref_list *list = ref_lists[i];
7549 size_t old, new;
7551 for (old = new = 0; old < list->size; old++)
7552 if (!strcmp(list->id, list->refs[old]->id))
7553 list->refs[new++] = list->refs[old];
7554 list->size = new;
7555 }
7557 return OK;
7558 }
7560 static void
7561 set_remote_branch(const char *name, const char *value, size_t valuelen)
7562 {
7563 if (!strcmp(name, ".remote")) {
7564 string_ncopy(opt_remote, value, valuelen);
7566 } else if (*opt_remote && !strcmp(name, ".merge")) {
7567 size_t from = strlen(opt_remote);
7569 if (!prefixcmp(value, "refs/heads/"))
7570 value += STRING_SIZE("refs/heads/");
7572 if (!string_format_from(opt_remote, &from, "/%s", value))
7573 opt_remote[0] = 0;
7574 }
7575 }
7577 static void
7578 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7579 {
7580 const char *argv[SIZEOF_ARG] = { name, "=" };
7581 int argc = 1 + (cmd == option_set_command);
7582 int error = ERR;
7584 if (!argv_from_string(argv, &argc, value))
7585 config_msg = "Too many option arguments";
7586 else
7587 error = cmd(argc, argv);
7589 if (error == ERR)
7590 warn("Option 'tig.%s': %s", name, config_msg);
7591 }
7593 static bool
7594 set_environment_variable(const char *name, const char *value)
7595 {
7596 size_t len = strlen(name) + 1 + strlen(value) + 1;
7597 char *env = malloc(len);
7599 if (env &&
7600 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7601 putenv(env) == 0)
7602 return TRUE;
7603 free(env);
7604 return FALSE;
7605 }
7607 static void
7608 set_work_tree(const char *value)
7609 {
7610 char cwd[SIZEOF_STR];
7612 if (!getcwd(cwd, sizeof(cwd)))
7613 die("Failed to get cwd path: %s", strerror(errno));
7614 if (chdir(opt_git_dir) < 0)
7615 die("Failed to chdir(%s): %s", strerror(errno));
7616 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7617 die("Failed to get git path: %s", strerror(errno));
7618 if (chdir(cwd) < 0)
7619 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7620 if (chdir(value) < 0)
7621 die("Failed to chdir(%s): %s", value, strerror(errno));
7622 if (!getcwd(cwd, sizeof(cwd)))
7623 die("Failed to get cwd path: %s", strerror(errno));
7624 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7625 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7626 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7627 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7628 opt_is_inside_work_tree = TRUE;
7629 }
7631 static int
7632 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7633 {
7634 if (!strcmp(name, "i18n.commitencoding"))
7635 string_ncopy(opt_encoding, value, valuelen);
7637 else if (!strcmp(name, "core.editor"))
7638 string_ncopy(opt_editor, value, valuelen);
7640 else if (!strcmp(name, "core.worktree"))
7641 set_work_tree(value);
7643 else if (!prefixcmp(name, "tig.color."))
7644 set_repo_config_option(name + 10, value, option_color_command);
7646 else if (!prefixcmp(name, "tig.bind."))
7647 set_repo_config_option(name + 9, value, option_bind_command);
7649 else if (!prefixcmp(name, "tig."))
7650 set_repo_config_option(name + 4, value, option_set_command);
7652 else if (*opt_head && !prefixcmp(name, "branch.") &&
7653 !strncmp(name + 7, opt_head, strlen(opt_head)))
7654 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7656 return OK;
7657 }
7659 static int
7660 load_git_config(void)
7661 {
7662 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7664 return io_run_load(config_list_argv, "=", read_repo_config_option);
7665 }
7667 static int
7668 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7669 {
7670 if (!opt_git_dir[0]) {
7671 string_ncopy(opt_git_dir, name, namelen);
7673 } else if (opt_is_inside_work_tree == -1) {
7674 /* This can be 3 different values depending on the
7675 * version of git being used. If git-rev-parse does not
7676 * understand --is-inside-work-tree it will simply echo
7677 * the option else either "true" or "false" is printed.
7678 * Default to true for the unknown case. */
7679 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7681 } else if (*name == '.') {
7682 string_ncopy(opt_cdup, name, namelen);
7684 } else {
7685 string_ncopy(opt_prefix, name, namelen);
7686 }
7688 return OK;
7689 }
7691 static int
7692 load_repo_info(void)
7693 {
7694 const char *rev_parse_argv[] = {
7695 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7696 "--show-cdup", "--show-prefix", NULL
7697 };
7699 return io_run_load(rev_parse_argv, "=", read_repo_info);
7700 }
7703 /*
7704 * Main
7705 */
7707 static const char usage[] =
7708 "tig " TIG_VERSION " (" __DATE__ ")\n"
7709 "\n"
7710 "Usage: tig [options] [revs] [--] [paths]\n"
7711 " or: tig show [options] [revs] [--] [paths]\n"
7712 " or: tig blame [rev] path\n"
7713 " or: tig status\n"
7714 " or: tig < [git command output]\n"
7715 "\n"
7716 "Options:\n"
7717 " -v, --version Show version and exit\n"
7718 " -h, --help Show help message and exit";
7720 static void __NORETURN
7721 quit(int sig)
7722 {
7723 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7724 if (cursed)
7725 endwin();
7726 exit(0);
7727 }
7729 static void __NORETURN
7730 die(const char *err, ...)
7731 {
7732 va_list args;
7734 endwin();
7736 va_start(args, err);
7737 fputs("tig: ", stderr);
7738 vfprintf(stderr, err, args);
7739 fputs("\n", stderr);
7740 va_end(args);
7742 exit(1);
7743 }
7745 static void
7746 warn(const char *msg, ...)
7747 {
7748 va_list args;
7750 va_start(args, msg);
7751 fputs("tig warning: ", stderr);
7752 vfprintf(stderr, msg, args);
7753 fputs("\n", stderr);
7754 va_end(args);
7755 }
7757 static const char ***filter_args;
7759 static int
7760 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7761 {
7762 return argv_append(filter_args, name) ? OK : ERR;
7763 }
7765 static void
7766 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7767 {
7768 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7769 const char **all_argv = NULL;
7771 filter_args = args;
7772 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7773 !argv_append_array(&all_argv, argv) ||
7774 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7775 die("Failed to split arguments");
7776 argv_free(all_argv);
7777 free(all_argv);
7778 }
7780 static void
7781 filter_options(const char *argv[])
7782 {
7783 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7784 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7785 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7786 }
7788 static enum request
7789 parse_options(int argc, const char *argv[])
7790 {
7791 enum request request = REQ_VIEW_MAIN;
7792 const char *subcommand;
7793 bool seen_dashdash = FALSE;
7794 const char **filter_argv = NULL;
7795 int i;
7797 if (!isatty(STDIN_FILENO))
7798 return REQ_VIEW_PAGER;
7800 if (argc <= 1)
7801 return REQ_VIEW_MAIN;
7803 subcommand = argv[1];
7804 if (!strcmp(subcommand, "status")) {
7805 if (argc > 2)
7806 warn("ignoring arguments after `%s'", subcommand);
7807 return REQ_VIEW_STATUS;
7809 } else if (!strcmp(subcommand, "blame")) {
7810 if (argc <= 2 || argc > 4)
7811 die("invalid number of options to blame\n\n%s", usage);
7813 i = 2;
7814 if (argc == 4) {
7815 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7816 i++;
7817 }
7819 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7820 return REQ_VIEW_BLAME;
7822 } else if (!strcmp(subcommand, "show")) {
7823 request = REQ_VIEW_DIFF;
7825 } else {
7826 subcommand = NULL;
7827 }
7829 for (i = 1 + !!subcommand; i < argc; i++) {
7830 const char *opt = argv[i];
7832 if (seen_dashdash) {
7833 argv_append(&opt_file_args, opt);
7834 continue;
7836 } else if (!strcmp(opt, "--")) {
7837 seen_dashdash = TRUE;
7838 continue;
7840 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7841 printf("tig version %s\n", TIG_VERSION);
7842 quit(0);
7844 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7845 printf("%s\n", usage);
7846 quit(0);
7848 } else if (!strcmp(opt, "--all")) {
7849 argv_append(&opt_rev_args, opt);
7850 continue;
7851 }
7853 if (!argv_append(&filter_argv, opt))
7854 die("command too long");
7855 }
7857 if (filter_argv)
7858 filter_options(filter_argv);
7860 return request;
7861 }
7863 int
7864 main(int argc, const char *argv[])
7865 {
7866 const char *codeset = "UTF-8";
7867 enum request request = parse_options(argc, argv);
7868 struct view *view;
7869 size_t i;
7871 signal(SIGINT, quit);
7872 signal(SIGPIPE, SIG_IGN);
7874 if (setlocale(LC_ALL, "")) {
7875 codeset = nl_langinfo(CODESET);
7876 }
7878 if (load_repo_info() == ERR)
7879 die("Failed to load repo info.");
7881 if (load_options() == ERR)
7882 die("Failed to load user config.");
7884 if (load_git_config() == ERR)
7885 die("Failed to load repo config.");
7887 /* Require a git repository unless when running in pager mode. */
7888 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7889 die("Not a git repository");
7891 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7892 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7893 if (opt_iconv_in == ICONV_NONE)
7894 die("Failed to initialize character set conversion");
7895 }
7897 if (codeset && strcmp(codeset, "UTF-8")) {
7898 opt_iconv_out = iconv_open(codeset, "UTF-8");
7899 if (opt_iconv_out == ICONV_NONE)
7900 die("Failed to initialize character set conversion");
7901 }
7903 if (load_refs() == ERR)
7904 die("Failed to load refs.");
7906 foreach_view (view, i) {
7907 if (getenv(view->cmd_env))
7908 warn("Use of the %s environment variable is deprecated,"
7909 " use options or TIG_DIFF_ARGS instead",
7910 view->cmd_env);
7911 if (!argv_from_env(view->ops->argv, view->cmd_env))
7912 die("Too many arguments in the `%s` environment variable",
7913 view->cmd_env);
7914 }
7916 init_display();
7918 while (view_driver(display[current_view], request)) {
7919 int key = get_input(0);
7921 view = display[current_view];
7922 request = get_keybinding(view->keymap, key);
7924 /* Some low-level request handling. This keeps access to
7925 * status_win restricted. */
7926 switch (request) {
7927 case REQ_NONE:
7928 report("Unknown key, press %s for help",
7929 get_key(view->keymap, REQ_VIEW_HELP));
7930 break;
7931 case REQ_PROMPT:
7932 {
7933 char *cmd = read_prompt(":");
7935 if (cmd && isdigit(*cmd)) {
7936 int lineno = view->lineno + 1;
7938 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7939 select_view_line(view, lineno - 1);
7940 report("");
7941 } else {
7942 report("Unable to parse '%s' as a line number", cmd);
7943 }
7945 } else if (cmd) {
7946 struct view *next = VIEW(REQ_VIEW_PAGER);
7947 const char *argv[SIZEOF_ARG] = { "git" };
7948 int argc = 1;
7950 /* When running random commands, initially show the
7951 * command in the title. However, it maybe later be
7952 * overwritten if a commit line is selected. */
7953 string_ncopy(next->ref, cmd, strlen(cmd));
7955 if (!argv_from_string(argv, &argc, cmd)) {
7956 report("Too many arguments");
7957 } else if (!prepare_update(next, argv, NULL)) {
7958 report("Failed to format command");
7959 } else {
7960 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7961 }
7962 }
7964 request = REQ_NONE;
7965 break;
7966 }
7967 case REQ_SEARCH:
7968 case REQ_SEARCH_BACK:
7969 {
7970 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7971 char *search = read_prompt(prompt);
7973 if (search)
7974 string_ncopy(opt_search, search, strlen(search));
7975 else if (*opt_search)
7976 request = request == REQ_SEARCH ?
7977 REQ_FIND_NEXT :
7978 REQ_FIND_PREV;
7979 else
7980 request = REQ_NONE;
7981 break;
7982 }
7983 default:
7984 break;
7985 }
7986 }
7988 quit(0);
7990 return 0;
7991 }