390923adb7121a767b4c68c79ba035d08f8b2370
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_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #include <ncurses.h>
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
68 #define ABS(x) ((x) >= 0 ? (x) : -(x))
69 #define MIN(x, y) ((x) < (y) ? (x) : (y))
70 #define MAX(x, y) ((x) > (y) ? (x) : (y))
72 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
73 #define STRING_SIZE(x) (sizeof(x) - 1)
75 #define SIZEOF_STR 1024 /* Default string size. */
76 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
77 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
78 #define SIZEOF_ARG 32 /* Default argument array size. */
80 /* Revision graph */
82 #define REVGRAPH_INIT 'I'
83 #define REVGRAPH_MERGE 'M'
84 #define REVGRAPH_BRANCH '+'
85 #define REVGRAPH_COMMIT '*'
86 #define REVGRAPH_BOUND '^'
88 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
90 /* This color name can be used to refer to the default term colors. */
91 #define COLOR_DEFAULT (-1)
93 #define ICONV_NONE ((iconv_t) -1)
94 #ifndef ICONV_CONST
95 #define ICONV_CONST /* nothing */
96 #endif
98 /* The format and size of the date column in the main view. */
99 #define DATE_FORMAT "%Y-%m-%d %H:%M"
100 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
101 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
103 #define ID_COLS 8
104 #define AUTHOR_COLS 19
106 #define MIN_VIEW_HEIGHT 4
108 #define NULL_ID "0000000000000000000000000000000000000000"
110 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
112 /* Some ASCII-shorthands fitted into the ncurses namespace. */
113 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
114 #define KEY_TAB '\t'
115 #define KEY_RETURN '\r'
116 #define KEY_ESC 27
119 struct ref {
120 char id[SIZEOF_REV]; /* Commit SHA1 ID */
121 unsigned int head:1; /* Is it the current HEAD? */
122 unsigned int tag:1; /* Is it a tag? */
123 unsigned int ltag:1; /* If so, is the tag local? */
124 unsigned int remote:1; /* Is it a remote ref? */
125 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
126 char name[1]; /* Ref name; tag or head names are shortened. */
127 };
129 struct ref_list {
130 char id[SIZEOF_REV]; /* Commit SHA1 ID */
131 size_t size; /* Number of refs. */
132 struct ref **refs; /* References for this ID. */
133 };
135 static struct ref *get_ref_head();
136 static struct ref_list *get_ref_list(const char *id);
137 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
138 static int load_refs(void);
140 enum input_status {
141 INPUT_OK,
142 INPUT_SKIP,
143 INPUT_STOP,
144 INPUT_CANCEL
145 };
147 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
149 static char *prompt_input(const char *prompt, input_handler handler, void *data);
150 static bool prompt_yesno(const char *prompt);
152 struct menu_item {
153 int hotkey;
154 const char *text;
155 void *data;
156 };
158 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
160 /*
161 * Allocation helpers ... Entering macro hell to never be seen again.
162 */
164 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
165 static type * \
166 name(type **mem, size_t size, size_t increase) \
167 { \
168 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
169 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
170 type *tmp = *mem; \
171 \
172 if (mem == NULL || num_chunks != num_chunks_new) { \
173 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
174 if (tmp) \
175 *mem = tmp; \
176 } \
177 \
178 return tmp; \
179 }
181 /*
182 * String helpers
183 */
185 static inline void
186 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187 {
188 if (srclen > dstlen - 1)
189 srclen = dstlen - 1;
191 strncpy(dst, src, srclen);
192 dst[srclen] = 0;
193 }
195 /* Shorthands for safely copying into a fixed buffer. */
197 #define string_copy(dst, src) \
198 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
200 #define string_ncopy(dst, src, srclen) \
201 string_ncopy_do(dst, sizeof(dst), src, srclen)
203 #define string_copy_rev(dst, src) \
204 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
206 #define string_add(dst, from, src) \
207 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
209 static size_t
210 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
211 {
212 size_t size, pos;
214 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
215 if (src[pos] == '\t') {
216 size_t expanded = tabsize - (size % tabsize);
218 if (expanded + size >= dstlen - 1)
219 expanded = dstlen - size - 1;
220 memcpy(dst + size, " ", expanded);
221 size += expanded;
222 } else {
223 dst[size++] = src[pos];
224 }
225 }
227 dst[size] = 0;
228 return pos;
229 }
231 static char *
232 chomp_string(char *name)
233 {
234 int namelen;
236 while (isspace(*name))
237 name++;
239 namelen = strlen(name) - 1;
240 while (namelen > 0 && isspace(name[namelen]))
241 name[namelen--] = 0;
243 return name;
244 }
246 static bool
247 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
248 {
249 va_list args;
250 size_t pos = bufpos ? *bufpos : 0;
252 va_start(args, fmt);
253 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
254 va_end(args);
256 if (bufpos)
257 *bufpos = pos;
259 return pos >= bufsize ? FALSE : TRUE;
260 }
262 #define string_format(buf, fmt, args...) \
263 string_nformat(buf, sizeof(buf), NULL, fmt, args)
265 #define string_format_from(buf, from, fmt, args...) \
266 string_nformat(buf, sizeof(buf), from, fmt, args)
268 static int
269 string_enum_compare(const char *str1, const char *str2, int len)
270 {
271 size_t i;
273 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
275 /* Diff-Header == DIFF_HEADER */
276 for (i = 0; i < len; i++) {
277 if (toupper(str1[i]) == toupper(str2[i]))
278 continue;
280 if (string_enum_sep(str1[i]) &&
281 string_enum_sep(str2[i]))
282 continue;
284 return str1[i] - str2[i];
285 }
287 return 0;
288 }
290 #define enum_equals(entry, str, len) \
291 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
293 struct enum_map {
294 const char *name;
295 int namelen;
296 int value;
297 };
299 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
301 static char *
302 enum_map_name(const char *name, size_t namelen)
303 {
304 static char buf[SIZEOF_STR];
305 int bufpos;
307 for (bufpos = 0; bufpos <= namelen; bufpos++) {
308 buf[bufpos] = tolower(name[bufpos]);
309 if (buf[bufpos] == '_')
310 buf[bufpos] = '-';
311 }
313 buf[bufpos] = 0;
314 return buf;
315 }
317 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
319 static bool
320 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
321 {
322 size_t namelen = strlen(name);
323 int i;
325 for (i = 0; i < map_size; i++)
326 if (enum_equals(map[i], name, namelen)) {
327 *value = map[i].value;
328 return TRUE;
329 }
331 return FALSE;
332 }
334 #define map_enum(attr, map, name) \
335 map_enum_do(map, ARRAY_SIZE(map), attr, name)
337 #define prefixcmp(str1, str2) \
338 strncmp(str1, str2, STRING_SIZE(str2))
340 static inline int
341 suffixcmp(const char *str, int slen, const char *suffix)
342 {
343 size_t len = slen >= 0 ? slen : strlen(str);
344 size_t suffixlen = strlen(suffix);
346 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
347 }
350 /*
351 * Unicode / UTF-8 handling
352 *
353 * NOTE: Much of the following code for dealing with Unicode is derived from
354 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
355 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
356 */
358 static inline int
359 unicode_width(unsigned long c, int tab_size)
360 {
361 if (c >= 0x1100 &&
362 (c <= 0x115f /* Hangul Jamo */
363 || c == 0x2329
364 || c == 0x232a
365 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
366 /* CJK ... Yi */
367 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
368 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
369 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
370 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
371 || (c >= 0xffe0 && c <= 0xffe6)
372 || (c >= 0x20000 && c <= 0x2fffd)
373 || (c >= 0x30000 && c <= 0x3fffd)))
374 return 2;
376 if (c == '\t')
377 return tab_size;
379 return 1;
380 }
382 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
383 * Illegal bytes are set one. */
384 static const unsigned char utf8_bytes[256] = {
385 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,
386 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,
387 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,
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 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,
392 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,
393 };
395 static inline unsigned char
396 utf8_char_length(const char *string, const char *end)
397 {
398 int c = *(unsigned char *) string;
400 return utf8_bytes[c];
401 }
403 /* Decode UTF-8 multi-byte representation into a Unicode character. */
404 static inline unsigned long
405 utf8_to_unicode(const char *string, size_t length)
406 {
407 unsigned long unicode;
409 switch (length) {
410 case 1:
411 unicode = string[0];
412 break;
413 case 2:
414 unicode = (string[0] & 0x1f) << 6;
415 unicode += (string[1] & 0x3f);
416 break;
417 case 3:
418 unicode = (string[0] & 0x0f) << 12;
419 unicode += ((string[1] & 0x3f) << 6);
420 unicode += (string[2] & 0x3f);
421 break;
422 case 4:
423 unicode = (string[0] & 0x0f) << 18;
424 unicode += ((string[1] & 0x3f) << 12);
425 unicode += ((string[2] & 0x3f) << 6);
426 unicode += (string[3] & 0x3f);
427 break;
428 case 5:
429 unicode = (string[0] & 0x0f) << 24;
430 unicode += ((string[1] & 0x3f) << 18);
431 unicode += ((string[2] & 0x3f) << 12);
432 unicode += ((string[3] & 0x3f) << 6);
433 unicode += (string[4] & 0x3f);
434 break;
435 case 6:
436 unicode = (string[0] & 0x01) << 30;
437 unicode += ((string[1] & 0x3f) << 24);
438 unicode += ((string[2] & 0x3f) << 18);
439 unicode += ((string[3] & 0x3f) << 12);
440 unicode += ((string[4] & 0x3f) << 6);
441 unicode += (string[5] & 0x3f);
442 break;
443 default:
444 return 0;
445 }
447 /* Invalid characters could return the special 0xfffd value but NUL
448 * should be just as good. */
449 return unicode > 0xffff ? 0 : unicode;
450 }
452 /* Calculates how much of string can be shown within the given maximum width
453 * and sets trimmed parameter to non-zero value if all of string could not be
454 * shown. If the reserve flag is TRUE, it will reserve at least one
455 * trailing character, which can be useful when drawing a delimiter.
456 *
457 * Returns the number of bytes to output from string to satisfy max_width. */
458 static size_t
459 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
460 {
461 const char *string = *start;
462 const char *end = strchr(string, '\0');
463 unsigned char last_bytes = 0;
464 size_t last_ucwidth = 0;
466 *width = 0;
467 *trimmed = 0;
469 while (string < end) {
470 unsigned char bytes = utf8_char_length(string, end);
471 size_t ucwidth;
472 unsigned long unicode;
474 if (string + bytes > end)
475 break;
477 /* Change representation to figure out whether
478 * it is a single- or double-width character. */
480 unicode = utf8_to_unicode(string, bytes);
481 /* FIXME: Graceful handling of invalid Unicode character. */
482 if (!unicode)
483 break;
485 ucwidth = unicode_width(unicode, tab_size);
486 if (skip > 0) {
487 skip -= ucwidth <= skip ? ucwidth : skip;
488 *start += bytes;
489 }
490 *width += ucwidth;
491 if (*width > max_width) {
492 *trimmed = 1;
493 *width -= ucwidth;
494 if (reserve && *width == max_width) {
495 string -= last_bytes;
496 *width -= last_ucwidth;
497 }
498 break;
499 }
501 string += bytes;
502 last_bytes = ucwidth ? bytes : 0;
503 last_ucwidth = ucwidth;
504 }
506 return string - *start;
507 }
510 #define DATE_INFO \
511 DATE_(NO), \
512 DATE_(DEFAULT), \
513 DATE_(LOCAL), \
514 DATE_(RELATIVE), \
515 DATE_(SHORT)
517 enum date {
518 #define DATE_(name) DATE_##name
519 DATE_INFO
520 #undef DATE_
521 };
523 static const struct enum_map date_map[] = {
524 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
525 DATE_INFO
526 #undef DATE_
527 };
529 struct time {
530 time_t sec;
531 int tz;
532 };
534 static inline int timecmp(const struct time *t1, const struct time *t2)
535 {
536 return t1->sec - t2->sec;
537 }
539 static const char *
540 mkdate(const struct time *time, enum date date)
541 {
542 static char buf[DATE_COLS + 1];
543 static const struct enum_map reldate[] = {
544 { "second", 1, 60 * 2 },
545 { "minute", 60, 60 * 60 * 2 },
546 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
547 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
548 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
549 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
550 };
551 struct tm tm;
553 if (!date || !time || !time->sec)
554 return "";
556 if (date == DATE_RELATIVE) {
557 struct timeval now;
558 time_t date = time->sec + time->tz;
559 time_t seconds;
560 int i;
562 gettimeofday(&now, NULL);
563 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
564 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
565 if (seconds >= reldate[i].value)
566 continue;
568 seconds /= reldate[i].namelen;
569 if (!string_format(buf, "%ld %s%s %s",
570 seconds, reldate[i].name,
571 seconds > 1 ? "s" : "",
572 now.tv_sec >= date ? "ago" : "ahead"))
573 break;
574 return buf;
575 }
576 }
578 if (date == DATE_LOCAL) {
579 time_t date = time->sec + time->tz;
580 localtime_r(&date, &tm);
581 }
582 else {
583 gmtime_r(&time->sec, &tm);
584 }
585 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
586 }
589 #define AUTHOR_VALUES \
590 AUTHOR_(NO), \
591 AUTHOR_(FULL), \
592 AUTHOR_(ABBREVIATED)
594 enum author {
595 #define AUTHOR_(name) AUTHOR_##name
596 AUTHOR_VALUES,
597 #undef AUTHOR_
598 AUTHOR_DEFAULT = AUTHOR_FULL
599 };
601 static const struct enum_map author_map[] = {
602 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
603 AUTHOR_VALUES
604 #undef AUTHOR_
605 };
607 static const char *
608 get_author_initials(const char *author)
609 {
610 static char initials[AUTHOR_COLS * 6 + 1];
611 size_t pos = 0;
612 const char *end = strchr(author, '\0');
614 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
616 memset(initials, 0, sizeof(initials));
617 while (author < end) {
618 unsigned char bytes;
619 size_t i;
621 while (is_initial_sep(*author))
622 author++;
624 bytes = utf8_char_length(author, end);
625 if (bytes < sizeof(initials) - 1 - pos) {
626 while (bytes--) {
627 initials[pos++] = *author++;
628 }
629 }
631 for (i = pos; author < end && !is_initial_sep(*author); author++) {
632 if (i < sizeof(initials) - 1)
633 initials[i++] = *author;
634 }
636 initials[i++] = 0;
637 }
639 return initials;
640 }
643 static bool
644 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
645 {
646 int valuelen;
648 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
649 bool advance = cmd[valuelen] != 0;
651 cmd[valuelen] = 0;
652 argv[(*argc)++] = chomp_string(cmd);
653 cmd = chomp_string(cmd + valuelen + advance);
654 }
656 if (*argc < SIZEOF_ARG)
657 argv[*argc] = NULL;
658 return *argc < SIZEOF_ARG;
659 }
661 static bool
662 argv_from_env(const char **argv, const char *name)
663 {
664 char *env = argv ? getenv(name) : NULL;
665 int argc = 0;
667 if (env && *env)
668 env = strdup(env);
669 return !env || argv_from_string(argv, &argc, env);
670 }
672 static void
673 argv_free(const char *argv[])
674 {
675 int argc;
677 if (!argv)
678 return;
679 for (argc = 0; argv[argc]; argc++)
680 free((void *) argv[argc]);
681 argv[0] = NULL;
682 }
684 static size_t
685 argv_size(const char **argv)
686 {
687 int argc = 0;
689 while (argv && argv[argc])
690 argc++;
692 return argc;
693 }
695 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
697 static bool
698 argv_append(const char ***argv, const char *arg)
699 {
700 size_t argc = argv_size(*argv);
702 if (!argv_realloc(argv, argc, 2))
703 return FALSE;
705 (*argv)[argc++] = strdup(arg);
706 (*argv)[argc] = NULL;
707 return TRUE;
708 }
710 static bool
711 argv_append_array(const char ***dst_argv, const char *src_argv[])
712 {
713 int i;
715 for (i = 0; src_argv && src_argv[i]; i++)
716 if (!argv_append(dst_argv, src_argv[i]))
717 return FALSE;
718 return TRUE;
719 }
721 static bool
722 argv_copy(const char ***dst, const char *src[])
723 {
724 int argc;
726 for (argc = 0; src[argc]; argc++)
727 if (!argv_append(dst, src[argc]))
728 return FALSE;
729 return TRUE;
730 }
733 /*
734 * Executing external commands.
735 */
737 enum io_type {
738 IO_FD, /* File descriptor based IO. */
739 IO_BG, /* Execute command in the background. */
740 IO_FG, /* Execute command with same std{in,out,err}. */
741 IO_RD, /* Read only fork+exec IO. */
742 IO_WR, /* Write only fork+exec IO. */
743 IO_AP, /* Append fork+exec output to file. */
744 };
746 struct io {
747 int pipe; /* Pipe end for reading or writing. */
748 pid_t pid; /* PID of spawned process. */
749 int error; /* Error status. */
750 char *buf; /* Read buffer. */
751 size_t bufalloc; /* Allocated buffer size. */
752 size_t bufsize; /* Buffer content size. */
753 char *bufpos; /* Current buffer position. */
754 unsigned int eof:1; /* Has end of file been reached. */
755 };
757 static void
758 io_init(struct io *io)
759 {
760 memset(io, 0, sizeof(*io));
761 io->pipe = -1;
762 }
764 static bool
765 io_open(struct io *io, const char *fmt, ...)
766 {
767 char name[SIZEOF_STR] = "";
768 bool fits;
769 va_list args;
771 io_init(io);
773 va_start(args, fmt);
774 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
775 va_end(args);
777 if (!fits) {
778 io->error = ENAMETOOLONG;
779 return FALSE;
780 }
781 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
782 if (io->pipe == -1)
783 io->error = errno;
784 return io->pipe != -1;
785 }
787 static bool
788 io_kill(struct io *io)
789 {
790 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
791 }
793 static bool
794 io_done(struct io *io)
795 {
796 pid_t pid = io->pid;
798 if (io->pipe != -1)
799 close(io->pipe);
800 free(io->buf);
801 io_init(io);
803 while (pid > 0) {
804 int status;
805 pid_t waiting = waitpid(pid, &status, 0);
807 if (waiting < 0) {
808 if (errno == EINTR)
809 continue;
810 io->error = errno;
811 return FALSE;
812 }
814 return waiting == pid &&
815 !WIFSIGNALED(status) &&
816 WIFEXITED(status) &&
817 !WEXITSTATUS(status);
818 }
820 return TRUE;
821 }
823 static bool
824 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
825 {
826 int pipefds[2] = { -1, -1 };
827 va_list args;
829 io_init(io);
831 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
832 io->error = errno;
833 return FALSE;
834 } else if (type == IO_AP) {
835 va_start(args, argv);
836 pipefds[1] = va_arg(args, int);
837 va_end(args);
838 }
840 if ((io->pid = fork())) {
841 if (io->pid == -1)
842 io->error = errno;
843 if (pipefds[!(type == IO_WR)] != -1)
844 close(pipefds[!(type == IO_WR)]);
845 if (io->pid != -1) {
846 io->pipe = pipefds[!!(type == IO_WR)];
847 return TRUE;
848 }
850 } else {
851 if (type != IO_FG) {
852 int devnull = open("/dev/null", O_RDWR);
853 int readfd = type == IO_WR ? pipefds[0] : devnull;
854 int writefd = (type == IO_RD || type == IO_AP)
855 ? pipefds[1] : devnull;
857 dup2(readfd, STDIN_FILENO);
858 dup2(writefd, STDOUT_FILENO);
859 dup2(devnull, STDERR_FILENO);
861 close(devnull);
862 if (pipefds[0] != -1)
863 close(pipefds[0]);
864 if (pipefds[1] != -1)
865 close(pipefds[1]);
866 }
868 if (dir && *dir && chdir(dir) == -1)
869 exit(errno);
871 execvp(argv[0], (char *const*) argv);
872 exit(errno);
873 }
875 if (pipefds[!!(type == IO_WR)] != -1)
876 close(pipefds[!!(type == IO_WR)]);
877 return FALSE;
878 }
880 static bool
881 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
882 {
883 struct io io;
885 return io_run(&io, type, dir, argv, fd) && io_done(&io);
886 }
888 static bool
889 io_run_bg(const char **argv)
890 {
891 return io_complete(IO_BG, argv, NULL, -1);
892 }
894 static bool
895 io_run_fg(const char **argv, const char *dir)
896 {
897 return io_complete(IO_FG, argv, dir, -1);
898 }
900 static bool
901 io_run_append(const char **argv, int fd)
902 {
903 return io_complete(IO_AP, argv, NULL, fd);
904 }
906 static bool
907 io_eof(struct io *io)
908 {
909 return io->eof;
910 }
912 static int
913 io_error(struct io *io)
914 {
915 return io->error;
916 }
918 static char *
919 io_strerror(struct io *io)
920 {
921 return strerror(io->error);
922 }
924 static bool
925 io_can_read(struct io *io)
926 {
927 struct timeval tv = { 0, 500 };
928 fd_set fds;
930 FD_ZERO(&fds);
931 FD_SET(io->pipe, &fds);
933 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
934 }
936 static ssize_t
937 io_read(struct io *io, void *buf, size_t bufsize)
938 {
939 do {
940 ssize_t readsize = read(io->pipe, buf, bufsize);
942 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
943 continue;
944 else if (readsize == -1)
945 io->error = errno;
946 else if (readsize == 0)
947 io->eof = 1;
948 return readsize;
949 } while (1);
950 }
952 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
954 static char *
955 io_get(struct io *io, int c, bool can_read)
956 {
957 char *eol;
958 ssize_t readsize;
960 while (TRUE) {
961 if (io->bufsize > 0) {
962 eol = memchr(io->bufpos, c, io->bufsize);
963 if (eol) {
964 char *line = io->bufpos;
966 *eol = 0;
967 io->bufpos = eol + 1;
968 io->bufsize -= io->bufpos - line;
969 return line;
970 }
971 }
973 if (io_eof(io)) {
974 if (io->bufsize) {
975 io->bufpos[io->bufsize] = 0;
976 io->bufsize = 0;
977 return io->bufpos;
978 }
979 return NULL;
980 }
982 if (!can_read)
983 return NULL;
985 if (io->bufsize > 0 && io->bufpos > io->buf)
986 memmove(io->buf, io->bufpos, io->bufsize);
988 if (io->bufalloc == io->bufsize) {
989 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
990 return NULL;
991 io->bufalloc += BUFSIZ;
992 }
994 io->bufpos = io->buf;
995 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
996 if (io_error(io))
997 return NULL;
998 io->bufsize += readsize;
999 }
1000 }
1002 static bool
1003 io_write(struct io *io, const void *buf, size_t bufsize)
1004 {
1005 size_t written = 0;
1007 while (!io_error(io) && written < bufsize) {
1008 ssize_t size;
1010 size = write(io->pipe, buf + written, bufsize - written);
1011 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1012 continue;
1013 else if (size == -1)
1014 io->error = errno;
1015 else
1016 written += size;
1017 }
1019 return written == bufsize;
1020 }
1022 static bool
1023 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024 {
1025 char *result = io_get(io, '\n', TRUE);
1027 if (result) {
1028 result = chomp_string(result);
1029 string_ncopy_do(buf, bufsize, result, strlen(result));
1030 }
1032 return io_done(io) && result;
1033 }
1035 static bool
1036 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037 {
1038 struct io io;
1040 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1041 }
1043 typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
1045 static int
1046 io_load(struct io *io, const char *separators,
1047 io_read_fn read_property, void *data)
1048 {
1049 char *name;
1050 int state = OK;
1052 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1053 char *value;
1054 size_t namelen;
1055 size_t valuelen;
1057 name = chomp_string(name);
1058 namelen = strcspn(name, separators);
1060 if (name[namelen]) {
1061 name[namelen] = 0;
1062 value = chomp_string(name + namelen + 1);
1063 valuelen = strlen(value);
1065 } else {
1066 value = "";
1067 valuelen = 0;
1068 }
1070 state = read_property(name, namelen, value, valuelen, data);
1071 }
1073 if (state != ERR && io_error(io))
1074 state = ERR;
1075 io_done(io);
1077 return state;
1078 }
1080 static int
1081 io_run_load(const char **argv, const char *separators,
1082 io_read_fn read_property, void *data)
1083 {
1084 struct io io;
1086 if (!io_run(&io, IO_RD, NULL, argv))
1087 return ERR;
1088 return io_load(&io, separators, read_property, data);
1089 }
1092 /*
1093 * User requests
1094 */
1096 #define REQ_INFO \
1097 /* XXX: Keep the view request first and in sync with views[]. */ \
1098 REQ_GROUP("View switching") \
1099 REQ_(VIEW_MAIN, "Show main view"), \
1100 REQ_(VIEW_DIFF, "Show diff view"), \
1101 REQ_(VIEW_LOG, "Show log view"), \
1102 REQ_(VIEW_TREE, "Show tree view"), \
1103 REQ_(VIEW_BLOB, "Show blob view"), \
1104 REQ_(VIEW_BLAME, "Show blame view"), \
1105 REQ_(VIEW_BRANCH, "Show branch view"), \
1106 REQ_(VIEW_HELP, "Show help page"), \
1107 REQ_(VIEW_PAGER, "Show pager view"), \
1108 REQ_(VIEW_STATUS, "Show status view"), \
1109 REQ_(VIEW_STAGE, "Show stage view"), \
1110 \
1111 REQ_GROUP("View manipulation") \
1112 REQ_(ENTER, "Enter current line and scroll"), \
1113 REQ_(NEXT, "Move to next"), \
1114 REQ_(PREVIOUS, "Move to previous"), \
1115 REQ_(PARENT, "Move to parent"), \
1116 REQ_(VIEW_NEXT, "Move focus to next view"), \
1117 REQ_(REFRESH, "Reload and refresh"), \
1118 REQ_(MAXIMIZE, "Maximize the current view"), \
1119 REQ_(VIEW_CLOSE, "Close the current view"), \
1120 REQ_(QUIT, "Close all views and quit"), \
1121 \
1122 REQ_GROUP("View specific requests") \
1123 REQ_(STATUS_UPDATE, "Update file status"), \
1124 REQ_(STATUS_REVERT, "Revert file changes"), \
1125 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1126 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1127 \
1128 REQ_GROUP("Cursor navigation") \
1129 REQ_(MOVE_UP, "Move cursor one line up"), \
1130 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1131 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1132 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1133 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1134 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1135 \
1136 REQ_GROUP("Scrolling") \
1137 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1138 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1139 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1140 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1141 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1142 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1143 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1144 \
1145 REQ_GROUP("Searching") \
1146 REQ_(SEARCH, "Search the view"), \
1147 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1148 REQ_(FIND_NEXT, "Find next search match"), \
1149 REQ_(FIND_PREV, "Find previous search match"), \
1150 \
1151 REQ_GROUP("Option manipulation") \
1152 REQ_(OPTIONS, "Open option menu"), \
1153 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1154 REQ_(TOGGLE_DATE, "Toggle date display"), \
1155 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1156 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1157 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1158 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160 \
1161 REQ_GROUP("Misc") \
1162 REQ_(PROMPT, "Bring up the prompt"), \
1163 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1164 REQ_(SHOW_VERSION, "Show version information"), \
1165 REQ_(STOP_LOADING, "Stop all loading views"), \
1166 REQ_(EDIT, "Open in editor"), \
1167 REQ_(NONE, "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175 /* Offset all requests to avoid conflicts with ncurses getch values. */
1176 REQ_UNKNOWN = KEY_MAX + 1,
1177 REQ_OFFSET,
1178 REQ_INFO
1180 #undef REQ_GROUP
1181 #undef REQ_
1182 };
1184 struct request_info {
1185 enum request request;
1186 const char *name;
1187 int namelen;
1188 const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194 REQ_INFO
1195 #undef REQ_GROUP
1196 #undef REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1201 {
1202 int namelen = strlen(name);
1203 int i;
1205 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206 if (enum_equals(req_info[i], name, namelen))
1207 return req_info[i].request;
1209 return REQ_UNKNOWN;
1210 }
1213 /*
1214 * Options
1215 */
1217 /* Option and state variables. */
1218 static enum date opt_date = DATE_DEFAULT;
1219 static enum author opt_author = AUTHOR_DEFAULT;
1220 static bool opt_line_number = FALSE;
1221 static bool opt_line_graphics = TRUE;
1222 static bool opt_rev_graph = FALSE;
1223 static bool opt_show_refs = TRUE;
1224 static bool opt_untracked_dirs_content = TRUE;
1225 static int opt_num_interval = 5;
1226 static double opt_hscroll = 0.50;
1227 static double opt_scale_split_view = 2.0 / 3.0;
1228 static int opt_tab_size = 8;
1229 static int opt_author_cols = AUTHOR_COLS;
1230 static char opt_path[SIZEOF_STR] = "";
1231 static char opt_file[SIZEOF_STR] = "";
1232 static char opt_ref[SIZEOF_REF] = "";
1233 static char opt_head[SIZEOF_REF] = "";
1234 static char opt_remote[SIZEOF_REF] = "";
1235 static char opt_encoding[20] = "UTF-8";
1236 static iconv_t opt_iconv_in = ICONV_NONE;
1237 static iconv_t opt_iconv_out = ICONV_NONE;
1238 static char opt_search[SIZEOF_STR] = "";
1239 static char opt_cdup[SIZEOF_STR] = "";
1240 static char opt_prefix[SIZEOF_STR] = "";
1241 static char opt_git_dir[SIZEOF_STR] = "";
1242 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1243 static char opt_editor[SIZEOF_STR] = "";
1244 static FILE *opt_tty = NULL;
1245 static const char **opt_diff_argv = NULL;
1246 static const char **opt_rev_argv = NULL;
1247 static const char **opt_file_argv = NULL;
1249 #define is_initial_commit() (!get_ref_head())
1250 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1253 /*
1254 * Line-oriented content detection.
1255 */
1257 #define LINE_INFO \
1258 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1273 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1274 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1275 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1279 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1280 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1281 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1282 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1283 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1284 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1285 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1289 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1290 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1291 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1293 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1296 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1297 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1301 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1302 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1306 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1307 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1308 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1309 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1310 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1311 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1312 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1313 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1315 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1316 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1318 enum line_type {
1319 #define LINE(type, line, fg, bg, attr) \
1320 LINE_##type
1321 LINE_INFO,
1322 LINE_NONE
1323 #undef LINE
1324 };
1326 struct line_info {
1327 const char *name; /* Option name. */
1328 int namelen; /* Size of option name. */
1329 const char *line; /* The start of line to match. */
1330 int linelen; /* Size of string to match. */
1331 int fg, bg, attr; /* Color and text attributes for the lines. */
1332 };
1334 static struct line_info line_info[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1337 LINE_INFO
1338 #undef LINE
1339 };
1341 static enum line_type
1342 get_line_type(const char *line)
1343 {
1344 int linelen = strlen(line);
1345 enum line_type type;
1347 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1348 /* Case insensitive search matches Signed-off-by lines better. */
1349 if (linelen >= line_info[type].linelen &&
1350 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1351 return type;
1353 return LINE_DEFAULT;
1354 }
1356 static inline int
1357 get_line_attr(enum line_type type)
1358 {
1359 assert(type < ARRAY_SIZE(line_info));
1360 return COLOR_PAIR(type) | line_info[type].attr;
1361 }
1363 static struct line_info *
1364 get_line_info(const char *name)
1365 {
1366 size_t namelen = strlen(name);
1367 enum line_type type;
1369 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1370 if (enum_equals(line_info[type], name, namelen))
1371 return &line_info[type];
1373 return NULL;
1374 }
1376 static void
1377 init_colors(void)
1378 {
1379 int default_bg = line_info[LINE_DEFAULT].bg;
1380 int default_fg = line_info[LINE_DEFAULT].fg;
1381 enum line_type type;
1383 start_color();
1385 if (assume_default_colors(default_fg, default_bg) == ERR) {
1386 default_bg = COLOR_BLACK;
1387 default_fg = COLOR_WHITE;
1388 }
1390 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1391 struct line_info *info = &line_info[type];
1392 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1393 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1395 init_pair(type, fg, bg);
1396 }
1397 }
1399 struct line {
1400 enum line_type type;
1402 /* State flags */
1403 unsigned int selected:1;
1404 unsigned int dirty:1;
1405 unsigned int cleareol:1;
1406 unsigned int other:16;
1408 void *data; /* User data */
1409 };
1412 /*
1413 * Keys
1414 */
1416 struct keybinding {
1417 int alias;
1418 enum request request;
1419 };
1421 static struct keybinding default_keybindings[] = {
1422 /* View switching */
1423 { 'm', REQ_VIEW_MAIN },
1424 { 'd', REQ_VIEW_DIFF },
1425 { 'l', REQ_VIEW_LOG },
1426 { 't', REQ_VIEW_TREE },
1427 { 'f', REQ_VIEW_BLOB },
1428 { 'B', REQ_VIEW_BLAME },
1429 { 'H', REQ_VIEW_BRANCH },
1430 { 'p', REQ_VIEW_PAGER },
1431 { 'h', REQ_VIEW_HELP },
1432 { 'S', REQ_VIEW_STATUS },
1433 { 'c', REQ_VIEW_STAGE },
1435 /* View manipulation */
1436 { 'q', REQ_VIEW_CLOSE },
1437 { KEY_TAB, REQ_VIEW_NEXT },
1438 { KEY_RETURN, REQ_ENTER },
1439 { KEY_UP, REQ_PREVIOUS },
1440 { KEY_CTL('P'), REQ_PREVIOUS },
1441 { KEY_DOWN, REQ_NEXT },
1442 { KEY_CTL('N'), REQ_NEXT },
1443 { 'R', REQ_REFRESH },
1444 { KEY_F(5), REQ_REFRESH },
1445 { 'O', REQ_MAXIMIZE },
1447 /* Cursor navigation */
1448 { 'k', REQ_MOVE_UP },
1449 { 'j', REQ_MOVE_DOWN },
1450 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1451 { KEY_END, REQ_MOVE_LAST_LINE },
1452 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1453 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1454 { ' ', REQ_MOVE_PAGE_DOWN },
1455 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1456 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1457 { 'b', REQ_MOVE_PAGE_UP },
1458 { '-', REQ_MOVE_PAGE_UP },
1460 /* Scrolling */
1461 { '|', REQ_SCROLL_FIRST_COL },
1462 { KEY_LEFT, REQ_SCROLL_LEFT },
1463 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1464 { KEY_IC, REQ_SCROLL_LINE_UP },
1465 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1466 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1467 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1468 { 'w', REQ_SCROLL_PAGE_UP },
1469 { 's', REQ_SCROLL_PAGE_DOWN },
1471 /* Searching */
1472 { '/', REQ_SEARCH },
1473 { '?', REQ_SEARCH_BACK },
1474 { 'n', REQ_FIND_NEXT },
1475 { 'N', REQ_FIND_PREV },
1477 /* Misc */
1478 { 'Q', REQ_QUIT },
1479 { 'z', REQ_STOP_LOADING },
1480 { 'v', REQ_SHOW_VERSION },
1481 { 'r', REQ_SCREEN_REDRAW },
1482 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1483 { 'o', REQ_OPTIONS },
1484 { '.', REQ_TOGGLE_LINENO },
1485 { 'D', REQ_TOGGLE_DATE },
1486 { 'A', REQ_TOGGLE_AUTHOR },
1487 { 'g', REQ_TOGGLE_REV_GRAPH },
1488 { 'F', REQ_TOGGLE_REFS },
1489 { 'I', REQ_TOGGLE_SORT_ORDER },
1490 { 'i', REQ_TOGGLE_SORT_FIELD },
1491 { ':', REQ_PROMPT },
1492 { 'u', REQ_STATUS_UPDATE },
1493 { '!', REQ_STATUS_REVERT },
1494 { 'M', REQ_STATUS_MERGE },
1495 { '@', REQ_STAGE_NEXT },
1496 { ',', REQ_PARENT },
1497 { 'e', REQ_EDIT },
1498 };
1500 #define KEYMAP_INFO \
1501 KEYMAP_(GENERIC), \
1502 KEYMAP_(MAIN), \
1503 KEYMAP_(DIFF), \
1504 KEYMAP_(LOG), \
1505 KEYMAP_(TREE), \
1506 KEYMAP_(BLOB), \
1507 KEYMAP_(BLAME), \
1508 KEYMAP_(BRANCH), \
1509 KEYMAP_(PAGER), \
1510 KEYMAP_(HELP), \
1511 KEYMAP_(STATUS), \
1512 KEYMAP_(STAGE)
1514 enum keymap {
1515 #define KEYMAP_(name) KEYMAP_##name
1516 KEYMAP_INFO
1517 #undef KEYMAP_
1518 };
1520 static const struct enum_map keymap_table[] = {
1521 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1522 KEYMAP_INFO
1523 #undef KEYMAP_
1524 };
1526 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1528 struct keybinding_table {
1529 struct keybinding *data;
1530 size_t size;
1531 };
1533 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1535 static void
1536 add_keybinding(enum keymap keymap, enum request request, int key)
1537 {
1538 struct keybinding_table *table = &keybindings[keymap];
1539 size_t i;
1541 for (i = 0; i < keybindings[keymap].size; i++) {
1542 if (keybindings[keymap].data[i].alias == key) {
1543 keybindings[keymap].data[i].request = request;
1544 return;
1545 }
1546 }
1548 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1549 if (!table->data)
1550 die("Failed to allocate keybinding");
1551 table->data[table->size].alias = key;
1552 table->data[table->size++].request = request;
1554 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1555 int i;
1557 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1558 if (default_keybindings[i].alias == key)
1559 default_keybindings[i].request = REQ_NONE;
1560 }
1561 }
1563 /* Looks for a key binding first in the given map, then in the generic map, and
1564 * lastly in the default keybindings. */
1565 static enum request
1566 get_keybinding(enum keymap keymap, int key)
1567 {
1568 size_t i;
1570 for (i = 0; i < keybindings[keymap].size; i++)
1571 if (keybindings[keymap].data[i].alias == key)
1572 return keybindings[keymap].data[i].request;
1574 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1575 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1576 return keybindings[KEYMAP_GENERIC].data[i].request;
1578 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1579 if (default_keybindings[i].alias == key)
1580 return default_keybindings[i].request;
1582 return (enum request) key;
1583 }
1586 struct key {
1587 const char *name;
1588 int value;
1589 };
1591 static const struct key key_table[] = {
1592 { "Enter", KEY_RETURN },
1593 { "Space", ' ' },
1594 { "Backspace", KEY_BACKSPACE },
1595 { "Tab", KEY_TAB },
1596 { "Escape", KEY_ESC },
1597 { "Left", KEY_LEFT },
1598 { "Right", KEY_RIGHT },
1599 { "Up", KEY_UP },
1600 { "Down", KEY_DOWN },
1601 { "Insert", KEY_IC },
1602 { "Delete", KEY_DC },
1603 { "Hash", '#' },
1604 { "Home", KEY_HOME },
1605 { "End", KEY_END },
1606 { "PageUp", KEY_PPAGE },
1607 { "PageDown", KEY_NPAGE },
1608 { "F1", KEY_F(1) },
1609 { "F2", KEY_F(2) },
1610 { "F3", KEY_F(3) },
1611 { "F4", KEY_F(4) },
1612 { "F5", KEY_F(5) },
1613 { "F6", KEY_F(6) },
1614 { "F7", KEY_F(7) },
1615 { "F8", KEY_F(8) },
1616 { "F9", KEY_F(9) },
1617 { "F10", KEY_F(10) },
1618 { "F11", KEY_F(11) },
1619 { "F12", KEY_F(12) },
1620 };
1622 static int
1623 get_key_value(const char *name)
1624 {
1625 int i;
1627 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1628 if (!strcasecmp(key_table[i].name, name))
1629 return key_table[i].value;
1631 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1632 return (int)name[1] & 0x1f;
1633 if (strlen(name) == 1 && isprint(*name))
1634 return (int) *name;
1635 return ERR;
1636 }
1638 static const char *
1639 get_key_name(int key_value)
1640 {
1641 static char key_char[] = "'X'\0";
1642 const char *seq = NULL;
1643 int key;
1645 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1646 if (key_table[key].value == key_value)
1647 seq = key_table[key].name;
1649 if (seq == NULL && key_value < 0x7f) {
1650 char *s = key_char + 1;
1652 if (key_value >= 0x20) {
1653 *s++ = key_value;
1654 } else {
1655 *s++ = '^';
1656 *s++ = 0x40 | (key_value & 0x1f);
1657 }
1658 *s++ = '\'';
1659 *s++ = '\0';
1660 seq = key_char;
1661 }
1663 return seq ? seq : "(no key)";
1664 }
1666 static bool
1667 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1668 {
1669 const char *sep = *pos > 0 ? ", " : "";
1670 const char *keyname = get_key_name(keybinding->alias);
1672 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1673 }
1675 static bool
1676 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1677 enum keymap keymap, bool all)
1678 {
1679 int i;
1681 for (i = 0; i < keybindings[keymap].size; i++) {
1682 if (keybindings[keymap].data[i].request == request) {
1683 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1684 return FALSE;
1685 if (!all)
1686 break;
1687 }
1688 }
1690 return TRUE;
1691 }
1693 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1695 static const char *
1696 get_keys(enum keymap keymap, enum request request, bool all)
1697 {
1698 static char buf[BUFSIZ];
1699 size_t pos = 0;
1700 int i;
1702 buf[pos] = 0;
1704 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1705 return "Too many keybindings!";
1706 if (pos > 0 && !all)
1707 return buf;
1709 if (keymap != KEYMAP_GENERIC) {
1710 /* Only the generic keymap includes the default keybindings when
1711 * listing all keys. */
1712 if (all)
1713 return buf;
1715 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1716 return "Too many keybindings!";
1717 if (pos)
1718 return buf;
1719 }
1721 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1722 if (default_keybindings[i].request == request) {
1723 if (!append_key(buf, &pos, &default_keybindings[i]))
1724 return "Too many keybindings!";
1725 if (!all)
1726 return buf;
1727 }
1728 }
1730 return buf;
1731 }
1733 struct run_request {
1734 enum keymap keymap;
1735 int key;
1736 const char **argv;
1737 };
1739 static struct run_request *run_request;
1740 static size_t run_requests;
1742 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1744 static enum request
1745 add_run_request(enum keymap keymap, int key, const char **argv)
1746 {
1747 struct run_request *req;
1749 if (!realloc_run_requests(&run_request, run_requests, 1))
1750 return REQ_NONE;
1752 req = &run_request[run_requests];
1753 req->keymap = keymap;
1754 req->key = key;
1755 req->argv = NULL;
1757 if (!argv_copy(&req->argv, argv))
1758 return REQ_NONE;
1760 return REQ_NONE + ++run_requests;
1761 }
1763 static struct run_request *
1764 get_run_request(enum request request)
1765 {
1766 if (request <= REQ_NONE)
1767 return NULL;
1768 return &run_request[request - REQ_NONE - 1];
1769 }
1771 static void
1772 add_builtin_run_requests(void)
1773 {
1774 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1775 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1776 const char *commit[] = { "git", "commit", NULL };
1777 const char *gc[] = { "git", "gc", NULL };
1778 struct run_request reqs[] = {
1779 { KEYMAP_MAIN, 'C', cherry_pick },
1780 { KEYMAP_STATUS, 'C', commit },
1781 { KEYMAP_BRANCH, 'C', checkout },
1782 { KEYMAP_GENERIC, 'G', gc },
1783 };
1784 int i;
1786 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1787 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1789 if (req != reqs[i].key)
1790 continue;
1791 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1792 if (req != REQ_NONE)
1793 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1794 }
1795 }
1797 /*
1798 * User config file handling.
1799 */
1801 #define OPT_ERR_INFO \
1802 OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
1803 OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
1804 OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
1805 OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
1806 OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
1807 OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
1808 OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
1809 OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
1810 OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
1811 OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
1812 OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
1813 OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
1814 OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
1815 OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
1816 OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
1817 OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
1819 enum option_code {
1820 #define OPT_ERR_(name, msg) OPT_ERR_ ## name
1821 OPT_ERR_INFO
1822 #undef OPT_ERR_
1823 OPT_OK
1824 };
1826 static const char *option_errors[] = {
1827 #define OPT_ERR_(name, msg) msg
1828 OPT_ERR_INFO
1829 #undef OPT_ERR_
1830 };
1832 static const struct enum_map color_map[] = {
1833 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1834 COLOR_MAP(DEFAULT),
1835 COLOR_MAP(BLACK),
1836 COLOR_MAP(BLUE),
1837 COLOR_MAP(CYAN),
1838 COLOR_MAP(GREEN),
1839 COLOR_MAP(MAGENTA),
1840 COLOR_MAP(RED),
1841 COLOR_MAP(WHITE),
1842 COLOR_MAP(YELLOW),
1843 };
1845 static const struct enum_map attr_map[] = {
1846 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1847 ATTR_MAP(NORMAL),
1848 ATTR_MAP(BLINK),
1849 ATTR_MAP(BOLD),
1850 ATTR_MAP(DIM),
1851 ATTR_MAP(REVERSE),
1852 ATTR_MAP(STANDOUT),
1853 ATTR_MAP(UNDERLINE),
1854 };
1856 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1858 static enum option_code
1859 parse_step(double *opt, const char *arg)
1860 {
1861 *opt = atoi(arg);
1862 if (!strchr(arg, '%'))
1863 return OPT_OK;
1865 /* "Shift down" so 100% and 1 does not conflict. */
1866 *opt = (*opt - 1) / 100;
1867 if (*opt >= 1.0) {
1868 *opt = 0.99;
1869 return OPT_ERR_INVALID_STEP_VALUE;
1870 }
1871 if (*opt < 0.0) {
1872 *opt = 1;
1873 return OPT_ERR_INVALID_STEP_VALUE;
1874 }
1875 return OPT_OK;
1876 }
1878 static enum option_code
1879 parse_int(int *opt, const char *arg, int min, int max)
1880 {
1881 int value = atoi(arg);
1883 if (min <= value && value <= max) {
1884 *opt = value;
1885 return OPT_OK;
1886 }
1888 return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
1889 }
1891 static bool
1892 set_color(int *color, const char *name)
1893 {
1894 if (map_enum(color, color_map, name))
1895 return TRUE;
1896 if (!prefixcmp(name, "color"))
1897 return parse_int(color, name + 5, 0, 255) == OK;
1898 return FALSE;
1899 }
1901 /* Wants: object fgcolor bgcolor [attribute] */
1902 static enum option_code
1903 option_color_command(int argc, const char *argv[])
1904 {
1905 struct line_info *info;
1907 if (argc < 3)
1908 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1910 info = get_line_info(argv[0]);
1911 if (!info) {
1912 static const struct enum_map obsolete[] = {
1913 ENUM_MAP("main-delim", LINE_DELIMITER),
1914 ENUM_MAP("main-date", LINE_DATE),
1915 ENUM_MAP("main-author", LINE_AUTHOR),
1916 };
1917 int index;
1919 if (!map_enum(&index, obsolete, argv[0])) {
1920 return OPT_ERR_UNKNOWN_COLOR_NAME;
1921 }
1922 info = &line_info[index];
1923 }
1925 if (!set_color(&info->fg, argv[1]) ||
1926 !set_color(&info->bg, argv[2])) {
1927 return OPT_ERR_UNKNOWN_COLOR;
1928 }
1930 info->attr = 0;
1931 while (argc-- > 3) {
1932 int attr;
1934 if (!set_attribute(&attr, argv[argc])) {
1935 return OPT_ERR_UNKNOWN_ATTRIBUTE;
1936 }
1937 info->attr |= attr;
1938 }
1940 return OPT_OK;
1941 }
1943 static enum option_code
1944 parse_bool(bool *opt, const char *arg)
1945 {
1946 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1947 ? TRUE : FALSE;
1948 return OPT_OK;
1949 }
1951 static enum option_code
1952 parse_enum_do(unsigned int *opt, const char *arg,
1953 const struct enum_map *map, size_t map_size)
1954 {
1955 bool is_true;
1957 assert(map_size > 1);
1959 if (map_enum_do(map, map_size, (int *) opt, arg))
1960 return OPT_OK;
1962 parse_bool(&is_true, arg);
1963 *opt = is_true ? map[1].value : map[0].value;
1964 return OPT_OK;
1965 }
1967 #define parse_enum(opt, arg, map) \
1968 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1970 static enum option_code
1971 parse_string(char *opt, const char *arg, size_t optsize)
1972 {
1973 int arglen = strlen(arg);
1975 switch (arg[0]) {
1976 case '\"':
1977 case '\'':
1978 if (arglen == 1 || arg[arglen - 1] != arg[0])
1979 return OPT_ERR_UNMATCHED_QUOTATION;
1980 arg += 1; arglen -= 2;
1981 default:
1982 string_ncopy_do(opt, optsize, arg, arglen);
1983 return OPT_OK;
1984 }
1985 }
1987 /* Wants: name = value */
1988 static enum option_code
1989 option_set_command(int argc, const char *argv[])
1990 {
1991 if (argc != 3)
1992 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1994 if (strcmp(argv[1], "="))
1995 return OPT_ERR_NO_VALUE_ASSIGNED;
1997 if (!strcmp(argv[0], "show-author"))
1998 return parse_enum(&opt_author, argv[2], author_map);
2000 if (!strcmp(argv[0], "show-date"))
2001 return parse_enum(&opt_date, argv[2], date_map);
2003 if (!strcmp(argv[0], "show-rev-graph"))
2004 return parse_bool(&opt_rev_graph, argv[2]);
2006 if (!strcmp(argv[0], "show-refs"))
2007 return parse_bool(&opt_show_refs, argv[2]);
2009 if (!strcmp(argv[0], "show-line-numbers"))
2010 return parse_bool(&opt_line_number, argv[2]);
2012 if (!strcmp(argv[0], "line-graphics"))
2013 return parse_bool(&opt_line_graphics, argv[2]);
2015 if (!strcmp(argv[0], "line-number-interval"))
2016 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2018 if (!strcmp(argv[0], "author-width"))
2019 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2021 if (!strcmp(argv[0], "horizontal-scroll"))
2022 return parse_step(&opt_hscroll, argv[2]);
2024 if (!strcmp(argv[0], "split-view-height"))
2025 return parse_step(&opt_scale_split_view, argv[2]);
2027 if (!strcmp(argv[0], "tab-size"))
2028 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2030 if (!strcmp(argv[0], "commit-encoding"))
2031 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2033 if (!strcmp(argv[0], "status-untracked-dirs"))
2034 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2036 return OPT_ERR_UNKNOWN_VARIABLE_NAME;
2037 }
2039 /* Wants: mode request key */
2040 static enum option_code
2041 option_bind_command(int argc, const char *argv[])
2042 {
2043 enum request request;
2044 int keymap = -1;
2045 int key;
2047 if (argc < 3)
2048 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
2050 if (!set_keymap(&keymap, argv[0]))
2051 return OPT_ERR_UNKNOWN_KEY_MAP;
2053 key = get_key_value(argv[1]);
2054 if (key == ERR)
2055 return OPT_ERR_UNKNOWN_KEY;
2057 request = get_request(argv[2]);
2058 if (request == REQ_UNKNOWN) {
2059 static const struct enum_map obsolete[] = {
2060 ENUM_MAP("cherry-pick", REQ_NONE),
2061 ENUM_MAP("screen-resize", REQ_NONE),
2062 ENUM_MAP("tree-parent", REQ_PARENT),
2063 };
2064 int alias;
2066 if (map_enum(&alias, obsolete, argv[2])) {
2067 if (alias != REQ_NONE)
2068 add_keybinding(keymap, alias, key);
2069 return OPT_ERR_OBSOLETE_REQUEST_NAME;
2070 }
2071 }
2072 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2073 request = add_run_request(keymap, key, argv + 2);
2074 if (request == REQ_UNKNOWN)
2075 return OPT_ERR_UNKNOWN_REQUEST_NAME;
2077 add_keybinding(keymap, request, key);
2079 return OPT_OK;
2080 }
2082 static enum option_code
2083 set_option(const char *opt, char *value)
2084 {
2085 const char *argv[SIZEOF_ARG];
2086 int argc = 0;
2088 if (!argv_from_string(argv, &argc, value))
2089 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
2091 if (!strcmp(opt, "color"))
2092 return option_color_command(argc, argv);
2094 if (!strcmp(opt, "set"))
2095 return option_set_command(argc, argv);
2097 if (!strcmp(opt, "bind"))
2098 return option_bind_command(argc, argv);
2100 return OPT_ERR_UNKNOWN_OPTION_COMMAND;
2101 }
2103 struct config_state {
2104 int lineno;
2105 bool errors;
2106 };
2108 static int
2109 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2110 {
2111 struct config_state *config = data;
2112 enum option_code status = OPT_ERR_NO_OPTION_VALUE;
2114 config->lineno++;
2116 /* Check for comment markers, since read_properties() will
2117 * only ensure opt and value are split at first " \t". */
2118 optlen = strcspn(opt, "#");
2119 if (optlen == 0)
2120 return OK;
2122 if (opt[optlen] == 0) {
2123 /* Look for comment endings in the value. */
2124 size_t len = strcspn(value, "#");
2126 if (len < valuelen) {
2127 valuelen = len;
2128 value[valuelen] = 0;
2129 }
2131 status = set_option(opt, value);
2132 }
2134 if (status != OPT_OK) {
2135 warn("Error on line %d, near '%.*s': %s",
2136 config->lineno, (int) optlen, opt, option_errors[status]);
2137 config->errors = TRUE;
2138 }
2140 /* Always keep going if errors are encountered. */
2141 return OK;
2142 }
2144 static void
2145 load_option_file(const char *path)
2146 {
2147 struct config_state config = { 0, FALSE };
2148 struct io io;
2150 /* It's OK that the file doesn't exist. */
2151 if (!io_open(&io, "%s", path))
2152 return;
2154 if (io_load(&io, " \t", read_option, &config) == ERR ||
2155 config.errors == TRUE)
2156 warn("Errors while loading %s.", path);
2157 }
2159 static int
2160 load_options(void)
2161 {
2162 const char *home = getenv("HOME");
2163 const char *tigrc_user = getenv("TIGRC_USER");
2164 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2165 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2166 char buf[SIZEOF_STR];
2168 if (!tigrc_system)
2169 tigrc_system = SYSCONFDIR "/tigrc";
2170 load_option_file(tigrc_system);
2172 if (!tigrc_user) {
2173 if (!home || !string_format(buf, "%s/.tigrc", home))
2174 return ERR;
2175 tigrc_user = buf;
2176 }
2177 load_option_file(tigrc_user);
2179 /* Add _after_ loading config files to avoid adding run requests
2180 * that conflict with keybindings. */
2181 add_builtin_run_requests();
2183 if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2184 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2185 int argc = 0;
2187 if (!string_format(buf, "%s", tig_diff_opts) ||
2188 !argv_from_string(diff_opts, &argc, buf))
2189 die("TIG_DIFF_OPTS contains too many arguments");
2190 else if (!argv_copy(&opt_diff_argv, diff_opts))
2191 die("Failed to format TIG_DIFF_OPTS arguments");
2192 }
2194 return OK;
2195 }
2198 /*
2199 * The viewer
2200 */
2202 struct view;
2203 struct view_ops;
2205 /* The display array of active views and the index of the current view. */
2206 static struct view *display[2];
2207 static WINDOW *display_win[2];
2208 static WINDOW *display_title[2];
2209 static unsigned int current_view;
2211 #define foreach_displayed_view(view, i) \
2212 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2214 #define displayed_views() (display[1] != NULL ? 2 : 1)
2216 /* Current head and commit ID */
2217 static char ref_blob[SIZEOF_REF] = "";
2218 static char ref_commit[SIZEOF_REF] = "HEAD";
2219 static char ref_head[SIZEOF_REF] = "HEAD";
2220 static char ref_branch[SIZEOF_REF] = "";
2222 enum view_type {
2223 VIEW_MAIN,
2224 VIEW_DIFF,
2225 VIEW_LOG,
2226 VIEW_TREE,
2227 VIEW_BLOB,
2228 VIEW_BLAME,
2229 VIEW_BRANCH,
2230 VIEW_HELP,
2231 VIEW_PAGER,
2232 VIEW_STATUS,
2233 VIEW_STAGE,
2234 };
2236 struct view {
2237 enum view_type type; /* View type */
2238 const char *name; /* View name */
2239 const char *cmd_env; /* Command line set via environment */
2240 const char *id; /* Points to either of ref_{head,commit,blob} */
2242 struct view_ops *ops; /* View operations */
2244 enum keymap keymap; /* What keymap does this view have */
2245 bool git_dir; /* Whether the view requires a git directory. */
2247 char ref[SIZEOF_REF]; /* Hovered commit reference */
2248 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2250 int height, width; /* The width and height of the main window */
2251 WINDOW *win; /* The main window */
2253 /* Navigation */
2254 unsigned long offset; /* Offset of the window top */
2255 unsigned long yoffset; /* Offset from the window side. */
2256 unsigned long lineno; /* Current line number */
2257 unsigned long p_offset; /* Previous offset of the window top */
2258 unsigned long p_yoffset;/* Previous offset from the window side */
2259 unsigned long p_lineno; /* Previous current line number */
2260 bool p_restore; /* Should the previous position be restored. */
2262 /* Searching */
2263 char grep[SIZEOF_STR]; /* Search string */
2264 regex_t *regex; /* Pre-compiled regexp */
2266 /* If non-NULL, points to the view that opened this view. If this view
2267 * is closed tig will switch back to the parent view. */
2268 struct view *parent;
2269 struct view *prev;
2271 /* Buffering */
2272 size_t lines; /* Total number of lines */
2273 struct line *line; /* Line index */
2274 unsigned int digits; /* Number of digits in the lines member. */
2276 /* Drawing */
2277 struct line *curline; /* Line currently being drawn. */
2278 enum line_type curtype; /* Attribute currently used for drawing. */
2279 unsigned long col; /* Column when drawing. */
2280 bool has_scrolled; /* View was scrolled. */
2282 /* Loading */
2283 const char **argv; /* Shell command arguments. */
2284 const char *dir; /* Directory from which to execute. */
2285 struct io io;
2286 struct io *pipe;
2287 time_t start_time;
2288 time_t update_secs;
2289 };
2291 struct view_ops {
2292 /* What type of content being displayed. Used in the title bar. */
2293 const char *type;
2294 /* Default command arguments. */
2295 const char **argv;
2296 /* Open and reads in all view content. */
2297 bool (*open)(struct view *view);
2298 /* Read one line; updates view->line. */
2299 bool (*read)(struct view *view, char *data);
2300 /* Draw one line; @lineno must be < view->height. */
2301 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2302 /* Depending on view handle a special requests. */
2303 enum request (*request)(struct view *view, enum request request, struct line *line);
2304 /* Search for regexp in a line. */
2305 bool (*grep)(struct view *view, struct line *line);
2306 /* Select line */
2307 void (*select)(struct view *view, struct line *line);
2308 /* Prepare view for loading */
2309 bool (*prepare)(struct view *view);
2310 };
2312 static struct view_ops blame_ops;
2313 static struct view_ops blob_ops;
2314 static struct view_ops diff_ops;
2315 static struct view_ops help_ops;
2316 static struct view_ops log_ops;
2317 static struct view_ops main_ops;
2318 static struct view_ops pager_ops;
2319 static struct view_ops stage_ops;
2320 static struct view_ops status_ops;
2321 static struct view_ops tree_ops;
2322 static struct view_ops branch_ops;
2324 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2325 { type, name, #env, ref, ops, map, git }
2327 #define VIEW_(id, name, ops, git, ref) \
2328 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2330 static struct view views[] = {
2331 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2332 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2333 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2334 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2335 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2336 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2337 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2338 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2339 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2340 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2341 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2342 };
2344 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2346 #define foreach_view(view, i) \
2347 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2349 #define view_is_displayed(view) \
2350 (view == display[0] || view == display[1])
2352 static enum request
2353 view_request(struct view *view, enum request request)
2354 {
2355 if (!view || !view->lines)
2356 return request;
2357 return view->ops->request(view, request, &view->line[view->lineno]);
2358 }
2361 /*
2362 * View drawing.
2363 */
2365 static inline void
2366 set_view_attr(struct view *view, enum line_type type)
2367 {
2368 if (!view->curline->selected && view->curtype != type) {
2369 (void) wattrset(view->win, get_line_attr(type));
2370 wchgat(view->win, -1, 0, type, NULL);
2371 view->curtype = type;
2372 }
2373 }
2375 static int
2376 draw_chars(struct view *view, enum line_type type, const char *string,
2377 int max_len, bool use_tilde)
2378 {
2379 static char out_buffer[BUFSIZ * 2];
2380 int len = 0;
2381 int col = 0;
2382 int trimmed = FALSE;
2383 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2385 if (max_len <= 0)
2386 return 0;
2388 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2390 set_view_attr(view, type);
2391 if (len > 0) {
2392 if (opt_iconv_out != ICONV_NONE) {
2393 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2394 size_t inlen = len + 1;
2396 char *outbuf = out_buffer;
2397 size_t outlen = sizeof(out_buffer);
2399 size_t ret;
2401 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2402 if (ret != (size_t) -1) {
2403 string = out_buffer;
2404 len = sizeof(out_buffer) - outlen;
2405 }
2406 }
2408 waddnstr(view->win, string, len);
2410 if (trimmed && use_tilde) {
2411 set_view_attr(view, LINE_DELIMITER);
2412 waddch(view->win, '~');
2413 col++;
2414 }
2415 }
2417 return col;
2418 }
2420 static int
2421 draw_space(struct view *view, enum line_type type, int max, int spaces)
2422 {
2423 static char space[] = " ";
2424 int col = 0;
2426 spaces = MIN(max, spaces);
2428 while (spaces > 0) {
2429 int len = MIN(spaces, sizeof(space) - 1);
2431 col += draw_chars(view, type, space, len, FALSE);
2432 spaces -= len;
2433 }
2435 return col;
2436 }
2438 static bool
2439 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2440 {
2441 char text[SIZEOF_STR];
2443 do {
2444 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2446 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2447 string += pos;
2448 } while (*string && view->width + view->yoffset > view->col);
2450 return view->width + view->yoffset <= view->col;
2451 }
2453 static bool
2454 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2455 {
2456 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2457 int max = view->width + view->yoffset - view->col;
2458 int i;
2460 if (max < size)
2461 size = max;
2463 set_view_attr(view, type);
2464 /* Using waddch() instead of waddnstr() ensures that
2465 * they'll be rendered correctly for the cursor line. */
2466 for (i = skip; i < size; i++)
2467 waddch(view->win, graphic[i]);
2469 view->col += size;
2470 if (size < max && skip <= size)
2471 waddch(view->win, ' ');
2472 view->col++;
2474 return view->width + view->yoffset <= view->col;
2475 }
2477 static bool
2478 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2479 {
2480 int max = MIN(view->width + view->yoffset - view->col, len);
2481 int col;
2483 if (text)
2484 col = draw_chars(view, type, text, max - 1, trim);
2485 else
2486 col = draw_space(view, type, max - 1, max - 1);
2488 view->col += col;
2489 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2490 return view->width + view->yoffset <= view->col;
2491 }
2493 static bool
2494 draw_date(struct view *view, struct time *time)
2495 {
2496 const char *date = mkdate(time, opt_date);
2497 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2499 return draw_field(view, LINE_DATE, date, cols, FALSE);
2500 }
2502 static bool
2503 draw_author(struct view *view, const char *author)
2504 {
2505 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2506 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2508 if (abbreviate && author)
2509 author = get_author_initials(author);
2511 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2512 }
2514 static bool
2515 draw_mode(struct view *view, mode_t mode)
2516 {
2517 const char *str;
2519 if (S_ISDIR(mode))
2520 str = "drwxr-xr-x";
2521 else if (S_ISLNK(mode))
2522 str = "lrwxrwxrwx";
2523 else if (S_ISGITLINK(mode))
2524 str = "m---------";
2525 else if (S_ISREG(mode) && mode & S_IXUSR)
2526 str = "-rwxr-xr-x";
2527 else if (S_ISREG(mode))
2528 str = "-rw-r--r--";
2529 else
2530 str = "----------";
2532 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2533 }
2535 static bool
2536 draw_lineno(struct view *view, unsigned int lineno)
2537 {
2538 char number[10];
2539 int digits3 = view->digits < 3 ? 3 : view->digits;
2540 int max = MIN(view->width + view->yoffset - view->col, digits3);
2541 char *text = NULL;
2542 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2544 lineno += view->offset + 1;
2545 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2546 static char fmt[] = "%1ld";
2548 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2549 if (string_format(number, fmt, lineno))
2550 text = number;
2551 }
2552 if (text)
2553 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2554 else
2555 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2556 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2557 }
2559 static bool
2560 draw_view_line(struct view *view, unsigned int lineno)
2561 {
2562 struct line *line;
2563 bool selected = (view->offset + lineno == view->lineno);
2565 assert(view_is_displayed(view));
2567 if (view->offset + lineno >= view->lines)
2568 return FALSE;
2570 line = &view->line[view->offset + lineno];
2572 wmove(view->win, lineno, 0);
2573 if (line->cleareol)
2574 wclrtoeol(view->win);
2575 view->col = 0;
2576 view->curline = line;
2577 view->curtype = LINE_NONE;
2578 line->selected = FALSE;
2579 line->dirty = line->cleareol = 0;
2581 if (selected) {
2582 set_view_attr(view, LINE_CURSOR);
2583 line->selected = TRUE;
2584 view->ops->select(view, line);
2585 }
2587 return view->ops->draw(view, line, lineno);
2588 }
2590 static void
2591 redraw_view_dirty(struct view *view)
2592 {
2593 bool dirty = FALSE;
2594 int lineno;
2596 for (lineno = 0; lineno < view->height; lineno++) {
2597 if (view->offset + lineno >= view->lines)
2598 break;
2599 if (!view->line[view->offset + lineno].dirty)
2600 continue;
2601 dirty = TRUE;
2602 if (!draw_view_line(view, lineno))
2603 break;
2604 }
2606 if (!dirty)
2607 return;
2608 wnoutrefresh(view->win);
2609 }
2611 static void
2612 redraw_view_from(struct view *view, int lineno)
2613 {
2614 assert(0 <= lineno && lineno < view->height);
2616 for (; lineno < view->height; lineno++) {
2617 if (!draw_view_line(view, lineno))
2618 break;
2619 }
2621 wnoutrefresh(view->win);
2622 }
2624 static void
2625 redraw_view(struct view *view)
2626 {
2627 werase(view->win);
2628 redraw_view_from(view, 0);
2629 }
2632 static void
2633 update_view_title(struct view *view)
2634 {
2635 char buf[SIZEOF_STR];
2636 char state[SIZEOF_STR];
2637 size_t bufpos = 0, statelen = 0;
2638 WINDOW *window = display[0] == view ? display_title[0] : display_title[1];
2640 assert(view_is_displayed(view));
2642 if (view->type != VIEW_STATUS && view->lines) {
2643 unsigned int view_lines = view->offset + view->height;
2644 unsigned int lines = view->lines
2645 ? MIN(view_lines, view->lines) * 100 / view->lines
2646 : 0;
2648 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2649 view->ops->type,
2650 view->lineno + 1,
2651 view->lines,
2652 lines);
2654 }
2656 if (view->pipe) {
2657 time_t secs = time(NULL) - view->start_time;
2659 /* Three git seconds are a long time ... */
2660 if (secs > 2)
2661 string_format_from(state, &statelen, " loading %lds", secs);
2662 }
2664 string_format_from(buf, &bufpos, "[%s]", view->name);
2665 if (*view->ref && bufpos < view->width) {
2666 size_t refsize = strlen(view->ref);
2667 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2669 if (minsize < view->width)
2670 refsize = view->width - minsize + 7;
2671 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2672 }
2674 if (statelen && bufpos < view->width) {
2675 string_format_from(buf, &bufpos, "%s", state);
2676 }
2678 if (view == display[current_view])
2679 wbkgdset(window, get_line_attr(LINE_TITLE_FOCUS));
2680 else
2681 wbkgdset(window, get_line_attr(LINE_TITLE_BLUR));
2683 mvwaddnstr(window, 0, 0, buf, bufpos);
2684 wclrtoeol(window);
2685 wnoutrefresh(window);
2686 }
2688 static int
2689 apply_step(double step, int value)
2690 {
2691 if (step >= 1)
2692 return (int) step;
2693 value *= step + 0.01;
2694 return value ? value : 1;
2695 }
2697 static void
2698 resize_display(void)
2699 {
2700 int offset, i;
2701 struct view *base = display[0];
2702 struct view *view = display[1] ? display[1] : display[0];
2704 /* Setup window dimensions */
2706 getmaxyx(stdscr, base->height, base->width);
2708 /* Make room for the status window. */
2709 base->height -= 1;
2711 if (view != base) {
2712 /* Horizontal split. */
2713 view->width = base->width;
2714 view->height = apply_step(opt_scale_split_view, base->height);
2715 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2716 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2717 base->height -= view->height;
2719 /* Make room for the title bar. */
2720 view->height -= 1;
2721 }
2723 /* Make room for the title bar. */
2724 base->height -= 1;
2726 offset = 0;
2728 foreach_displayed_view (view, i) {
2729 if (!display_win[i]) {
2730 display_win[i] = newwin(view->height, 0, offset, 0);
2731 if (!display_win[i])
2732 die("Failed to create %s view", view->name);
2734 scrollok(display_win[i], FALSE);
2736 display_title[i] = newwin(1, 0, offset + view->height, 0);
2737 if (!display_title[i])
2738 die("Failed to create title window");
2740 } else {
2741 wresize(display_win[i], view->height, view->width);
2742 mvwin(display_win[i], offset, 0);
2743 mvwin(display_title[i], offset + view->height, 0);
2744 }
2746 view->win = display_win[i];
2748 offset += view->height + 1;
2749 }
2750 }
2752 static void
2753 redraw_display(bool clear)
2754 {
2755 struct view *view;
2756 int i;
2758 foreach_displayed_view (view, i) {
2759 if (clear)
2760 wclear(view->win);
2761 redraw_view(view);
2762 update_view_title(view);
2763 }
2764 }
2767 /*
2768 * Option management
2769 */
2771 #define TOGGLE_MENU \
2772 TOGGLE_(LINENO, '.', "line numbers", &opt_line_number, NULL) \
2773 TOGGLE_(DATE, 'D', "dates", &opt_date, date_map) \
2774 TOGGLE_(AUTHOR, 'A', "author names", &opt_author, author_map) \
2775 TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \
2776 TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL)
2778 static void
2779 toggle_option(enum request request)
2780 {
2781 const struct {
2782 enum request request;
2783 const struct enum_map *map;
2784 size_t map_size;
2785 } data[] = {
2786 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
2787 TOGGLE_MENU
2788 #undef TOGGLE_
2789 };
2790 const struct menu_item menu[] = {
2791 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2792 TOGGLE_MENU
2793 #undef TOGGLE_
2794 { 0 }
2795 };
2796 int i = 0;
2798 if (request == REQ_OPTIONS) {
2799 if (!prompt_menu("Toggle option", menu, &i))
2800 return;
2801 } else {
2802 while (i < ARRAY_SIZE(data) && data[i].request != request)
2803 i++;
2804 if (i >= ARRAY_SIZE(data))
2805 die("Invalid request (%d)", request);
2806 }
2808 if (data[i].map != NULL) {
2809 unsigned int *opt = menu[i].data;
2811 *opt = (*opt + 1) % data[i].map_size;
2812 redraw_display(FALSE);
2813 report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2815 } else {
2816 bool *option = menu[i].data;
2818 *option = !*option;
2819 redraw_display(FALSE);
2820 report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
2821 }
2822 }
2824 static void
2825 maximize_view(struct view *view)
2826 {
2827 memset(display, 0, sizeof(display));
2828 current_view = 0;
2829 display[current_view] = view;
2830 resize_display();
2831 redraw_display(FALSE);
2832 report("");
2833 }
2836 /*
2837 * Navigation
2838 */
2840 static bool
2841 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2842 {
2843 if (lineno >= view->lines)
2844 lineno = view->lines > 0 ? view->lines - 1 : 0;
2846 if (offset > lineno || offset + view->height <= lineno) {
2847 unsigned long half = view->height / 2;
2849 if (lineno > half)
2850 offset = lineno - half;
2851 else
2852 offset = 0;
2853 }
2855 if (offset != view->offset || lineno != view->lineno) {
2856 view->offset = offset;
2857 view->lineno = lineno;
2858 return TRUE;
2859 }
2861 return FALSE;
2862 }
2864 /* Scrolling backend */
2865 static void
2866 do_scroll_view(struct view *view, int lines)
2867 {
2868 bool redraw_current_line = FALSE;
2870 /* The rendering expects the new offset. */
2871 view->offset += lines;
2873 assert(0 <= view->offset && view->offset < view->lines);
2874 assert(lines);
2876 /* Move current line into the view. */
2877 if (view->lineno < view->offset) {
2878 view->lineno = view->offset;
2879 redraw_current_line = TRUE;
2880 } else if (view->lineno >= view->offset + view->height) {
2881 view->lineno = view->offset + view->height - 1;
2882 redraw_current_line = TRUE;
2883 }
2885 assert(view->offset <= view->lineno && view->lineno < view->lines);
2887 /* Redraw the whole screen if scrolling is pointless. */
2888 if (view->height < ABS(lines)) {
2889 redraw_view(view);
2891 } else {
2892 int line = lines > 0 ? view->height - lines : 0;
2893 int end = line + ABS(lines);
2895 scrollok(view->win, TRUE);
2896 wscrl(view->win, lines);
2897 scrollok(view->win, FALSE);
2899 while (line < end && draw_view_line(view, line))
2900 line++;
2902 if (redraw_current_line)
2903 draw_view_line(view, view->lineno - view->offset);
2904 wnoutrefresh(view->win);
2905 }
2907 view->has_scrolled = TRUE;
2908 report("");
2909 }
2911 /* Scroll frontend */
2912 static void
2913 scroll_view(struct view *view, enum request request)
2914 {
2915 int lines = 1;
2917 assert(view_is_displayed(view));
2919 switch (request) {
2920 case REQ_SCROLL_FIRST_COL:
2921 view->yoffset = 0;
2922 redraw_view_from(view, 0);
2923 report("");
2924 return;
2925 case REQ_SCROLL_LEFT:
2926 if (view->yoffset == 0) {
2927 report("Cannot scroll beyond the first column");
2928 return;
2929 }
2930 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2931 view->yoffset = 0;
2932 else
2933 view->yoffset -= apply_step(opt_hscroll, view->width);
2934 redraw_view_from(view, 0);
2935 report("");
2936 return;
2937 case REQ_SCROLL_RIGHT:
2938 view->yoffset += apply_step(opt_hscroll, view->width);
2939 redraw_view(view);
2940 report("");
2941 return;
2942 case REQ_SCROLL_PAGE_DOWN:
2943 lines = view->height;
2944 case REQ_SCROLL_LINE_DOWN:
2945 if (view->offset + lines > view->lines)
2946 lines = view->lines - view->offset;
2948 if (lines == 0 || view->offset + view->height >= view->lines) {
2949 report("Cannot scroll beyond the last line");
2950 return;
2951 }
2952 break;
2954 case REQ_SCROLL_PAGE_UP:
2955 lines = view->height;
2956 case REQ_SCROLL_LINE_UP:
2957 if (lines > view->offset)
2958 lines = view->offset;
2960 if (lines == 0) {
2961 report("Cannot scroll beyond the first line");
2962 return;
2963 }
2965 lines = -lines;
2966 break;
2968 default:
2969 die("request %d not handled in switch", request);
2970 }
2972 do_scroll_view(view, lines);
2973 }
2975 /* Cursor moving */
2976 static void
2977 move_view(struct view *view, enum request request)
2978 {
2979 int scroll_steps = 0;
2980 int steps;
2982 switch (request) {
2983 case REQ_MOVE_FIRST_LINE:
2984 steps = -view->lineno;
2985 break;
2987 case REQ_MOVE_LAST_LINE:
2988 steps = view->lines - view->lineno - 1;
2989 break;
2991 case REQ_MOVE_PAGE_UP:
2992 steps = view->height > view->lineno
2993 ? -view->lineno : -view->height;
2994 break;
2996 case REQ_MOVE_PAGE_DOWN:
2997 steps = view->lineno + view->height >= view->lines
2998 ? view->lines - view->lineno - 1 : view->height;
2999 break;
3001 case REQ_MOVE_UP:
3002 steps = -1;
3003 break;
3005 case REQ_MOVE_DOWN:
3006 steps = 1;
3007 break;
3009 default:
3010 die("request %d not handled in switch", request);
3011 }
3013 if (steps <= 0 && view->lineno == 0) {
3014 report("Cannot move beyond the first line");
3015 return;
3017 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3018 report("Cannot move beyond the last line");
3019 return;
3020 }
3022 /* Move the current line */
3023 view->lineno += steps;
3024 assert(0 <= view->lineno && view->lineno < view->lines);
3026 /* Check whether the view needs to be scrolled */
3027 if (view->lineno < view->offset ||
3028 view->lineno >= view->offset + view->height) {
3029 scroll_steps = steps;
3030 if (steps < 0 && -steps > view->offset) {
3031 scroll_steps = -view->offset;
3033 } else if (steps > 0) {
3034 if (view->lineno == view->lines - 1 &&
3035 view->lines > view->height) {
3036 scroll_steps = view->lines - view->offset - 1;
3037 if (scroll_steps >= view->height)
3038 scroll_steps -= view->height - 1;
3039 }
3040 }
3041 }
3043 if (!view_is_displayed(view)) {
3044 view->offset += scroll_steps;
3045 assert(0 <= view->offset && view->offset < view->lines);
3046 view->ops->select(view, &view->line[view->lineno]);
3047 return;
3048 }
3050 /* Repaint the old "current" line if we be scrolling */
3051 if (ABS(steps) < view->height)
3052 draw_view_line(view, view->lineno - steps - view->offset);
3054 if (scroll_steps) {
3055 do_scroll_view(view, scroll_steps);
3056 return;
3057 }
3059 /* Draw the current line */
3060 draw_view_line(view, view->lineno - view->offset);
3062 wnoutrefresh(view->win);
3063 report("");
3064 }
3067 /*
3068 * Searching
3069 */
3071 static void search_view(struct view *view, enum request request);
3073 static bool
3074 grep_text(struct view *view, const char *text[])
3075 {
3076 regmatch_t pmatch;
3077 size_t i;
3079 for (i = 0; text[i]; i++)
3080 if (*text[i] &&
3081 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3082 return TRUE;
3083 return FALSE;
3084 }
3086 static void
3087 select_view_line(struct view *view, unsigned long lineno)
3088 {
3089 unsigned long old_lineno = view->lineno;
3090 unsigned long old_offset = view->offset;
3092 if (goto_view_line(view, view->offset, lineno)) {
3093 if (view_is_displayed(view)) {
3094 if (old_offset != view->offset) {
3095 redraw_view(view);
3096 } else {
3097 draw_view_line(view, old_lineno - view->offset);
3098 draw_view_line(view, view->lineno - view->offset);
3099 wnoutrefresh(view->win);
3100 }
3101 } else {
3102 view->ops->select(view, &view->line[view->lineno]);
3103 }
3104 }
3105 }
3107 static void
3108 find_next(struct view *view, enum request request)
3109 {
3110 unsigned long lineno = view->lineno;
3111 int direction;
3113 if (!*view->grep) {
3114 if (!*opt_search)
3115 report("No previous search");
3116 else
3117 search_view(view, request);
3118 return;
3119 }
3121 switch (request) {
3122 case REQ_SEARCH:
3123 case REQ_FIND_NEXT:
3124 direction = 1;
3125 break;
3127 case REQ_SEARCH_BACK:
3128 case REQ_FIND_PREV:
3129 direction = -1;
3130 break;
3132 default:
3133 return;
3134 }
3136 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3137 lineno += direction;
3139 /* Note, lineno is unsigned long so will wrap around in which case it
3140 * will become bigger than view->lines. */
3141 for (; lineno < view->lines; lineno += direction) {
3142 if (view->ops->grep(view, &view->line[lineno])) {
3143 select_view_line(view, lineno);
3144 report("Line %ld matches '%s'", lineno + 1, view->grep);
3145 return;
3146 }
3147 }
3149 report("No match found for '%s'", view->grep);
3150 }
3152 static void
3153 search_view(struct view *view, enum request request)
3154 {
3155 int regex_err;
3157 if (view->regex) {
3158 regfree(view->regex);
3159 *view->grep = 0;
3160 } else {
3161 view->regex = calloc(1, sizeof(*view->regex));
3162 if (!view->regex)
3163 return;
3164 }
3166 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3167 if (regex_err != 0) {
3168 char buf[SIZEOF_STR] = "unknown error";
3170 regerror(regex_err, view->regex, buf, sizeof(buf));
3171 report("Search failed: %s", buf);
3172 return;
3173 }
3175 string_copy(view->grep, opt_search);
3177 find_next(view, request);
3178 }
3180 /*
3181 * Incremental updating
3182 */
3184 static void
3185 reset_view(struct view *view)
3186 {
3187 int i;
3189 for (i = 0; i < view->lines; i++)
3190 free(view->line[i].data);
3191 free(view->line);
3193 view->p_offset = view->offset;
3194 view->p_yoffset = view->yoffset;
3195 view->p_lineno = view->lineno;
3197 view->line = NULL;
3198 view->offset = 0;
3199 view->yoffset = 0;
3200 view->lines = 0;
3201 view->lineno = 0;
3202 view->vid[0] = 0;
3203 view->update_secs = 0;
3204 }
3206 static const char *
3207 format_arg(const char *name)
3208 {
3209 static struct {
3210 const char *name;
3211 size_t namelen;
3212 const char *value;
3213 const char *value_if_empty;
3214 } vars[] = {
3215 #define FORMAT_VAR(name, value, value_if_empty) \
3216 { name, STRING_SIZE(name), value, value_if_empty }
3217 FORMAT_VAR("%(directory)", opt_path, ""),
3218 FORMAT_VAR("%(file)", opt_file, ""),
3219 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3220 FORMAT_VAR("%(head)", ref_head, ""),
3221 FORMAT_VAR("%(commit)", ref_commit, ""),
3222 FORMAT_VAR("%(blob)", ref_blob, ""),
3223 FORMAT_VAR("%(branch)", ref_branch, ""),
3224 };
3225 int i;
3227 for (i = 0; i < ARRAY_SIZE(vars); i++)
3228 if (!strncmp(name, vars[i].name, vars[i].namelen))
3229 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3231 report("Unknown replacement: `%s`", name);
3232 return NULL;
3233 }
3235 static bool
3236 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3237 {
3238 char buf[SIZEOF_STR];
3239 int argc;
3241 argv_free(*dst_argv);
3243 for (argc = 0; src_argv[argc]; argc++) {
3244 const char *arg = src_argv[argc];
3245 size_t bufpos = 0;
3247 if (!strcmp(arg, "%(fileargs)")) {
3248 if (!argv_append_array(dst_argv, opt_file_argv))
3249 break;
3250 continue;
3252 } else if (!strcmp(arg, "%(diffargs)")) {
3253 if (!argv_append_array(dst_argv, opt_diff_argv))
3254 break;
3255 continue;
3257 } else if (!strcmp(arg, "%(revargs)") ||
3258 (first && !strcmp(arg, "%(commit)"))) {
3259 if (!argv_append_array(dst_argv, opt_rev_argv))
3260 break;
3261 continue;
3262 }
3264 while (arg) {
3265 char *next = strstr(arg, "%(");
3266 int len = next - arg;
3267 const char *value;
3269 if (!next || !replace) {
3270 len = strlen(arg);
3271 value = "";
3273 } else {
3274 value = format_arg(next);
3276 if (!value) {
3277 return FALSE;
3278 }
3279 }
3281 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3282 return FALSE;
3284 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3285 }
3287 if (!argv_append(dst_argv, buf))
3288 break;
3289 }
3291 return src_argv[argc] == NULL;
3292 }
3294 static bool
3295 restore_view_position(struct view *view)
3296 {
3297 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3298 return FALSE;
3300 /* Changing the view position cancels the restoring. */
3301 /* FIXME: Changing back to the first line is not detected. */
3302 if (view->offset != 0 || view->lineno != 0) {
3303 view->p_restore = FALSE;
3304 return FALSE;
3305 }
3307 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3308 view_is_displayed(view))
3309 werase(view->win);
3311 view->yoffset = view->p_yoffset;
3312 view->p_restore = FALSE;
3314 return TRUE;
3315 }
3317 static void
3318 end_update(struct view *view, bool force)
3319 {
3320 if (!view->pipe)
3321 return;
3322 while (!view->ops->read(view, NULL))
3323 if (!force)
3324 return;
3325 if (force)
3326 io_kill(view->pipe);
3327 io_done(view->pipe);
3328 view->pipe = NULL;
3329 }
3331 static void
3332 setup_update(struct view *view, const char *vid)
3333 {
3334 reset_view(view);
3335 string_copy_rev(view->vid, vid);
3336 view->pipe = &view->io;
3337 view->start_time = time(NULL);
3338 }
3340 static bool
3341 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3342 {
3343 view->dir = dir;
3344 return format_argv(&view->argv, argv, replace, !view->prev);
3345 }
3347 static bool
3348 prepare_update(struct view *view, const char *argv[], const char *dir)
3349 {
3350 if (view->pipe)
3351 end_update(view, TRUE);
3352 return prepare_io(view, dir, argv, FALSE);
3353 }
3355 static bool
3356 start_update(struct view *view, const char **argv, const char *dir)
3357 {
3358 if (view->pipe)
3359 io_done(view->pipe);
3360 return prepare_io(view, dir, argv, FALSE) &&
3361 io_run(&view->io, IO_RD, dir, view->argv);
3362 }
3364 static bool
3365 prepare_update_file(struct view *view, const char *name)
3366 {
3367 if (view->pipe)
3368 end_update(view, TRUE);
3369 argv_free(view->argv);
3370 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3371 }
3373 static bool
3374 begin_update(struct view *view, bool refresh)
3375 {
3376 if (view->pipe)
3377 end_update(view, TRUE);
3379 if (!refresh) {
3380 if (view->ops->prepare) {
3381 if (!view->ops->prepare(view))
3382 return FALSE;
3383 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3384 return FALSE;
3385 }
3387 /* Put the current ref_* value to the view title ref
3388 * member. This is needed by the blob view. Most other
3389 * views sets it automatically after loading because the
3390 * first line is a commit line. */
3391 string_copy_rev(view->ref, view->id);
3392 }
3394 if (view->argv && view->argv[0] &&
3395 !io_run(&view->io, IO_RD, view->dir, view->argv))
3396 return FALSE;
3398 setup_update(view, view->id);
3400 return TRUE;
3401 }
3403 static bool
3404 update_view(struct view *view)
3405 {
3406 char out_buffer[BUFSIZ * 2];
3407 char *line;
3408 /* Clear the view and redraw everything since the tree sorting
3409 * might have rearranged things. */
3410 bool redraw = view->lines == 0;
3411 bool can_read = TRUE;
3413 if (!view->pipe)
3414 return TRUE;
3416 if (!io_can_read(view->pipe)) {
3417 if (view->lines == 0 && view_is_displayed(view)) {
3418 time_t secs = time(NULL) - view->start_time;
3420 if (secs > 1 && secs > view->update_secs) {
3421 if (view->update_secs == 0)
3422 redraw_view(view);
3423 update_view_title(view);
3424 view->update_secs = secs;
3425 }
3426 }
3427 return TRUE;
3428 }
3430 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3431 if (opt_iconv_in != ICONV_NONE) {
3432 ICONV_CONST char *inbuf = line;
3433 size_t inlen = strlen(line) + 1;
3435 char *outbuf = out_buffer;
3436 size_t outlen = sizeof(out_buffer);
3438 size_t ret;
3440 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3441 if (ret != (size_t) -1)
3442 line = out_buffer;
3443 }
3445 if (!view->ops->read(view, line)) {
3446 report("Allocation failure");
3447 end_update(view, TRUE);
3448 return FALSE;
3449 }
3450 }
3452 {
3453 unsigned long lines = view->lines;
3454 int digits;
3456 for (digits = 0; lines; digits++)
3457 lines /= 10;
3459 /* Keep the displayed view in sync with line number scaling. */
3460 if (digits != view->digits) {
3461 view->digits = digits;
3462 if (opt_line_number || view->type == VIEW_BLAME)
3463 redraw = TRUE;
3464 }
3465 }
3467 if (io_error(view->pipe)) {
3468 report("Failed to read: %s", io_strerror(view->pipe));
3469 end_update(view, TRUE);
3471 } else if (io_eof(view->pipe)) {
3472 if (view_is_displayed(view))
3473 report("");
3474 end_update(view, FALSE);
3475 }
3477 if (restore_view_position(view))
3478 redraw = TRUE;
3480 if (!view_is_displayed(view))
3481 return TRUE;
3483 if (redraw)
3484 redraw_view_from(view, 0);
3485 else
3486 redraw_view_dirty(view);
3488 /* Update the title _after_ the redraw so that if the redraw picks up a
3489 * commit reference in view->ref it'll be available here. */
3490 update_view_title(view);
3491 return TRUE;
3492 }
3494 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3496 static struct line *
3497 add_line_data(struct view *view, void *data, enum line_type type)
3498 {
3499 struct line *line;
3501 if (!realloc_lines(&view->line, view->lines, 1))
3502 return NULL;
3504 line = &view->line[view->lines++];
3505 memset(line, 0, sizeof(*line));
3506 line->type = type;
3507 line->data = data;
3508 line->dirty = 1;
3510 return line;
3511 }
3513 static struct line *
3514 add_line_text(struct view *view, const char *text, enum line_type type)
3515 {
3516 char *data = text ? strdup(text) : NULL;
3518 return data ? add_line_data(view, data, type) : NULL;
3519 }
3521 static struct line *
3522 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3523 {
3524 char buf[SIZEOF_STR];
3525 va_list args;
3527 va_start(args, fmt);
3528 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3529 buf[0] = 0;
3530 va_end(args);
3532 return buf[0] ? add_line_text(view, buf, type) : NULL;
3533 }
3535 /*
3536 * View opening
3537 */
3539 enum open_flags {
3540 OPEN_DEFAULT = 0, /* Use default view switching. */
3541 OPEN_SPLIT = 1, /* Split current view. */
3542 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3543 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3544 OPEN_PREPARED = 32, /* Open already prepared command. */
3545 };
3547 static void
3548 open_view(struct view *prev, enum request request, enum open_flags flags)
3549 {
3550 bool split = !!(flags & OPEN_SPLIT);
3551 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3552 bool nomaximize = !!(flags & OPEN_REFRESH);
3553 struct view *view = VIEW(request);
3554 int nviews = displayed_views();
3555 struct view *base_view = display[0];
3557 if (view == prev && nviews == 1 && !reload) {
3558 report("Already in %s view", view->name);
3559 return;
3560 }
3562 if (view->git_dir && !opt_git_dir[0]) {
3563 report("The %s view is disabled in pager view", view->name);
3564 return;
3565 }
3567 if (split) {
3568 display[1] = view;
3569 current_view = 1;
3570 view->parent = prev;
3571 } else if (!nomaximize) {
3572 /* Maximize the current view. */
3573 memset(display, 0, sizeof(display));
3574 current_view = 0;
3575 display[current_view] = view;
3576 }
3578 /* No prev signals that this is the first loaded view. */
3579 if (prev && view != prev) {
3580 view->prev = prev;
3581 }
3583 /* Resize the view when switching between split- and full-screen,
3584 * or when switching between two different full-screen views. */
3585 if (nviews != displayed_views() ||
3586 (nviews == 1 && base_view != display[0]))
3587 resize_display();
3589 if (view->ops->open) {
3590 if (view->pipe)
3591 end_update(view, TRUE);
3592 if (!view->ops->open(view)) {
3593 report("Failed to load %s view", view->name);
3594 return;
3595 }
3596 restore_view_position(view);
3598 } else if ((reload || strcmp(view->vid, view->id)) &&
3599 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3600 report("Failed to load %s view", view->name);
3601 return;
3602 }
3604 if (split && prev->lineno - prev->offset >= prev->height) {
3605 /* Take the title line into account. */
3606 int lines = prev->lineno - prev->offset - prev->height + 1;
3608 /* Scroll the view that was split if the current line is
3609 * outside the new limited view. */
3610 do_scroll_view(prev, lines);
3611 }
3613 if (prev && view != prev && split && view_is_displayed(prev)) {
3614 /* "Blur" the previous view. */
3615 update_view_title(prev);
3616 }
3618 if (view->pipe && view->lines == 0) {
3619 /* Clear the old view and let the incremental updating refill
3620 * the screen. */
3621 werase(view->win);
3622 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3623 report("");
3624 } else if (view_is_displayed(view)) {
3625 redraw_view(view);
3626 report("");
3627 }
3628 }
3630 static void
3631 open_external_viewer(const char *argv[], const char *dir)
3632 {
3633 def_prog_mode(); /* save current tty modes */
3634 endwin(); /* restore original tty modes */
3635 io_run_fg(argv, dir);
3636 fprintf(stderr, "Press Enter to continue");
3637 getc(opt_tty);
3638 reset_prog_mode();
3639 redraw_display(TRUE);
3640 }
3642 static void
3643 open_mergetool(const char *file)
3644 {
3645 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3647 open_external_viewer(mergetool_argv, opt_cdup);
3648 }
3650 static void
3651 open_editor(const char *file)
3652 {
3653 const char *editor_argv[] = { "vi", file, NULL };
3654 const char *editor;
3656 editor = getenv("GIT_EDITOR");
3657 if (!editor && *opt_editor)
3658 editor = opt_editor;
3659 if (!editor)
3660 editor = getenv("VISUAL");
3661 if (!editor)
3662 editor = getenv("EDITOR");
3663 if (!editor)
3664 editor = "vi";
3666 editor_argv[0] = editor;
3667 open_external_viewer(editor_argv, opt_cdup);
3668 }
3670 static void
3671 open_run_request(enum request request)
3672 {
3673 struct run_request *req = get_run_request(request);
3674 const char **argv = NULL;
3676 if (!req) {
3677 report("Unknown run request");
3678 return;
3679 }
3681 if (format_argv(&argv, req->argv, TRUE, FALSE))
3682 open_external_viewer(argv, NULL);
3683 if (argv)
3684 argv_free(argv);
3685 free(argv);
3686 }
3688 /*
3689 * User request switch noodle
3690 */
3692 static int
3693 view_driver(struct view *view, enum request request)
3694 {
3695 int i;
3697 if (request == REQ_NONE)
3698 return TRUE;
3700 if (request > REQ_NONE) {
3701 open_run_request(request);
3702 view_request(view, REQ_REFRESH);
3703 return TRUE;
3704 }
3706 request = view_request(view, request);
3707 if (request == REQ_NONE)
3708 return TRUE;
3710 switch (request) {
3711 case REQ_MOVE_UP:
3712 case REQ_MOVE_DOWN:
3713 case REQ_MOVE_PAGE_UP:
3714 case REQ_MOVE_PAGE_DOWN:
3715 case REQ_MOVE_FIRST_LINE:
3716 case REQ_MOVE_LAST_LINE:
3717 move_view(view, request);
3718 break;
3720 case REQ_SCROLL_FIRST_COL:
3721 case REQ_SCROLL_LEFT:
3722 case REQ_SCROLL_RIGHT:
3723 case REQ_SCROLL_LINE_DOWN:
3724 case REQ_SCROLL_LINE_UP:
3725 case REQ_SCROLL_PAGE_DOWN:
3726 case REQ_SCROLL_PAGE_UP:
3727 scroll_view(view, request);
3728 break;
3730 case REQ_VIEW_BLAME:
3731 if (!opt_file[0]) {
3732 report("No file chosen, press %s to open tree view",
3733 get_key(view->keymap, REQ_VIEW_TREE));
3734 break;
3735 }
3736 open_view(view, request, OPEN_DEFAULT);
3737 break;
3739 case REQ_VIEW_BLOB:
3740 if (!ref_blob[0]) {
3741 report("No file chosen, press %s to open tree view",
3742 get_key(view->keymap, REQ_VIEW_TREE));
3743 break;
3744 }
3745 open_view(view, request, OPEN_DEFAULT);
3746 break;
3748 case REQ_VIEW_PAGER:
3749 if (view == NULL) {
3750 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3751 die("Failed to open stdin");
3752 open_view(view, request, OPEN_PREPARED);
3753 break;
3754 }
3756 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3757 report("No pager content, press %s to run command from prompt",
3758 get_key(view->keymap, REQ_PROMPT));
3759 break;
3760 }
3761 open_view(view, request, OPEN_DEFAULT);
3762 break;
3764 case REQ_VIEW_STAGE:
3765 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3766 report("No stage content, press %s to open the status view and choose file",
3767 get_key(view->keymap, REQ_VIEW_STATUS));
3768 break;
3769 }
3770 open_view(view, request, OPEN_DEFAULT);
3771 break;
3773 case REQ_VIEW_STATUS:
3774 if (opt_is_inside_work_tree == FALSE) {
3775 report("The status view requires a working tree");
3776 break;
3777 }
3778 open_view(view, request, OPEN_DEFAULT);
3779 break;
3781 case REQ_VIEW_MAIN:
3782 case REQ_VIEW_DIFF:
3783 case REQ_VIEW_LOG:
3784 case REQ_VIEW_TREE:
3785 case REQ_VIEW_HELP:
3786 case REQ_VIEW_BRANCH:
3787 open_view(view, request, OPEN_DEFAULT);
3788 break;
3790 case REQ_NEXT:
3791 case REQ_PREVIOUS:
3792 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3794 if (view->parent) {
3795 int line;
3797 view = view->parent;
3798 line = view->lineno;
3799 move_view(view, request);
3800 if (view_is_displayed(view))
3801 update_view_title(view);
3802 if (line != view->lineno)
3803 view_request(view, REQ_ENTER);
3804 } else {
3805 move_view(view, request);
3806 }
3807 break;
3809 case REQ_VIEW_NEXT:
3810 {
3811 int nviews = displayed_views();
3812 int next_view = (current_view + 1) % nviews;
3814 if (next_view == current_view) {
3815 report("Only one view is displayed");
3816 break;
3817 }
3819 current_view = next_view;
3820 /* Blur out the title of the previous view. */
3821 update_view_title(view);
3822 report("");
3823 break;
3824 }
3825 case REQ_REFRESH:
3826 report("Refreshing is not yet supported for the %s view", view->name);
3827 break;
3829 case REQ_MAXIMIZE:
3830 if (displayed_views() == 2)
3831 maximize_view(view);
3832 break;
3834 case REQ_OPTIONS:
3835 case REQ_TOGGLE_LINENO:
3836 case REQ_TOGGLE_DATE:
3837 case REQ_TOGGLE_AUTHOR:
3838 case REQ_TOGGLE_REV_GRAPH:
3839 case REQ_TOGGLE_REFS:
3840 toggle_option(request);
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_argv) {
4233 int pos = argv_size(view->argv)
4234 - argv_size(opt_file_argv) - 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, 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 (!opt_untracked_dirs_content)
5808 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5810 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5811 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5812 return FALSE;
5814 /* Restore the exact position or use the specialized restore
5815 * mode? */
5816 if (!view->p_restore)
5817 status_restore(view);
5818 return TRUE;
5819 }
5821 static bool
5822 status_draw(struct view *view, struct line *line, unsigned int lineno)
5823 {
5824 struct status *status = line->data;
5825 enum line_type type;
5826 const char *text;
5828 if (!status) {
5829 switch (line->type) {
5830 case LINE_STAT_STAGED:
5831 type = LINE_STAT_SECTION;
5832 text = "Changes to be committed:";
5833 break;
5835 case LINE_STAT_UNSTAGED:
5836 type = LINE_STAT_SECTION;
5837 text = "Changed but not updated:";
5838 break;
5840 case LINE_STAT_UNTRACKED:
5841 type = LINE_STAT_SECTION;
5842 text = "Untracked files:";
5843 break;
5845 case LINE_STAT_NONE:
5846 type = LINE_DEFAULT;
5847 text = " (no files)";
5848 break;
5850 case LINE_STAT_HEAD:
5851 type = LINE_STAT_HEAD;
5852 text = status_onbranch;
5853 break;
5855 default:
5856 return FALSE;
5857 }
5858 } else {
5859 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5861 buf[0] = status->status;
5862 if (draw_text(view, line->type, buf, TRUE))
5863 return TRUE;
5864 type = LINE_DEFAULT;
5865 text = status->new.name;
5866 }
5868 draw_text(view, type, text, TRUE);
5869 return TRUE;
5870 }
5872 static enum request
5873 status_load_error(struct view *view, struct view *stage, const char *path)
5874 {
5875 if (displayed_views() == 2 || display[current_view] != view)
5876 maximize_view(view);
5877 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5878 return REQ_NONE;
5879 }
5881 static enum request
5882 status_enter(struct view *view, struct line *line)
5883 {
5884 struct status *status = line->data;
5885 const char *oldpath = status ? status->old.name : NULL;
5886 /* Diffs for unmerged entries are empty when passing the new
5887 * path, so leave it empty. */
5888 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5889 const char *info;
5890 enum open_flags split;
5891 struct view *stage = VIEW(REQ_VIEW_STAGE);
5893 if (line->type == LINE_STAT_NONE ||
5894 (!status && line[1].type == LINE_STAT_NONE)) {
5895 report("No file to diff");
5896 return REQ_NONE;
5897 }
5899 switch (line->type) {
5900 case LINE_STAT_STAGED:
5901 if (is_initial_commit()) {
5902 const char *no_head_diff_argv[] = {
5903 "git", "diff", "--no-color", "--patch-with-stat",
5904 "--", "/dev/null", newpath, NULL
5905 };
5907 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5908 return status_load_error(view, stage, newpath);
5909 } else {
5910 const char *index_show_argv[] = {
5911 "git", "diff-index", "--root", "--patch-with-stat",
5912 "-C", "-M", "--cached", "HEAD", "--",
5913 oldpath, newpath, NULL
5914 };
5916 if (!prepare_update(stage, index_show_argv, opt_cdup))
5917 return status_load_error(view, stage, newpath);
5918 }
5920 if (status)
5921 info = "Staged changes to %s";
5922 else
5923 info = "Staged changes";
5924 break;
5926 case LINE_STAT_UNSTAGED:
5927 {
5928 const char *files_show_argv[] = {
5929 "git", "diff-files", "--root", "--patch-with-stat",
5930 "-C", "-M", "--", oldpath, newpath, NULL
5931 };
5933 if (!prepare_update(stage, files_show_argv, opt_cdup))
5934 return status_load_error(view, stage, newpath);
5935 if (status)
5936 info = "Unstaged changes to %s";
5937 else
5938 info = "Unstaged changes";
5939 break;
5940 }
5941 case LINE_STAT_UNTRACKED:
5942 if (!newpath) {
5943 report("No file to show");
5944 return REQ_NONE;
5945 }
5947 if (!suffixcmp(status->new.name, -1, "/")) {
5948 report("Cannot display a directory");
5949 return REQ_NONE;
5950 }
5952 if (!prepare_update_file(stage, newpath))
5953 return status_load_error(view, stage, newpath);
5954 info = "Untracked file %s";
5955 break;
5957 case LINE_STAT_HEAD:
5958 return REQ_NONE;
5960 default:
5961 die("line type %d not handled in switch", line->type);
5962 }
5964 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5965 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5966 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5967 if (status) {
5968 stage_status = *status;
5969 } else {
5970 memset(&stage_status, 0, sizeof(stage_status));
5971 }
5973 stage_line_type = line->type;
5974 stage_chunks = 0;
5975 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5976 }
5978 return REQ_NONE;
5979 }
5981 static bool
5982 status_exists(struct status *status, enum line_type type)
5983 {
5984 struct view *view = VIEW(REQ_VIEW_STATUS);
5985 unsigned long lineno;
5987 for (lineno = 0; lineno < view->lines; lineno++) {
5988 struct line *line = &view->line[lineno];
5989 struct status *pos = line->data;
5991 if (line->type != type)
5992 continue;
5993 if (!pos && (!status || !status->status) && line[1].data) {
5994 select_view_line(view, lineno);
5995 return TRUE;
5996 }
5997 if (pos && !strcmp(status->new.name, pos->new.name)) {
5998 select_view_line(view, lineno);
5999 return TRUE;
6000 }
6001 }
6003 return FALSE;
6004 }
6007 static bool
6008 status_update_prepare(struct io *io, enum line_type type)
6009 {
6010 const char *staged_argv[] = {
6011 "git", "update-index", "-z", "--index-info", NULL
6012 };
6013 const char *others_argv[] = {
6014 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6015 };
6017 switch (type) {
6018 case LINE_STAT_STAGED:
6019 return io_run(io, IO_WR, opt_cdup, staged_argv);
6021 case LINE_STAT_UNSTAGED:
6022 case LINE_STAT_UNTRACKED:
6023 return io_run(io, IO_WR, opt_cdup, others_argv);
6025 default:
6026 die("line type %d not handled in switch", type);
6027 return FALSE;
6028 }
6029 }
6031 static bool
6032 status_update_write(struct io *io, struct status *status, enum line_type type)
6033 {
6034 char buf[SIZEOF_STR];
6035 size_t bufsize = 0;
6037 switch (type) {
6038 case LINE_STAT_STAGED:
6039 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6040 status->old.mode,
6041 status->old.rev,
6042 status->old.name, 0))
6043 return FALSE;
6044 break;
6046 case LINE_STAT_UNSTAGED:
6047 case LINE_STAT_UNTRACKED:
6048 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6049 return FALSE;
6050 break;
6052 default:
6053 die("line type %d not handled in switch", type);
6054 }
6056 return io_write(io, buf, bufsize);
6057 }
6059 static bool
6060 status_update_file(struct status *status, enum line_type type)
6061 {
6062 struct io io;
6063 bool result;
6065 if (!status_update_prepare(&io, type))
6066 return FALSE;
6068 result = status_update_write(&io, status, type);
6069 return io_done(&io) && result;
6070 }
6072 static bool
6073 status_update_files(struct view *view, struct line *line)
6074 {
6075 char buf[sizeof(view->ref)];
6076 struct io io;
6077 bool result = TRUE;
6078 struct line *pos = view->line + view->lines;
6079 int files = 0;
6080 int file, done;
6081 int cursor_y = -1, cursor_x = -1;
6083 if (!status_update_prepare(&io, line->type))
6084 return FALSE;
6086 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6087 files++;
6089 string_copy(buf, view->ref);
6090 getsyx(cursor_y, cursor_x);
6091 for (file = 0, done = 5; result && file < files; line++, file++) {
6092 int almost_done = file * 100 / files;
6094 if (almost_done > done) {
6095 done = almost_done;
6096 string_format(view->ref, "updating file %u of %u (%d%% done)",
6097 file, files, done);
6098 update_view_title(view);
6099 setsyx(cursor_y, cursor_x);
6100 doupdate();
6101 }
6102 result = status_update_write(&io, line->data, line->type);
6103 }
6104 string_copy(view->ref, buf);
6106 return io_done(&io) && result;
6107 }
6109 static bool
6110 status_update(struct view *view)
6111 {
6112 struct line *line = &view->line[view->lineno];
6114 assert(view->lines);
6116 if (!line->data) {
6117 /* This should work even for the "On branch" line. */
6118 if (line < view->line + view->lines && !line[1].data) {
6119 report("Nothing to update");
6120 return FALSE;
6121 }
6123 if (!status_update_files(view, line + 1)) {
6124 report("Failed to update file status");
6125 return FALSE;
6126 }
6128 } else if (!status_update_file(line->data, line->type)) {
6129 report("Failed to update file status");
6130 return FALSE;
6131 }
6133 return TRUE;
6134 }
6136 static bool
6137 status_revert(struct status *status, enum line_type type, bool has_none)
6138 {
6139 if (!status || type != LINE_STAT_UNSTAGED) {
6140 if (type == LINE_STAT_STAGED) {
6141 report("Cannot revert changes to staged files");
6142 } else if (type == LINE_STAT_UNTRACKED) {
6143 report("Cannot revert changes to untracked files");
6144 } else if (has_none) {
6145 report("Nothing to revert");
6146 } else {
6147 report("Cannot revert changes to multiple files");
6148 }
6150 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6151 char mode[10] = "100644";
6152 const char *reset_argv[] = {
6153 "git", "update-index", "--cacheinfo", mode,
6154 status->old.rev, status->old.name, NULL
6155 };
6156 const char *checkout_argv[] = {
6157 "git", "checkout", "--", status->old.name, NULL
6158 };
6160 if (status->status == 'U') {
6161 string_format(mode, "%5o", status->old.mode);
6163 if (status->old.mode == 0 && status->new.mode == 0) {
6164 reset_argv[2] = "--force-remove";
6165 reset_argv[3] = status->old.name;
6166 reset_argv[4] = NULL;
6167 }
6169 if (!io_run_fg(reset_argv, opt_cdup))
6170 return FALSE;
6171 if (status->old.mode == 0 && status->new.mode == 0)
6172 return TRUE;
6173 }
6175 return io_run_fg(checkout_argv, opt_cdup);
6176 }
6178 return FALSE;
6179 }
6181 static enum request
6182 status_request(struct view *view, enum request request, struct line *line)
6183 {
6184 struct status *status = line->data;
6186 switch (request) {
6187 case REQ_STATUS_UPDATE:
6188 if (!status_update(view))
6189 return REQ_NONE;
6190 break;
6192 case REQ_STATUS_REVERT:
6193 if (!status_revert(status, line->type, status_has_none(view, line)))
6194 return REQ_NONE;
6195 break;
6197 case REQ_STATUS_MERGE:
6198 if (!status || status->status != 'U') {
6199 report("Merging only possible for files with unmerged status ('U').");
6200 return REQ_NONE;
6201 }
6202 open_mergetool(status->new.name);
6203 break;
6205 case REQ_EDIT:
6206 if (!status)
6207 return request;
6208 if (status->status == 'D') {
6209 report("File has been deleted.");
6210 return REQ_NONE;
6211 }
6213 open_editor(status->new.name);
6214 break;
6216 case REQ_VIEW_BLAME:
6217 if (status)
6218 opt_ref[0] = 0;
6219 return request;
6221 case REQ_ENTER:
6222 /* After returning the status view has been split to
6223 * show the stage view. No further reloading is
6224 * necessary. */
6225 return status_enter(view, line);
6227 case REQ_REFRESH:
6228 /* Simply reload the view. */
6229 break;
6231 default:
6232 return request;
6233 }
6235 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6237 return REQ_NONE;
6238 }
6240 static void
6241 status_select(struct view *view, struct line *line)
6242 {
6243 struct status *status = line->data;
6244 char file[SIZEOF_STR] = "all files";
6245 const char *text;
6246 const char *key;
6248 if (status && !string_format(file, "'%s'", status->new.name))
6249 return;
6251 if (!status && line[1].type == LINE_STAT_NONE)
6252 line++;
6254 switch (line->type) {
6255 case LINE_STAT_STAGED:
6256 text = "Press %s to unstage %s for commit";
6257 break;
6259 case LINE_STAT_UNSTAGED:
6260 text = "Press %s to stage %s for commit";
6261 break;
6263 case LINE_STAT_UNTRACKED:
6264 text = "Press %s to stage %s for addition";
6265 break;
6267 case LINE_STAT_HEAD:
6268 case LINE_STAT_NONE:
6269 text = "Nothing to update";
6270 break;
6272 default:
6273 die("line type %d not handled in switch", line->type);
6274 }
6276 if (status && status->status == 'U') {
6277 text = "Press %s to resolve conflict in %s";
6278 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6280 } else {
6281 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6282 }
6284 string_format(view->ref, text, key, file);
6285 if (status)
6286 string_copy(opt_file, status->new.name);
6287 }
6289 static bool
6290 status_grep(struct view *view, struct line *line)
6291 {
6292 struct status *status = line->data;
6294 if (status) {
6295 const char buf[2] = { status->status, 0 };
6296 const char *text[] = { status->new.name, buf, NULL };
6298 return grep_text(view, text);
6299 }
6301 return FALSE;
6302 }
6304 static struct view_ops status_ops = {
6305 "file",
6306 NULL,
6307 status_open,
6308 NULL,
6309 status_draw,
6310 status_request,
6311 status_grep,
6312 status_select,
6313 };
6316 static bool
6317 stage_diff_write(struct io *io, struct line *line, struct line *end)
6318 {
6319 while (line < end) {
6320 if (!io_write(io, line->data, strlen(line->data)) ||
6321 !io_write(io, "\n", 1))
6322 return FALSE;
6323 line++;
6324 if (line->type == LINE_DIFF_CHUNK ||
6325 line->type == LINE_DIFF_HEADER)
6326 break;
6327 }
6329 return TRUE;
6330 }
6332 static struct line *
6333 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6334 {
6335 for (; view->line < line; line--)
6336 if (line->type == type)
6337 return line;
6339 return NULL;
6340 }
6342 static bool
6343 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6344 {
6345 const char *apply_argv[SIZEOF_ARG] = {
6346 "git", "apply", "--whitespace=nowarn", NULL
6347 };
6348 struct line *diff_hdr;
6349 struct io io;
6350 int argc = 3;
6352 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6353 if (!diff_hdr)
6354 return FALSE;
6356 if (!revert)
6357 apply_argv[argc++] = "--cached";
6358 if (revert || stage_line_type == LINE_STAT_STAGED)
6359 apply_argv[argc++] = "-R";
6360 apply_argv[argc++] = "-";
6361 apply_argv[argc++] = NULL;
6362 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6363 return FALSE;
6365 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6366 !stage_diff_write(&io, chunk, view->line + view->lines))
6367 chunk = NULL;
6369 io_done(&io);
6370 io_run_bg(update_index_argv);
6372 return chunk ? TRUE : FALSE;
6373 }
6375 static bool
6376 stage_update(struct view *view, struct line *line)
6377 {
6378 struct line *chunk = NULL;
6380 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6381 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6383 if (chunk) {
6384 if (!stage_apply_chunk(view, chunk, FALSE)) {
6385 report("Failed to apply chunk");
6386 return FALSE;
6387 }
6389 } else if (!stage_status.status) {
6390 view = VIEW(REQ_VIEW_STATUS);
6392 for (line = view->line; line < view->line + view->lines; line++)
6393 if (line->type == stage_line_type)
6394 break;
6396 if (!status_update_files(view, line + 1)) {
6397 report("Failed to update files");
6398 return FALSE;
6399 }
6401 } else if (!status_update_file(&stage_status, stage_line_type)) {
6402 report("Failed to update file");
6403 return FALSE;
6404 }
6406 return TRUE;
6407 }
6409 static bool
6410 stage_revert(struct view *view, struct line *line)
6411 {
6412 struct line *chunk = NULL;
6414 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6415 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6417 if (chunk) {
6418 if (!prompt_yesno("Are you sure you want to revert changes?"))
6419 return FALSE;
6421 if (!stage_apply_chunk(view, chunk, TRUE)) {
6422 report("Failed to revert chunk");
6423 return FALSE;
6424 }
6425 return TRUE;
6427 } else {
6428 return status_revert(stage_status.status ? &stage_status : NULL,
6429 stage_line_type, FALSE);
6430 }
6431 }
6434 static void
6435 stage_next(struct view *view, struct line *line)
6436 {
6437 int i;
6439 if (!stage_chunks) {
6440 for (line = view->line; line < view->line + view->lines; line++) {
6441 if (line->type != LINE_DIFF_CHUNK)
6442 continue;
6444 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6445 report("Allocation failure");
6446 return;
6447 }
6449 stage_chunk[stage_chunks++] = line - view->line;
6450 }
6451 }
6453 for (i = 0; i < stage_chunks; i++) {
6454 if (stage_chunk[i] > view->lineno) {
6455 do_scroll_view(view, stage_chunk[i] - view->lineno);
6456 report("Chunk %d of %d", i + 1, stage_chunks);
6457 return;
6458 }
6459 }
6461 report("No next chunk found");
6462 }
6464 static enum request
6465 stage_request(struct view *view, enum request request, struct line *line)
6466 {
6467 switch (request) {
6468 case REQ_STATUS_UPDATE:
6469 if (!stage_update(view, line))
6470 return REQ_NONE;
6471 break;
6473 case REQ_STATUS_REVERT:
6474 if (!stage_revert(view, line))
6475 return REQ_NONE;
6476 break;
6478 case REQ_STAGE_NEXT:
6479 if (stage_line_type == LINE_STAT_UNTRACKED) {
6480 report("File is untracked; press %s to add",
6481 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6482 return REQ_NONE;
6483 }
6484 stage_next(view, line);
6485 return REQ_NONE;
6487 case REQ_EDIT:
6488 if (!stage_status.new.name[0])
6489 return request;
6490 if (stage_status.status == 'D') {
6491 report("File has been deleted.");
6492 return REQ_NONE;
6493 }
6495 open_editor(stage_status.new.name);
6496 break;
6498 case REQ_REFRESH:
6499 /* Reload everything ... */
6500 break;
6502 case REQ_VIEW_BLAME:
6503 if (stage_status.new.name[0]) {
6504 string_copy(opt_file, stage_status.new.name);
6505 opt_ref[0] = 0;
6506 }
6507 return request;
6509 case REQ_ENTER:
6510 return pager_request(view, request, line);
6512 default:
6513 return request;
6514 }
6516 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6517 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6519 /* Check whether the staged entry still exists, and close the
6520 * stage view if it doesn't. */
6521 if (!status_exists(&stage_status, stage_line_type)) {
6522 status_restore(VIEW(REQ_VIEW_STATUS));
6523 return REQ_VIEW_CLOSE;
6524 }
6526 if (stage_line_type == LINE_STAT_UNTRACKED) {
6527 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6528 report("Cannot display a directory");
6529 return REQ_NONE;
6530 }
6532 if (!prepare_update_file(view, stage_status.new.name)) {
6533 report("Failed to open file: %s", strerror(errno));
6534 return REQ_NONE;
6535 }
6536 }
6537 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6539 return REQ_NONE;
6540 }
6542 static struct view_ops stage_ops = {
6543 "line",
6544 NULL,
6545 NULL,
6546 pager_read,
6547 pager_draw,
6548 stage_request,
6549 pager_grep,
6550 pager_select,
6551 };
6554 /*
6555 * Revision graph
6556 */
6558 struct commit {
6559 char id[SIZEOF_REV]; /* SHA1 ID. */
6560 char title[128]; /* First line of the commit message. */
6561 const char *author; /* Author of the commit. */
6562 struct time time; /* Date from the author ident. */
6563 struct ref_list *refs; /* Repository references. */
6564 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6565 size_t graph_size; /* The width of the graph array. */
6566 bool has_parents; /* Rewritten --parents seen. */
6567 };
6569 /* Size of rev graph with no "padding" columns */
6570 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6572 struct rev_graph {
6573 struct rev_graph *prev, *next, *parents;
6574 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6575 size_t size;
6576 struct commit *commit;
6577 size_t pos;
6578 unsigned int boundary:1;
6579 };
6581 /* Parents of the commit being visualized. */
6582 static struct rev_graph graph_parents[4];
6584 /* The current stack of revisions on the graph. */
6585 static struct rev_graph graph_stacks[4] = {
6586 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6587 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6588 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6589 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6590 };
6592 static inline bool
6593 graph_parent_is_merge(struct rev_graph *graph)
6594 {
6595 return graph->parents->size > 1;
6596 }
6598 static inline void
6599 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6600 {
6601 struct commit *commit = graph->commit;
6603 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6604 commit->graph[commit->graph_size++] = symbol;
6605 }
6607 static void
6608 clear_rev_graph(struct rev_graph *graph)
6609 {
6610 graph->boundary = 0;
6611 graph->size = graph->pos = 0;
6612 graph->commit = NULL;
6613 memset(graph->parents, 0, sizeof(*graph->parents));
6614 }
6616 static void
6617 done_rev_graph(struct rev_graph *graph)
6618 {
6619 if (graph_parent_is_merge(graph) &&
6620 graph->pos < graph->size - 1 &&
6621 graph->next->size == graph->size + graph->parents->size - 1) {
6622 size_t i = graph->pos + graph->parents->size - 1;
6624 graph->commit->graph_size = i * 2;
6625 while (i < graph->next->size - 1) {
6626 append_to_rev_graph(graph, ' ');
6627 append_to_rev_graph(graph, '\\');
6628 i++;
6629 }
6630 }
6632 clear_rev_graph(graph);
6633 }
6635 static void
6636 push_rev_graph(struct rev_graph *graph, const char *parent)
6637 {
6638 int i;
6640 /* "Collapse" duplicate parents lines.
6641 *
6642 * FIXME: This needs to also update update the drawn graph but
6643 * for now it just serves as a method for pruning graph lines. */
6644 for (i = 0; i < graph->size; i++)
6645 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6646 return;
6648 if (graph->size < SIZEOF_REVITEMS) {
6649 string_copy_rev(graph->rev[graph->size++], parent);
6650 }
6651 }
6653 static chtype
6654 get_rev_graph_symbol(struct rev_graph *graph)
6655 {
6656 chtype symbol;
6658 if (graph->boundary)
6659 symbol = REVGRAPH_BOUND;
6660 else if (graph->parents->size == 0)
6661 symbol = REVGRAPH_INIT;
6662 else if (graph_parent_is_merge(graph))
6663 symbol = REVGRAPH_MERGE;
6664 else if (graph->pos >= graph->size)
6665 symbol = REVGRAPH_BRANCH;
6666 else
6667 symbol = REVGRAPH_COMMIT;
6669 return symbol;
6670 }
6672 static void
6673 draw_rev_graph(struct rev_graph *graph)
6674 {
6675 struct rev_filler {
6676 chtype separator, line;
6677 };
6678 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6679 static struct rev_filler fillers[] = {
6680 { ' ', '|' },
6681 { '`', '.' },
6682 { '\'', ' ' },
6683 { '/', ' ' },
6684 };
6685 chtype symbol = get_rev_graph_symbol(graph);
6686 struct rev_filler *filler;
6687 size_t i;
6689 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6690 filler = &fillers[DEFAULT];
6692 for (i = 0; i < graph->pos; i++) {
6693 append_to_rev_graph(graph, filler->line);
6694 if (graph_parent_is_merge(graph->prev) &&
6695 graph->prev->pos == i)
6696 filler = &fillers[RSHARP];
6698 append_to_rev_graph(graph, filler->separator);
6699 }
6701 /* Place the symbol for this revision. */
6702 append_to_rev_graph(graph, symbol);
6704 if (graph->prev->size > graph->size)
6705 filler = &fillers[RDIAG];
6706 else
6707 filler = &fillers[DEFAULT];
6709 i++;
6711 for (; i < graph->size; i++) {
6712 append_to_rev_graph(graph, filler->separator);
6713 append_to_rev_graph(graph, filler->line);
6714 if (graph_parent_is_merge(graph->prev) &&
6715 i < graph->prev->pos + graph->parents->size)
6716 filler = &fillers[RSHARP];
6717 if (graph->prev->size > graph->size)
6718 filler = &fillers[LDIAG];
6719 }
6721 if (graph->prev->size > graph->size) {
6722 append_to_rev_graph(graph, filler->separator);
6723 if (filler->line != ' ')
6724 append_to_rev_graph(graph, filler->line);
6725 }
6726 }
6728 /* Prepare the next rev graph */
6729 static void
6730 prepare_rev_graph(struct rev_graph *graph)
6731 {
6732 size_t i;
6734 /* First, traverse all lines of revisions up to the active one. */
6735 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6736 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6737 break;
6739 push_rev_graph(graph->next, graph->rev[graph->pos]);
6740 }
6742 /* Interleave the new revision parent(s). */
6743 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6744 push_rev_graph(graph->next, graph->parents->rev[i]);
6746 /* Lastly, put any remaining revisions. */
6747 for (i = graph->pos + 1; i < graph->size; i++)
6748 push_rev_graph(graph->next, graph->rev[i]);
6749 }
6751 static void
6752 update_rev_graph(struct view *view, struct rev_graph *graph)
6753 {
6754 /* If this is the finalizing update ... */
6755 if (graph->commit)
6756 prepare_rev_graph(graph);
6758 /* Graph visualization needs a one rev look-ahead,
6759 * so the first update doesn't visualize anything. */
6760 if (!graph->prev->commit)
6761 return;
6763 if (view->lines > 2)
6764 view->line[view->lines - 3].dirty = 1;
6765 if (view->lines > 1)
6766 view->line[view->lines - 2].dirty = 1;
6767 draw_rev_graph(graph->prev);
6768 done_rev_graph(graph->prev->prev);
6769 }
6772 /*
6773 * Main view backend
6774 */
6776 static const char *main_argv[SIZEOF_ARG] = {
6777 "git", "log", "--no-color", "--pretty=raw", "--parents",
6778 "--topo-order", "%(diffargs)", "%(revargs)",
6779 "--", "%(fileargs)", NULL
6780 };
6782 static bool
6783 main_draw(struct view *view, struct line *line, unsigned int lineno)
6784 {
6785 struct commit *commit = line->data;
6787 if (!commit->author)
6788 return FALSE;
6790 if (opt_date && draw_date(view, &commit->time))
6791 return TRUE;
6793 if (opt_author && draw_author(view, commit->author))
6794 return TRUE;
6796 if (opt_rev_graph && commit->graph_size &&
6797 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6798 return TRUE;
6800 if (opt_show_refs && commit->refs) {
6801 size_t i;
6803 for (i = 0; i < commit->refs->size; i++) {
6804 struct ref *ref = commit->refs->refs[i];
6805 enum line_type type;
6807 if (ref->head)
6808 type = LINE_MAIN_HEAD;
6809 else if (ref->ltag)
6810 type = LINE_MAIN_LOCAL_TAG;
6811 else if (ref->tag)
6812 type = LINE_MAIN_TAG;
6813 else if (ref->tracked)
6814 type = LINE_MAIN_TRACKED;
6815 else if (ref->remote)
6816 type = LINE_MAIN_REMOTE;
6817 else
6818 type = LINE_MAIN_REF;
6820 if (draw_text(view, type, "[", TRUE) ||
6821 draw_text(view, type, ref->name, TRUE) ||
6822 draw_text(view, type, "]", TRUE))
6823 return TRUE;
6825 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6826 return TRUE;
6827 }
6828 }
6830 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6831 return TRUE;
6832 }
6834 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6835 static bool
6836 main_read(struct view *view, char *line)
6837 {
6838 static struct rev_graph *graph = graph_stacks;
6839 enum line_type type;
6840 struct commit *commit;
6842 if (!line) {
6843 int i;
6845 if (!view->lines && !view->prev)
6846 die("No revisions match the given arguments.");
6847 if (view->lines > 0) {
6848 commit = view->line[view->lines - 1].data;
6849 view->line[view->lines - 1].dirty = 1;
6850 if (!commit->author) {
6851 view->lines--;
6852 free(commit);
6853 graph->commit = NULL;
6854 }
6855 }
6856 update_rev_graph(view, graph);
6858 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6859 clear_rev_graph(&graph_stacks[i]);
6860 return TRUE;
6861 }
6863 type = get_line_type(line);
6864 if (type == LINE_COMMIT) {
6865 commit = calloc(1, sizeof(struct commit));
6866 if (!commit)
6867 return FALSE;
6869 line += STRING_SIZE("commit ");
6870 if (*line == '-') {
6871 graph->boundary = 1;
6872 line++;
6873 }
6875 string_copy_rev(commit->id, line);
6876 commit->refs = get_ref_list(commit->id);
6877 graph->commit = commit;
6878 add_line_data(view, commit, LINE_MAIN_COMMIT);
6880 while ((line = strchr(line, ' '))) {
6881 line++;
6882 push_rev_graph(graph->parents, line);
6883 commit->has_parents = TRUE;
6884 }
6885 return TRUE;
6886 }
6888 if (!view->lines)
6889 return TRUE;
6890 commit = view->line[view->lines - 1].data;
6892 switch (type) {
6893 case LINE_PARENT:
6894 if (commit->has_parents)
6895 break;
6896 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6897 break;
6899 case LINE_AUTHOR:
6900 parse_author_line(line + STRING_SIZE("author "),
6901 &commit->author, &commit->time);
6902 update_rev_graph(view, graph);
6903 graph = graph->next;
6904 break;
6906 default:
6907 /* Fill in the commit title if it has not already been set. */
6908 if (commit->title[0])
6909 break;
6911 /* Require titles to start with a non-space character at the
6912 * offset used by git log. */
6913 if (strncmp(line, " ", 4))
6914 break;
6915 line += 4;
6916 /* Well, if the title starts with a whitespace character,
6917 * try to be forgiving. Otherwise we end up with no title. */
6918 while (isspace(*line))
6919 line++;
6920 if (*line == '\0')
6921 break;
6922 /* FIXME: More graceful handling of titles; append "..." to
6923 * shortened titles, etc. */
6925 string_expand(commit->title, sizeof(commit->title), line, 1);
6926 view->line[view->lines - 1].dirty = 1;
6927 }
6929 return TRUE;
6930 }
6932 static enum request
6933 main_request(struct view *view, enum request request, struct line *line)
6934 {
6935 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6937 switch (request) {
6938 case REQ_ENTER:
6939 if (view_is_displayed(view) && display[0] != view)
6940 maximize_view(view);
6941 open_view(view, REQ_VIEW_DIFF, flags);
6942 break;
6943 case REQ_REFRESH:
6944 load_refs();
6945 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6946 break;
6947 default:
6948 return request;
6949 }
6951 return REQ_NONE;
6952 }
6954 static bool
6955 grep_refs(struct ref_list *list, regex_t *regex)
6956 {
6957 regmatch_t pmatch;
6958 size_t i;
6960 if (!opt_show_refs || !list)
6961 return FALSE;
6963 for (i = 0; i < list->size; i++) {
6964 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6965 return TRUE;
6966 }
6968 return FALSE;
6969 }
6971 static bool
6972 main_grep(struct view *view, struct line *line)
6973 {
6974 struct commit *commit = line->data;
6975 const char *text[] = {
6976 commit->title,
6977 opt_author ? commit->author : "",
6978 mkdate(&commit->time, opt_date),
6979 NULL
6980 };
6982 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6983 }
6985 static void
6986 main_select(struct view *view, struct line *line)
6987 {
6988 struct commit *commit = line->data;
6990 string_copy_rev(view->ref, commit->id);
6991 string_copy_rev(ref_commit, view->ref);
6992 }
6994 static struct view_ops main_ops = {
6995 "commit",
6996 main_argv,
6997 NULL,
6998 main_read,
6999 main_draw,
7000 main_request,
7001 main_grep,
7002 main_select,
7003 };
7006 /*
7007 * Status management
7008 */
7010 /* Whether or not the curses interface has been initialized. */
7011 static bool cursed = FALSE;
7013 /* Terminal hacks and workarounds. */
7014 static bool use_scroll_redrawwin;
7015 static bool use_scroll_status_wclear;
7017 /* The status window is used for polling keystrokes. */
7018 static WINDOW *status_win;
7020 /* Reading from the prompt? */
7021 static bool input_mode = FALSE;
7023 static bool status_empty = FALSE;
7025 /* Update status and title window. */
7026 static void
7027 report(const char *msg, ...)
7028 {
7029 struct view *view = display[current_view];
7031 if (input_mode)
7032 return;
7034 if (!view) {
7035 char buf[SIZEOF_STR];
7036 va_list args;
7038 va_start(args, msg);
7039 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7040 buf[sizeof(buf) - 1] = 0;
7041 buf[sizeof(buf) - 2] = '.';
7042 buf[sizeof(buf) - 3] = '.';
7043 buf[sizeof(buf) - 4] = '.';
7044 }
7045 va_end(args);
7046 die("%s", buf);
7047 }
7049 if (!status_empty || *msg) {
7050 va_list args;
7052 va_start(args, msg);
7054 wmove(status_win, 0, 0);
7055 if (view->has_scrolled && use_scroll_status_wclear)
7056 wclear(status_win);
7057 if (*msg) {
7058 vwprintw(status_win, msg, args);
7059 status_empty = FALSE;
7060 } else {
7061 status_empty = TRUE;
7062 }
7063 wclrtoeol(status_win);
7064 wnoutrefresh(status_win);
7066 va_end(args);
7067 }
7069 update_view_title(view);
7070 }
7072 static void
7073 init_display(void)
7074 {
7075 const char *term;
7076 int x, y;
7078 /* Initialize the curses library */
7079 if (isatty(STDIN_FILENO)) {
7080 cursed = !!initscr();
7081 opt_tty = stdin;
7082 } else {
7083 /* Leave stdin and stdout alone when acting as a pager. */
7084 opt_tty = fopen("/dev/tty", "r+");
7085 if (!opt_tty)
7086 die("Failed to open /dev/tty");
7087 cursed = !!newterm(NULL, opt_tty, opt_tty);
7088 }
7090 if (!cursed)
7091 die("Failed to initialize curses");
7093 nonl(); /* Disable conversion and detect newlines from input. */
7094 cbreak(); /* Take input chars one at a time, no wait for \n */
7095 noecho(); /* Don't echo input */
7096 leaveok(stdscr, FALSE);
7098 if (has_colors())
7099 init_colors();
7101 getmaxyx(stdscr, y, x);
7102 status_win = newwin(1, 0, y - 1, 0);
7103 if (!status_win)
7104 die("Failed to create status window");
7106 /* Enable keyboard mapping */
7107 keypad(status_win, TRUE);
7108 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7110 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7111 set_tabsize(opt_tab_size);
7112 #else
7113 TABSIZE = opt_tab_size;
7114 #endif
7116 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7117 if (term && !strcmp(term, "gnome-terminal")) {
7118 /* In the gnome-terminal-emulator, the message from
7119 * scrolling up one line when impossible followed by
7120 * scrolling down one line causes corruption of the
7121 * status line. This is fixed by calling wclear. */
7122 use_scroll_status_wclear = TRUE;
7123 use_scroll_redrawwin = FALSE;
7125 } else if (term && !strcmp(term, "xrvt-xpm")) {
7126 /* No problems with full optimizations in xrvt-(unicode)
7127 * and aterm. */
7128 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7130 } else {
7131 /* When scrolling in (u)xterm the last line in the
7132 * scrolling direction will update slowly. */
7133 use_scroll_redrawwin = TRUE;
7134 use_scroll_status_wclear = FALSE;
7135 }
7136 }
7138 static int
7139 get_input(int prompt_position)
7140 {
7141 struct view *view;
7142 int i, key, cursor_y, cursor_x;
7144 if (prompt_position)
7145 input_mode = TRUE;
7147 while (TRUE) {
7148 bool loading = FALSE;
7150 foreach_view (view, i) {
7151 update_view(view);
7152 if (view_is_displayed(view) && view->has_scrolled &&
7153 use_scroll_redrawwin)
7154 redrawwin(view->win);
7155 view->has_scrolled = FALSE;
7156 if (view->pipe)
7157 loading = TRUE;
7158 }
7160 /* Update the cursor position. */
7161 if (prompt_position) {
7162 getbegyx(status_win, cursor_y, cursor_x);
7163 cursor_x = prompt_position;
7164 } else {
7165 view = display[current_view];
7166 getbegyx(view->win, cursor_y, cursor_x);
7167 cursor_x = view->width - 1;
7168 cursor_y += view->lineno - view->offset;
7169 }
7170 setsyx(cursor_y, cursor_x);
7172 /* Refresh, accept single keystroke of input */
7173 doupdate();
7174 nodelay(status_win, loading);
7175 key = wgetch(status_win);
7177 /* wgetch() with nodelay() enabled returns ERR when
7178 * there's no input. */
7179 if (key == ERR) {
7181 } else if (key == KEY_RESIZE) {
7182 int height, width;
7184 getmaxyx(stdscr, height, width);
7186 wresize(status_win, 1, width);
7187 mvwin(status_win, height - 1, 0);
7188 wnoutrefresh(status_win);
7189 resize_display();
7190 redraw_display(TRUE);
7192 } else {
7193 input_mode = FALSE;
7194 return key;
7195 }
7196 }
7197 }
7199 static char *
7200 prompt_input(const char *prompt, input_handler handler, void *data)
7201 {
7202 enum input_status status = INPUT_OK;
7203 static char buf[SIZEOF_STR];
7204 size_t pos = 0;
7206 buf[pos] = 0;
7208 while (status == INPUT_OK || status == INPUT_SKIP) {
7209 int key;
7211 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7212 wclrtoeol(status_win);
7214 key = get_input(pos + 1);
7215 switch (key) {
7216 case KEY_RETURN:
7217 case KEY_ENTER:
7218 case '\n':
7219 status = pos ? INPUT_STOP : INPUT_CANCEL;
7220 break;
7222 case KEY_BACKSPACE:
7223 if (pos > 0)
7224 buf[--pos] = 0;
7225 else
7226 status = INPUT_CANCEL;
7227 break;
7229 case KEY_ESC:
7230 status = INPUT_CANCEL;
7231 break;
7233 default:
7234 if (pos >= sizeof(buf)) {
7235 report("Input string too long");
7236 return NULL;
7237 }
7239 status = handler(data, buf, key);
7240 if (status == INPUT_OK)
7241 buf[pos++] = (char) key;
7242 }
7243 }
7245 /* Clear the status window */
7246 status_empty = FALSE;
7247 report("");
7249 if (status == INPUT_CANCEL)
7250 return NULL;
7252 buf[pos++] = 0;
7254 return buf;
7255 }
7257 static enum input_status
7258 prompt_yesno_handler(void *data, char *buf, int c)
7259 {
7260 if (c == 'y' || c == 'Y')
7261 return INPUT_STOP;
7262 if (c == 'n' || c == 'N')
7263 return INPUT_CANCEL;
7264 return INPUT_SKIP;
7265 }
7267 static bool
7268 prompt_yesno(const char *prompt)
7269 {
7270 char prompt2[SIZEOF_STR];
7272 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7273 return FALSE;
7275 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7276 }
7278 static enum input_status
7279 read_prompt_handler(void *data, char *buf, int c)
7280 {
7281 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7282 }
7284 static char *
7285 read_prompt(const char *prompt)
7286 {
7287 return prompt_input(prompt, read_prompt_handler, NULL);
7288 }
7290 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7291 {
7292 enum input_status status = INPUT_OK;
7293 int size = 0;
7295 while (items[size].text)
7296 size++;
7298 while (status == INPUT_OK) {
7299 const struct menu_item *item = &items[*selected];
7300 int key;
7301 int i;
7303 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7304 prompt, *selected + 1, size);
7305 if (item->hotkey)
7306 wprintw(status_win, "[%c] ", (char) item->hotkey);
7307 wprintw(status_win, "%s", item->text);
7308 wclrtoeol(status_win);
7310 key = get_input(COLS - 1);
7311 switch (key) {
7312 case KEY_RETURN:
7313 case KEY_ENTER:
7314 case '\n':
7315 status = INPUT_STOP;
7316 break;
7318 case KEY_LEFT:
7319 case KEY_UP:
7320 *selected = *selected - 1;
7321 if (*selected < 0)
7322 *selected = size - 1;
7323 break;
7325 case KEY_RIGHT:
7326 case KEY_DOWN:
7327 *selected = (*selected + 1) % size;
7328 break;
7330 case KEY_ESC:
7331 status = INPUT_CANCEL;
7332 break;
7334 default:
7335 for (i = 0; items[i].text; i++)
7336 if (items[i].hotkey == key) {
7337 *selected = i;
7338 status = INPUT_STOP;
7339 break;
7340 }
7341 }
7342 }
7344 /* Clear the status window */
7345 status_empty = FALSE;
7346 report("");
7348 return status != INPUT_CANCEL;
7349 }
7351 /*
7352 * Repository properties
7353 */
7355 static struct ref **refs = NULL;
7356 static size_t refs_size = 0;
7357 static struct ref *refs_head = NULL;
7359 static struct ref_list **ref_lists = NULL;
7360 static size_t ref_lists_size = 0;
7362 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7363 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7364 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7366 static int
7367 compare_refs(const void *ref1_, const void *ref2_)
7368 {
7369 const struct ref *ref1 = *(const struct ref **)ref1_;
7370 const struct ref *ref2 = *(const struct ref **)ref2_;
7372 if (ref1->tag != ref2->tag)
7373 return ref2->tag - ref1->tag;
7374 if (ref1->ltag != ref2->ltag)
7375 return ref2->ltag - ref2->ltag;
7376 if (ref1->head != ref2->head)
7377 return ref2->head - ref1->head;
7378 if (ref1->tracked != ref2->tracked)
7379 return ref2->tracked - ref1->tracked;
7380 if (ref1->remote != ref2->remote)
7381 return ref2->remote - ref1->remote;
7382 return strcmp(ref1->name, ref2->name);
7383 }
7385 static void
7386 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7387 {
7388 size_t i;
7390 for (i = 0; i < refs_size; i++)
7391 if (!visitor(data, refs[i]))
7392 break;
7393 }
7395 static struct ref *
7396 get_ref_head()
7397 {
7398 return refs_head;
7399 }
7401 static struct ref_list *
7402 get_ref_list(const char *id)
7403 {
7404 struct ref_list *list;
7405 size_t i;
7407 for (i = 0; i < ref_lists_size; i++)
7408 if (!strcmp(id, ref_lists[i]->id))
7409 return ref_lists[i];
7411 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7412 return NULL;
7413 list = calloc(1, sizeof(*list));
7414 if (!list)
7415 return NULL;
7417 for (i = 0; i < refs_size; i++) {
7418 if (!strcmp(id, refs[i]->id) &&
7419 realloc_refs_list(&list->refs, list->size, 1))
7420 list->refs[list->size++] = refs[i];
7421 }
7423 if (!list->refs) {
7424 free(list);
7425 return NULL;
7426 }
7428 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7429 ref_lists[ref_lists_size++] = list;
7430 return list;
7431 }
7433 static int
7434 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7435 {
7436 struct ref *ref = NULL;
7437 bool tag = FALSE;
7438 bool ltag = FALSE;
7439 bool remote = FALSE;
7440 bool tracked = FALSE;
7441 bool head = FALSE;
7442 int from = 0, to = refs_size - 1;
7444 if (!prefixcmp(name, "refs/tags/")) {
7445 if (!suffixcmp(name, namelen, "^{}")) {
7446 namelen -= 3;
7447 name[namelen] = 0;
7448 } else {
7449 ltag = TRUE;
7450 }
7452 tag = TRUE;
7453 namelen -= STRING_SIZE("refs/tags/");
7454 name += STRING_SIZE("refs/tags/");
7456 } else if (!prefixcmp(name, "refs/remotes/")) {
7457 remote = TRUE;
7458 namelen -= STRING_SIZE("refs/remotes/");
7459 name += STRING_SIZE("refs/remotes/");
7460 tracked = !strcmp(opt_remote, name);
7462 } else if (!prefixcmp(name, "refs/heads/")) {
7463 namelen -= STRING_SIZE("refs/heads/");
7464 name += STRING_SIZE("refs/heads/");
7465 if (!strncmp(opt_head, name, namelen))
7466 return OK;
7468 } else if (!strcmp(name, "HEAD")) {
7469 head = TRUE;
7470 if (*opt_head) {
7471 namelen = strlen(opt_head);
7472 name = opt_head;
7473 }
7474 }
7476 /* If we are reloading or it's an annotated tag, replace the
7477 * previous SHA1 with the resolved commit id; relies on the fact
7478 * git-ls-remote lists the commit id of an annotated tag right
7479 * before the commit id it points to. */
7480 while (from <= to) {
7481 size_t pos = (to + from) / 2;
7482 int cmp = strcmp(name, refs[pos]->name);
7484 if (!cmp) {
7485 ref = refs[pos];
7486 break;
7487 }
7489 if (cmp < 0)
7490 to = pos - 1;
7491 else
7492 from = pos + 1;
7493 }
7495 if (!ref) {
7496 if (!realloc_refs(&refs, refs_size, 1))
7497 return ERR;
7498 ref = calloc(1, sizeof(*ref) + namelen);
7499 if (!ref)
7500 return ERR;
7501 memmove(refs + from + 1, refs + from,
7502 (refs_size - from) * sizeof(*refs));
7503 refs[from] = ref;
7504 strncpy(ref->name, name, namelen);
7505 refs_size++;
7506 }
7508 ref->head = head;
7509 ref->tag = tag;
7510 ref->ltag = ltag;
7511 ref->remote = remote;
7512 ref->tracked = tracked;
7513 string_copy_rev(ref->id, id);
7515 if (head)
7516 refs_head = ref;
7517 return OK;
7518 }
7520 static int
7521 load_refs(void)
7522 {
7523 const char *head_argv[] = {
7524 "git", "symbolic-ref", "HEAD", NULL
7525 };
7526 static const char *ls_remote_argv[SIZEOF_ARG] = {
7527 "git", "ls-remote", opt_git_dir, NULL
7528 };
7529 static bool init = FALSE;
7530 size_t i;
7532 if (!init) {
7533 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7534 die("TIG_LS_REMOTE contains too many arguments");
7535 init = TRUE;
7536 }
7538 if (!*opt_git_dir)
7539 return OK;
7541 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7542 !prefixcmp(opt_head, "refs/heads/")) {
7543 char *offset = opt_head + STRING_SIZE("refs/heads/");
7545 memmove(opt_head, offset, strlen(offset) + 1);
7546 }
7548 refs_head = NULL;
7549 for (i = 0; i < refs_size; i++)
7550 refs[i]->id[0] = 0;
7552 if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7553 return ERR;
7555 /* Update the ref lists to reflect changes. */
7556 for (i = 0; i < ref_lists_size; i++) {
7557 struct ref_list *list = ref_lists[i];
7558 size_t old, new;
7560 for (old = new = 0; old < list->size; old++)
7561 if (!strcmp(list->id, list->refs[old]->id))
7562 list->refs[new++] = list->refs[old];
7563 list->size = new;
7564 }
7566 return OK;
7567 }
7569 static void
7570 set_remote_branch(const char *name, const char *value, size_t valuelen)
7571 {
7572 if (!strcmp(name, ".remote")) {
7573 string_ncopy(opt_remote, value, valuelen);
7575 } else if (*opt_remote && !strcmp(name, ".merge")) {
7576 size_t from = strlen(opt_remote);
7578 if (!prefixcmp(value, "refs/heads/"))
7579 value += STRING_SIZE("refs/heads/");
7581 if (!string_format_from(opt_remote, &from, "/%s", value))
7582 opt_remote[0] = 0;
7583 }
7584 }
7586 static void
7587 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7588 {
7589 const char *argv[SIZEOF_ARG] = { name, "=" };
7590 int argc = 1 + (cmd == option_set_command);
7591 enum option_code error;
7593 if (!argv_from_string(argv, &argc, value))
7594 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7595 else
7596 error = cmd(argc, argv);
7598 if (error != OPT_OK)
7599 warn("Option 'tig.%s': %s", name, option_errors[error]);
7600 }
7602 static bool
7603 set_environment_variable(const char *name, const char *value)
7604 {
7605 size_t len = strlen(name) + 1 + strlen(value) + 1;
7606 char *env = malloc(len);
7608 if (env &&
7609 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7610 putenv(env) == 0)
7611 return TRUE;
7612 free(env);
7613 return FALSE;
7614 }
7616 static void
7617 set_work_tree(const char *value)
7618 {
7619 char cwd[SIZEOF_STR];
7621 if (!getcwd(cwd, sizeof(cwd)))
7622 die("Failed to get cwd path: %s", strerror(errno));
7623 if (chdir(opt_git_dir) < 0)
7624 die("Failed to chdir(%s): %s", strerror(errno));
7625 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7626 die("Failed to get git path: %s", strerror(errno));
7627 if (chdir(cwd) < 0)
7628 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7629 if (chdir(value) < 0)
7630 die("Failed to chdir(%s): %s", value, strerror(errno));
7631 if (!getcwd(cwd, sizeof(cwd)))
7632 die("Failed to get cwd path: %s", strerror(errno));
7633 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7634 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7635 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7636 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7637 opt_is_inside_work_tree = TRUE;
7638 }
7640 static int
7641 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7642 {
7643 if (!strcmp(name, "i18n.commitencoding"))
7644 string_ncopy(opt_encoding, value, valuelen);
7646 else if (!strcmp(name, "core.editor"))
7647 string_ncopy(opt_editor, value, valuelen);
7649 else if (!strcmp(name, "core.worktree"))
7650 set_work_tree(value);
7652 else if (!prefixcmp(name, "tig.color."))
7653 set_repo_config_option(name + 10, value, option_color_command);
7655 else if (!prefixcmp(name, "tig.bind."))
7656 set_repo_config_option(name + 9, value, option_bind_command);
7658 else if (!prefixcmp(name, "tig."))
7659 set_repo_config_option(name + 4, value, option_set_command);
7661 else if (*opt_head && !prefixcmp(name, "branch.") &&
7662 !strncmp(name + 7, opt_head, strlen(opt_head)))
7663 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7665 return OK;
7666 }
7668 static int
7669 load_git_config(void)
7670 {
7671 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7673 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7674 }
7676 static int
7677 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7678 {
7679 if (!opt_git_dir[0]) {
7680 string_ncopy(opt_git_dir, name, namelen);
7682 } else if (opt_is_inside_work_tree == -1) {
7683 /* This can be 3 different values depending on the
7684 * version of git being used. If git-rev-parse does not
7685 * understand --is-inside-work-tree it will simply echo
7686 * the option else either "true" or "false" is printed.
7687 * Default to true for the unknown case. */
7688 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7690 } else if (*name == '.') {
7691 string_ncopy(opt_cdup, name, namelen);
7693 } else {
7694 string_ncopy(opt_prefix, name, namelen);
7695 }
7697 return OK;
7698 }
7700 static int
7701 load_repo_info(void)
7702 {
7703 const char *rev_parse_argv[] = {
7704 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7705 "--show-cdup", "--show-prefix", NULL
7706 };
7708 return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7709 }
7712 /*
7713 * Main
7714 */
7716 static const char usage[] =
7717 "tig " TIG_VERSION " (" __DATE__ ")\n"
7718 "\n"
7719 "Usage: tig [options] [revs] [--] [paths]\n"
7720 " or: tig show [options] [revs] [--] [paths]\n"
7721 " or: tig blame [rev] path\n"
7722 " or: tig status\n"
7723 " or: tig < [git command output]\n"
7724 "\n"
7725 "Options:\n"
7726 " -v, --version Show version and exit\n"
7727 " -h, --help Show help message and exit";
7729 static void __NORETURN
7730 quit(int sig)
7731 {
7732 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7733 if (cursed)
7734 endwin();
7735 exit(0);
7736 }
7738 static void __NORETURN
7739 die(const char *err, ...)
7740 {
7741 va_list args;
7743 endwin();
7745 va_start(args, err);
7746 fputs("tig: ", stderr);
7747 vfprintf(stderr, err, args);
7748 fputs("\n", stderr);
7749 va_end(args);
7751 exit(1);
7752 }
7754 static void
7755 warn(const char *msg, ...)
7756 {
7757 va_list args;
7759 va_start(args, msg);
7760 fputs("tig warning: ", stderr);
7761 vfprintf(stderr, msg, args);
7762 fputs("\n", stderr);
7763 va_end(args);
7764 }
7766 static int
7767 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7768 {
7769 const char ***filter_args = data;
7771 return argv_append(filter_args, name) ? OK : ERR;
7772 }
7774 static void
7775 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7776 {
7777 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7778 const char **all_argv = NULL;
7780 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7781 !argv_append_array(&all_argv, argv) ||
7782 !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7783 die("Failed to split arguments");
7784 argv_free(all_argv);
7785 free(all_argv);
7786 }
7788 static void
7789 filter_options(const char *argv[])
7790 {
7791 filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7792 filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7793 filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7794 }
7796 static enum request
7797 parse_options(int argc, const char *argv[])
7798 {
7799 enum request request = REQ_VIEW_MAIN;
7800 const char *subcommand;
7801 bool seen_dashdash = FALSE;
7802 const char **filter_argv = NULL;
7803 int i;
7805 if (!isatty(STDIN_FILENO))
7806 return REQ_VIEW_PAGER;
7808 if (argc <= 1)
7809 return REQ_VIEW_MAIN;
7811 subcommand = argv[1];
7812 if (!strcmp(subcommand, "status")) {
7813 if (argc > 2)
7814 warn("ignoring arguments after `%s'", subcommand);
7815 return REQ_VIEW_STATUS;
7817 } else if (!strcmp(subcommand, "blame")) {
7818 if (argc <= 2 || argc > 4)
7819 die("invalid number of options to blame\n\n%s", usage);
7821 i = 2;
7822 if (argc == 4) {
7823 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7824 i++;
7825 }
7827 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7828 return REQ_VIEW_BLAME;
7830 } else if (!strcmp(subcommand, "show")) {
7831 request = REQ_VIEW_DIFF;
7833 } else {
7834 subcommand = NULL;
7835 }
7837 for (i = 1 + !!subcommand; i < argc; i++) {
7838 const char *opt = argv[i];
7840 if (seen_dashdash) {
7841 argv_append(&opt_file_argv, opt);
7842 continue;
7844 } else if (!strcmp(opt, "--")) {
7845 seen_dashdash = TRUE;
7846 continue;
7848 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7849 printf("tig version %s\n", TIG_VERSION);
7850 quit(0);
7852 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7853 printf("%s\n", usage);
7854 quit(0);
7856 } else if (!strcmp(opt, "--all")) {
7857 argv_append(&opt_rev_argv, opt);
7858 continue;
7859 }
7861 if (!argv_append(&filter_argv, opt))
7862 die("command too long");
7863 }
7865 if (filter_argv)
7866 filter_options(filter_argv);
7868 return request;
7869 }
7871 int
7872 main(int argc, const char *argv[])
7873 {
7874 const char *codeset = "UTF-8";
7875 enum request request = parse_options(argc, argv);
7876 struct view *view;
7877 size_t i;
7879 signal(SIGINT, quit);
7880 signal(SIGPIPE, SIG_IGN);
7882 if (setlocale(LC_ALL, "")) {
7883 codeset = nl_langinfo(CODESET);
7884 }
7886 if (load_repo_info() == ERR)
7887 die("Failed to load repo info.");
7889 if (load_options() == ERR)
7890 die("Failed to load user config.");
7892 if (load_git_config() == ERR)
7893 die("Failed to load repo config.");
7895 /* Require a git repository unless when running in pager mode. */
7896 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7897 die("Not a git repository");
7899 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7900 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7901 if (opt_iconv_in == ICONV_NONE)
7902 die("Failed to initialize character set conversion");
7903 }
7905 if (codeset && strcmp(codeset, "UTF-8")) {
7906 opt_iconv_out = iconv_open(codeset, "UTF-8");
7907 if (opt_iconv_out == ICONV_NONE)
7908 die("Failed to initialize character set conversion");
7909 }
7911 if (load_refs() == ERR)
7912 die("Failed to load refs.");
7914 foreach_view (view, i) {
7915 if (getenv(view->cmd_env))
7916 warn("Use of the %s environment variable is deprecated,"
7917 " use options or TIG_DIFF_ARGS instead",
7918 view->cmd_env);
7919 if (!argv_from_env(view->ops->argv, view->cmd_env))
7920 die("Too many arguments in the `%s` environment variable",
7921 view->cmd_env);
7922 }
7924 init_display();
7926 while (view_driver(display[current_view], request)) {
7927 int key = get_input(0);
7929 view = display[current_view];
7930 request = get_keybinding(view->keymap, key);
7932 /* Some low-level request handling. This keeps access to
7933 * status_win restricted. */
7934 switch (request) {
7935 case REQ_NONE:
7936 report("Unknown key, press %s for help",
7937 get_key(view->keymap, REQ_VIEW_HELP));
7938 break;
7939 case REQ_PROMPT:
7940 {
7941 char *cmd = read_prompt(":");
7943 if (cmd && isdigit(*cmd)) {
7944 int lineno = view->lineno + 1;
7946 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7947 select_view_line(view, lineno - 1);
7948 report("");
7949 } else {
7950 report("Unable to parse '%s' as a line number", cmd);
7951 }
7953 } else if (cmd) {
7954 struct view *next = VIEW(REQ_VIEW_PAGER);
7955 const char *argv[SIZEOF_ARG] = { "git" };
7956 int argc = 1;
7958 /* When running random commands, initially show the
7959 * command in the title. However, it maybe later be
7960 * overwritten if a commit line is selected. */
7961 string_ncopy(next->ref, cmd, strlen(cmd));
7963 if (!argv_from_string(argv, &argc, cmd)) {
7964 report("Too many arguments");
7965 } else if (!prepare_update(next, argv, NULL)) {
7966 report("Failed to format command");
7967 } else {
7968 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7969 }
7970 }
7972 request = REQ_NONE;
7973 break;
7974 }
7975 case REQ_SEARCH:
7976 case REQ_SEARCH_BACK:
7977 {
7978 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7979 char *search = read_prompt(prompt);
7981 if (search)
7982 string_ncopy(opt_search, search, strlen(search));
7983 else if (*opt_search)
7984 request = request == REQ_SEARCH ?
7985 REQ_FIND_NEXT :
7986 REQ_FIND_PREV;
7987 else
7988 request = REQ_NONE;
7989 break;
7990 }
7991 default:
7992 break;
7993 }
7994 }
7996 quit(0);
7998 return 0;
7999 }