8933e3092a47bc62860430f2fa920dce978eb4a5
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 info = &line_info[index];
1922 }
1924 if (!set_color(&info->fg, argv[1]) ||
1925 !set_color(&info->bg, argv[2]))
1926 return OPT_ERR_UNKNOWN_COLOR;
1928 info->attr = 0;
1929 while (argc-- > 3) {
1930 int attr;
1932 if (!set_attribute(&attr, argv[argc]))
1933 return OPT_ERR_UNKNOWN_ATTRIBUTE;
1934 info->attr |= attr;
1935 }
1937 return OPT_OK;
1938 }
1940 static enum option_code
1941 parse_bool(bool *opt, const char *arg)
1942 {
1943 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1944 ? TRUE : FALSE;
1945 return OPT_OK;
1946 }
1948 static enum option_code
1949 parse_enum_do(unsigned int *opt, const char *arg,
1950 const struct enum_map *map, size_t map_size)
1951 {
1952 bool is_true;
1954 assert(map_size > 1);
1956 if (map_enum_do(map, map_size, (int *) opt, arg))
1957 return OPT_OK;
1959 parse_bool(&is_true, arg);
1960 *opt = is_true ? map[1].value : map[0].value;
1961 return OPT_OK;
1962 }
1964 #define parse_enum(opt, arg, map) \
1965 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1967 static enum option_code
1968 parse_string(char *opt, const char *arg, size_t optsize)
1969 {
1970 int arglen = strlen(arg);
1972 switch (arg[0]) {
1973 case '\"':
1974 case '\'':
1975 if (arglen == 1 || arg[arglen - 1] != arg[0])
1976 return OPT_ERR_UNMATCHED_QUOTATION;
1977 arg += 1; arglen -= 2;
1978 default:
1979 string_ncopy_do(opt, optsize, arg, arglen);
1980 return OPT_OK;
1981 }
1982 }
1984 /* Wants: name = value */
1985 static enum option_code
1986 option_set_command(int argc, const char *argv[])
1987 {
1988 if (argc != 3)
1989 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1991 if (strcmp(argv[1], "="))
1992 return OPT_ERR_NO_VALUE_ASSIGNED;
1994 if (!strcmp(argv[0], "show-author"))
1995 return parse_enum(&opt_author, argv[2], author_map);
1997 if (!strcmp(argv[0], "show-date"))
1998 return parse_enum(&opt_date, argv[2], date_map);
2000 if (!strcmp(argv[0], "show-rev-graph"))
2001 return parse_bool(&opt_rev_graph, argv[2]);
2003 if (!strcmp(argv[0], "show-refs"))
2004 return parse_bool(&opt_show_refs, argv[2]);
2006 if (!strcmp(argv[0], "show-line-numbers"))
2007 return parse_bool(&opt_line_number, argv[2]);
2009 if (!strcmp(argv[0], "line-graphics"))
2010 return parse_bool(&opt_line_graphics, argv[2]);
2012 if (!strcmp(argv[0], "line-number-interval"))
2013 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2015 if (!strcmp(argv[0], "author-width"))
2016 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2018 if (!strcmp(argv[0], "horizontal-scroll"))
2019 return parse_step(&opt_hscroll, argv[2]);
2021 if (!strcmp(argv[0], "split-view-height"))
2022 return parse_step(&opt_scale_split_view, argv[2]);
2024 if (!strcmp(argv[0], "tab-size"))
2025 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2027 if (!strcmp(argv[0], "commit-encoding"))
2028 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2030 if (!strcmp(argv[0], "status-untracked-dirs"))
2031 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2033 return OPT_ERR_UNKNOWN_VARIABLE_NAME;
2034 }
2036 /* Wants: mode request key */
2037 static enum option_code
2038 option_bind_command(int argc, const char *argv[])
2039 {
2040 enum request request;
2041 int keymap = -1;
2042 int key;
2044 if (argc < 3)
2045 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
2047 if (!set_keymap(&keymap, argv[0]))
2048 return OPT_ERR_UNKNOWN_KEY_MAP;
2050 key = get_key_value(argv[1]);
2051 if (key == ERR)
2052 return OPT_ERR_UNKNOWN_KEY;
2054 request = get_request(argv[2]);
2055 if (request == REQ_UNKNOWN) {
2056 static const struct enum_map obsolete[] = {
2057 ENUM_MAP("cherry-pick", REQ_NONE),
2058 ENUM_MAP("screen-resize", REQ_NONE),
2059 ENUM_MAP("tree-parent", REQ_PARENT),
2060 };
2061 int alias;
2063 if (map_enum(&alias, obsolete, argv[2])) {
2064 if (alias != REQ_NONE)
2065 add_keybinding(keymap, alias, key);
2066 return OPT_ERR_OBSOLETE_REQUEST_NAME;
2067 }
2068 }
2069 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2070 request = add_run_request(keymap, key, argv + 2);
2071 if (request == REQ_UNKNOWN)
2072 return OPT_ERR_UNKNOWN_REQUEST_NAME;
2074 add_keybinding(keymap, request, key);
2076 return OPT_OK;
2077 }
2079 static enum option_code
2080 set_option(const char *opt, char *value)
2081 {
2082 const char *argv[SIZEOF_ARG];
2083 int argc = 0;
2085 if (!argv_from_string(argv, &argc, value))
2086 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
2088 if (!strcmp(opt, "color"))
2089 return option_color_command(argc, argv);
2091 if (!strcmp(opt, "set"))
2092 return option_set_command(argc, argv);
2094 if (!strcmp(opt, "bind"))
2095 return option_bind_command(argc, argv);
2097 return OPT_ERR_UNKNOWN_OPTION_COMMAND;
2098 }
2100 struct config_state {
2101 int lineno;
2102 bool errors;
2103 };
2105 static int
2106 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2107 {
2108 struct config_state *config = data;
2109 enum option_code status = OPT_ERR_NO_OPTION_VALUE;
2111 config->lineno++;
2113 /* Check for comment markers, since read_properties() will
2114 * only ensure opt and value are split at first " \t". */
2115 optlen = strcspn(opt, "#");
2116 if (optlen == 0)
2117 return OK;
2119 if (opt[optlen] == 0) {
2120 /* Look for comment endings in the value. */
2121 size_t len = strcspn(value, "#");
2123 if (len < valuelen) {
2124 valuelen = len;
2125 value[valuelen] = 0;
2126 }
2128 status = set_option(opt, value);
2129 }
2131 if (status != OPT_OK) {
2132 warn("Error on line %d, near '%.*s': %s",
2133 config->lineno, (int) optlen, opt, option_errors[status]);
2134 config->errors = TRUE;
2135 }
2137 /* Always keep going if errors are encountered. */
2138 return OK;
2139 }
2141 static void
2142 load_option_file(const char *path)
2143 {
2144 struct config_state config = { 0, FALSE };
2145 struct io io;
2147 /* It's OK that the file doesn't exist. */
2148 if (!io_open(&io, "%s", path))
2149 return;
2151 if (io_load(&io, " \t", read_option, &config) == ERR ||
2152 config.errors == TRUE)
2153 warn("Errors while loading %s.", path);
2154 }
2156 static int
2157 load_options(void)
2158 {
2159 const char *home = getenv("HOME");
2160 const char *tigrc_user = getenv("TIGRC_USER");
2161 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2162 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2163 char buf[SIZEOF_STR];
2165 if (!tigrc_system)
2166 tigrc_system = SYSCONFDIR "/tigrc";
2167 load_option_file(tigrc_system);
2169 if (!tigrc_user) {
2170 if (!home || !string_format(buf, "%s/.tigrc", home))
2171 return ERR;
2172 tigrc_user = buf;
2173 }
2174 load_option_file(tigrc_user);
2176 /* Add _after_ loading config files to avoid adding run requests
2177 * that conflict with keybindings. */
2178 add_builtin_run_requests();
2180 if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2181 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2182 int argc = 0;
2184 if (!string_format(buf, "%s", tig_diff_opts) ||
2185 !argv_from_string(diff_opts, &argc, buf))
2186 die("TIG_DIFF_OPTS contains too many arguments");
2187 else if (!argv_copy(&opt_diff_argv, diff_opts))
2188 die("Failed to format TIG_DIFF_OPTS arguments");
2189 }
2191 return OK;
2192 }
2195 /*
2196 * The viewer
2197 */
2199 struct view;
2200 struct view_ops;
2202 /* The display array of active views and the index of the current view. */
2203 static struct view *display[2];
2204 static WINDOW *display_win[2];
2205 static WINDOW *display_title[2];
2206 static unsigned int current_view;
2208 #define foreach_displayed_view(view, i) \
2209 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2211 #define displayed_views() (display[1] != NULL ? 2 : 1)
2213 /* Current head and commit ID */
2214 static char ref_blob[SIZEOF_REF] = "";
2215 static char ref_commit[SIZEOF_REF] = "HEAD";
2216 static char ref_head[SIZEOF_REF] = "HEAD";
2217 static char ref_branch[SIZEOF_REF] = "";
2219 enum view_type {
2220 VIEW_MAIN,
2221 VIEW_DIFF,
2222 VIEW_LOG,
2223 VIEW_TREE,
2224 VIEW_BLOB,
2225 VIEW_BLAME,
2226 VIEW_BRANCH,
2227 VIEW_HELP,
2228 VIEW_PAGER,
2229 VIEW_STATUS,
2230 VIEW_STAGE,
2231 };
2233 struct view {
2234 enum view_type type; /* View type */
2235 const char *name; /* View name */
2236 const char *cmd_env; /* Command line set via environment */
2237 const char *id; /* Points to either of ref_{head,commit,blob} */
2239 struct view_ops *ops; /* View operations */
2241 enum keymap keymap; /* What keymap does this view have */
2242 bool git_dir; /* Whether the view requires a git directory. */
2244 char ref[SIZEOF_REF]; /* Hovered commit reference */
2245 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2247 int height, width; /* The width and height of the main window */
2248 WINDOW *win; /* The main window */
2250 /* Navigation */
2251 unsigned long offset; /* Offset of the window top */
2252 unsigned long yoffset; /* Offset from the window side. */
2253 unsigned long lineno; /* Current line number */
2254 unsigned long p_offset; /* Previous offset of the window top */
2255 unsigned long p_yoffset;/* Previous offset from the window side */
2256 unsigned long p_lineno; /* Previous current line number */
2257 bool p_restore; /* Should the previous position be restored. */
2259 /* Searching */
2260 char grep[SIZEOF_STR]; /* Search string */
2261 regex_t *regex; /* Pre-compiled regexp */
2263 /* If non-NULL, points to the view that opened this view. If this view
2264 * is closed tig will switch back to the parent view. */
2265 struct view *parent;
2266 struct view *prev;
2268 /* Buffering */
2269 size_t lines; /* Total number of lines */
2270 struct line *line; /* Line index */
2271 unsigned int digits; /* Number of digits in the lines member. */
2273 /* Drawing */
2274 struct line *curline; /* Line currently being drawn. */
2275 enum line_type curtype; /* Attribute currently used for drawing. */
2276 unsigned long col; /* Column when drawing. */
2277 bool has_scrolled; /* View was scrolled. */
2279 /* Loading */
2280 const char **argv; /* Shell command arguments. */
2281 const char *dir; /* Directory from which to execute. */
2282 struct io io;
2283 struct io *pipe;
2284 time_t start_time;
2285 time_t update_secs;
2286 };
2288 struct view_ops {
2289 /* What type of content being displayed. Used in the title bar. */
2290 const char *type;
2291 /* Default command arguments. */
2292 const char **argv;
2293 /* Open and reads in all view content. */
2294 bool (*open)(struct view *view);
2295 /* Read one line; updates view->line. */
2296 bool (*read)(struct view *view, char *data);
2297 /* Draw one line; @lineno must be < view->height. */
2298 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2299 /* Depending on view handle a special requests. */
2300 enum request (*request)(struct view *view, enum request request, struct line *line);
2301 /* Search for regexp in a line. */
2302 bool (*grep)(struct view *view, struct line *line);
2303 /* Select line */
2304 void (*select)(struct view *view, struct line *line);
2305 /* Prepare view for loading */
2306 bool (*prepare)(struct view *view);
2307 };
2309 static struct view_ops blame_ops;
2310 static struct view_ops blob_ops;
2311 static struct view_ops diff_ops;
2312 static struct view_ops help_ops;
2313 static struct view_ops log_ops;
2314 static struct view_ops main_ops;
2315 static struct view_ops pager_ops;
2316 static struct view_ops stage_ops;
2317 static struct view_ops status_ops;
2318 static struct view_ops tree_ops;
2319 static struct view_ops branch_ops;
2321 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2322 { type, name, #env, ref, ops, map, git }
2324 #define VIEW_(id, name, ops, git, ref) \
2325 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2327 static struct view views[] = {
2328 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2329 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2330 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2331 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2332 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2333 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2334 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2335 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2336 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2337 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2338 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2339 };
2341 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2343 #define foreach_view(view, i) \
2344 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2346 #define view_is_displayed(view) \
2347 (view == display[0] || view == display[1])
2349 static enum request
2350 view_request(struct view *view, enum request request)
2351 {
2352 if (!view || !view->lines)
2353 return request;
2354 return view->ops->request(view, request, &view->line[view->lineno]);
2355 }
2358 /*
2359 * View drawing.
2360 */
2362 static inline void
2363 set_view_attr(struct view *view, enum line_type type)
2364 {
2365 if (!view->curline->selected && view->curtype != type) {
2366 (void) wattrset(view->win, get_line_attr(type));
2367 wchgat(view->win, -1, 0, type, NULL);
2368 view->curtype = type;
2369 }
2370 }
2372 static int
2373 draw_chars(struct view *view, enum line_type type, const char *string,
2374 int max_len, bool use_tilde)
2375 {
2376 static char out_buffer[BUFSIZ * 2];
2377 int len = 0;
2378 int col = 0;
2379 int trimmed = FALSE;
2380 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2382 if (max_len <= 0)
2383 return 0;
2385 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2387 set_view_attr(view, type);
2388 if (len > 0) {
2389 if (opt_iconv_out != ICONV_NONE) {
2390 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2391 size_t inlen = len + 1;
2393 char *outbuf = out_buffer;
2394 size_t outlen = sizeof(out_buffer);
2396 size_t ret;
2398 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2399 if (ret != (size_t) -1) {
2400 string = out_buffer;
2401 len = sizeof(out_buffer) - outlen;
2402 }
2403 }
2405 waddnstr(view->win, string, len);
2407 if (trimmed && use_tilde) {
2408 set_view_attr(view, LINE_DELIMITER);
2409 waddch(view->win, '~');
2410 col++;
2411 }
2412 }
2414 return col;
2415 }
2417 static int
2418 draw_space(struct view *view, enum line_type type, int max, int spaces)
2419 {
2420 static char space[] = " ";
2421 int col = 0;
2423 spaces = MIN(max, spaces);
2425 while (spaces > 0) {
2426 int len = MIN(spaces, sizeof(space) - 1);
2428 col += draw_chars(view, type, space, len, FALSE);
2429 spaces -= len;
2430 }
2432 return col;
2433 }
2435 static bool
2436 draw_text(struct view *view, enum line_type type, const char *string)
2437 {
2438 char text[SIZEOF_STR];
2440 do {
2441 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2443 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, TRUE);
2444 string += pos;
2445 } while (*string && view->width + view->yoffset > view->col);
2447 return view->width + view->yoffset <= view->col;
2448 }
2450 static bool
2451 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2452 {
2453 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2454 int max = view->width + view->yoffset - view->col;
2455 int i;
2457 if (max < size)
2458 size = max;
2460 set_view_attr(view, type);
2461 /* Using waddch() instead of waddnstr() ensures that
2462 * they'll be rendered correctly for the cursor line. */
2463 for (i = skip; i < size; i++)
2464 waddch(view->win, graphic[i]);
2466 view->col += size;
2467 if (size < max && skip <= size)
2468 waddch(view->win, ' ');
2469 view->col++;
2471 return view->width + view->yoffset <= view->col;
2472 }
2474 static bool
2475 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2476 {
2477 int max = MIN(view->width + view->yoffset - view->col, len);
2478 int col;
2480 if (text)
2481 col = draw_chars(view, type, text, max - 1, trim);
2482 else
2483 col = draw_space(view, type, max - 1, max - 1);
2485 view->col += col;
2486 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2487 return view->width + view->yoffset <= view->col;
2488 }
2490 static bool
2491 draw_date(struct view *view, struct time *time)
2492 {
2493 const char *date = mkdate(time, opt_date);
2494 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2496 return draw_field(view, LINE_DATE, date, cols, FALSE);
2497 }
2499 static bool
2500 draw_author(struct view *view, const char *author)
2501 {
2502 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2503 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2505 if (abbreviate && author)
2506 author = get_author_initials(author);
2508 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2509 }
2511 static bool
2512 draw_mode(struct view *view, mode_t mode)
2513 {
2514 const char *str;
2516 if (S_ISDIR(mode))
2517 str = "drwxr-xr-x";
2518 else if (S_ISLNK(mode))
2519 str = "lrwxrwxrwx";
2520 else if (S_ISGITLINK(mode))
2521 str = "m---------";
2522 else if (S_ISREG(mode) && mode & S_IXUSR)
2523 str = "-rwxr-xr-x";
2524 else if (S_ISREG(mode))
2525 str = "-rw-r--r--";
2526 else
2527 str = "----------";
2529 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2530 }
2532 static bool
2533 draw_lineno(struct view *view, unsigned int lineno)
2534 {
2535 char number[10];
2536 int digits3 = view->digits < 3 ? 3 : view->digits;
2537 int max = MIN(view->width + view->yoffset - view->col, digits3);
2538 char *text = NULL;
2539 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2541 lineno += view->offset + 1;
2542 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2543 static char fmt[] = "%1ld";
2545 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2546 if (string_format(number, fmt, lineno))
2547 text = number;
2548 }
2549 if (text)
2550 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2551 else
2552 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2553 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2554 }
2556 static bool
2557 draw_view_line(struct view *view, unsigned int lineno)
2558 {
2559 struct line *line;
2560 bool selected = (view->offset + lineno == view->lineno);
2562 assert(view_is_displayed(view));
2564 if (view->offset + lineno >= view->lines)
2565 return FALSE;
2567 line = &view->line[view->offset + lineno];
2569 wmove(view->win, lineno, 0);
2570 if (line->cleareol)
2571 wclrtoeol(view->win);
2572 view->col = 0;
2573 view->curline = line;
2574 view->curtype = LINE_NONE;
2575 line->selected = FALSE;
2576 line->dirty = line->cleareol = 0;
2578 if (selected) {
2579 set_view_attr(view, LINE_CURSOR);
2580 line->selected = TRUE;
2581 view->ops->select(view, line);
2582 }
2584 return view->ops->draw(view, line, lineno);
2585 }
2587 static void
2588 redraw_view_dirty(struct view *view)
2589 {
2590 bool dirty = FALSE;
2591 int lineno;
2593 for (lineno = 0; lineno < view->height; lineno++) {
2594 if (view->offset + lineno >= view->lines)
2595 break;
2596 if (!view->line[view->offset + lineno].dirty)
2597 continue;
2598 dirty = TRUE;
2599 if (!draw_view_line(view, lineno))
2600 break;
2601 }
2603 if (!dirty)
2604 return;
2605 wnoutrefresh(view->win);
2606 }
2608 static void
2609 redraw_view_from(struct view *view, int lineno)
2610 {
2611 assert(0 <= lineno && lineno < view->height);
2613 for (; lineno < view->height; lineno++) {
2614 if (!draw_view_line(view, lineno))
2615 break;
2616 }
2618 wnoutrefresh(view->win);
2619 }
2621 static void
2622 redraw_view(struct view *view)
2623 {
2624 werase(view->win);
2625 redraw_view_from(view, 0);
2626 }
2629 static void
2630 update_view_title(struct view *view)
2631 {
2632 char buf[SIZEOF_STR];
2633 char state[SIZEOF_STR];
2634 size_t bufpos = 0, statelen = 0;
2635 WINDOW *window = display[0] == view ? display_title[0] : display_title[1];
2637 assert(view_is_displayed(view));
2639 if (view->type != VIEW_STATUS && view->lines) {
2640 unsigned int view_lines = view->offset + view->height;
2641 unsigned int lines = view->lines
2642 ? MIN(view_lines, view->lines) * 100 / view->lines
2643 : 0;
2645 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2646 view->ops->type,
2647 view->lineno + 1,
2648 view->lines,
2649 lines);
2651 }
2653 if (view->pipe) {
2654 time_t secs = time(NULL) - view->start_time;
2656 /* Three git seconds are a long time ... */
2657 if (secs > 2)
2658 string_format_from(state, &statelen, " loading %lds", secs);
2659 }
2661 string_format_from(buf, &bufpos, "[%s]", view->name);
2662 if (*view->ref && bufpos < view->width) {
2663 size_t refsize = strlen(view->ref);
2664 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2666 if (minsize < view->width)
2667 refsize = view->width - minsize + 7;
2668 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2669 }
2671 if (statelen && bufpos < view->width) {
2672 string_format_from(buf, &bufpos, "%s", state);
2673 }
2675 if (view == display[current_view])
2676 wbkgdset(window, get_line_attr(LINE_TITLE_FOCUS));
2677 else
2678 wbkgdset(window, get_line_attr(LINE_TITLE_BLUR));
2680 mvwaddnstr(window, 0, 0, buf, bufpos);
2681 wclrtoeol(window);
2682 wnoutrefresh(window);
2683 }
2685 static int
2686 apply_step(double step, int value)
2687 {
2688 if (step >= 1)
2689 return (int) step;
2690 value *= step + 0.01;
2691 return value ? value : 1;
2692 }
2694 static void
2695 resize_display(void)
2696 {
2697 int offset, i;
2698 struct view *base = display[0];
2699 struct view *view = display[1] ? display[1] : display[0];
2701 /* Setup window dimensions */
2703 getmaxyx(stdscr, base->height, base->width);
2705 /* Make room for the status window. */
2706 base->height -= 1;
2708 if (view != base) {
2709 /* Horizontal split. */
2710 view->width = base->width;
2711 view->height = apply_step(opt_scale_split_view, base->height);
2712 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2713 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2714 base->height -= view->height;
2716 /* Make room for the title bar. */
2717 view->height -= 1;
2718 }
2720 /* Make room for the title bar. */
2721 base->height -= 1;
2723 offset = 0;
2725 foreach_displayed_view (view, i) {
2726 if (!display_win[i]) {
2727 display_win[i] = newwin(view->height, 0, offset, 0);
2728 if (!display_win[i])
2729 die("Failed to create %s view", view->name);
2731 scrollok(display_win[i], FALSE);
2733 display_title[i] = newwin(1, 0, offset + view->height, 0);
2734 if (!display_title[i])
2735 die("Failed to create title window");
2737 } else {
2738 wresize(display_win[i], view->height, view->width);
2739 mvwin(display_win[i], offset, 0);
2740 mvwin(display_title[i], offset + view->height, 0);
2741 }
2743 view->win = display_win[i];
2745 offset += view->height + 1;
2746 }
2747 }
2749 static void
2750 redraw_display(bool clear)
2751 {
2752 struct view *view;
2753 int i;
2755 foreach_displayed_view (view, i) {
2756 if (clear)
2757 wclear(view->win);
2758 redraw_view(view);
2759 update_view_title(view);
2760 }
2761 }
2764 /*
2765 * Option management
2766 */
2768 #define TOGGLE_MENU \
2769 TOGGLE_(LINENO, '.', "line numbers", &opt_line_number, NULL) \
2770 TOGGLE_(DATE, 'D', "dates", &opt_date, date_map) \
2771 TOGGLE_(AUTHOR, 'A', "author names", &opt_author, author_map) \
2772 TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \
2773 TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL)
2775 static void
2776 toggle_option(enum request request)
2777 {
2778 const struct {
2779 enum request request;
2780 const struct enum_map *map;
2781 size_t map_size;
2782 } data[] = {
2783 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
2784 TOGGLE_MENU
2785 #undef TOGGLE_
2786 };
2787 const struct menu_item menu[] = {
2788 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2789 TOGGLE_MENU
2790 #undef TOGGLE_
2791 { 0 }
2792 };
2793 int i = 0;
2795 if (request == REQ_OPTIONS) {
2796 if (!prompt_menu("Toggle option", menu, &i))
2797 return;
2798 } else {
2799 while (i < ARRAY_SIZE(data) && data[i].request != request)
2800 i++;
2801 if (i >= ARRAY_SIZE(data))
2802 die("Invalid request (%d)", request);
2803 }
2805 if (data[i].map != NULL) {
2806 unsigned int *opt = menu[i].data;
2808 *opt = (*opt + 1) % data[i].map_size;
2809 redraw_display(FALSE);
2810 report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2812 } else {
2813 bool *option = menu[i].data;
2815 *option = !*option;
2816 redraw_display(FALSE);
2817 report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
2818 }
2819 }
2821 static void
2822 maximize_view(struct view *view)
2823 {
2824 memset(display, 0, sizeof(display));
2825 current_view = 0;
2826 display[current_view] = view;
2827 resize_display();
2828 redraw_display(FALSE);
2829 report("");
2830 }
2833 /*
2834 * Navigation
2835 */
2837 static bool
2838 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2839 {
2840 if (lineno >= view->lines)
2841 lineno = view->lines > 0 ? view->lines - 1 : 0;
2843 if (offset > lineno || offset + view->height <= lineno) {
2844 unsigned long half = view->height / 2;
2846 if (lineno > half)
2847 offset = lineno - half;
2848 else
2849 offset = 0;
2850 }
2852 if (offset != view->offset || lineno != view->lineno) {
2853 view->offset = offset;
2854 view->lineno = lineno;
2855 return TRUE;
2856 }
2858 return FALSE;
2859 }
2861 /* Scrolling backend */
2862 static void
2863 do_scroll_view(struct view *view, int lines)
2864 {
2865 bool redraw_current_line = FALSE;
2867 /* The rendering expects the new offset. */
2868 view->offset += lines;
2870 assert(0 <= view->offset && view->offset < view->lines);
2871 assert(lines);
2873 /* Move current line into the view. */
2874 if (view->lineno < view->offset) {
2875 view->lineno = view->offset;
2876 redraw_current_line = TRUE;
2877 } else if (view->lineno >= view->offset + view->height) {
2878 view->lineno = view->offset + view->height - 1;
2879 redraw_current_line = TRUE;
2880 }
2882 assert(view->offset <= view->lineno && view->lineno < view->lines);
2884 /* Redraw the whole screen if scrolling is pointless. */
2885 if (view->height < ABS(lines)) {
2886 redraw_view(view);
2888 } else {
2889 int line = lines > 0 ? view->height - lines : 0;
2890 int end = line + ABS(lines);
2892 scrollok(view->win, TRUE);
2893 wscrl(view->win, lines);
2894 scrollok(view->win, FALSE);
2896 while (line < end && draw_view_line(view, line))
2897 line++;
2899 if (redraw_current_line)
2900 draw_view_line(view, view->lineno - view->offset);
2901 wnoutrefresh(view->win);
2902 }
2904 view->has_scrolled = TRUE;
2905 report("");
2906 }
2908 /* Scroll frontend */
2909 static void
2910 scroll_view(struct view *view, enum request request)
2911 {
2912 int lines = 1;
2914 assert(view_is_displayed(view));
2916 switch (request) {
2917 case REQ_SCROLL_FIRST_COL:
2918 view->yoffset = 0;
2919 redraw_view_from(view, 0);
2920 report("");
2921 return;
2922 case REQ_SCROLL_LEFT:
2923 if (view->yoffset == 0) {
2924 report("Cannot scroll beyond the first column");
2925 return;
2926 }
2927 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2928 view->yoffset = 0;
2929 else
2930 view->yoffset -= apply_step(opt_hscroll, view->width);
2931 redraw_view_from(view, 0);
2932 report("");
2933 return;
2934 case REQ_SCROLL_RIGHT:
2935 view->yoffset += apply_step(opt_hscroll, view->width);
2936 redraw_view(view);
2937 report("");
2938 return;
2939 case REQ_SCROLL_PAGE_DOWN:
2940 lines = view->height;
2941 case REQ_SCROLL_LINE_DOWN:
2942 if (view->offset + lines > view->lines)
2943 lines = view->lines - view->offset;
2945 if (lines == 0 || view->offset + view->height >= view->lines) {
2946 report("Cannot scroll beyond the last line");
2947 return;
2948 }
2949 break;
2951 case REQ_SCROLL_PAGE_UP:
2952 lines = view->height;
2953 case REQ_SCROLL_LINE_UP:
2954 if (lines > view->offset)
2955 lines = view->offset;
2957 if (lines == 0) {
2958 report("Cannot scroll beyond the first line");
2959 return;
2960 }
2962 lines = -lines;
2963 break;
2965 default:
2966 die("request %d not handled in switch", request);
2967 }
2969 do_scroll_view(view, lines);
2970 }
2972 /* Cursor moving */
2973 static void
2974 move_view(struct view *view, enum request request)
2975 {
2976 int scroll_steps = 0;
2977 int steps;
2979 switch (request) {
2980 case REQ_MOVE_FIRST_LINE:
2981 steps = -view->lineno;
2982 break;
2984 case REQ_MOVE_LAST_LINE:
2985 steps = view->lines - view->lineno - 1;
2986 break;
2988 case REQ_MOVE_PAGE_UP:
2989 steps = view->height > view->lineno
2990 ? -view->lineno : -view->height;
2991 break;
2993 case REQ_MOVE_PAGE_DOWN:
2994 steps = view->lineno + view->height >= view->lines
2995 ? view->lines - view->lineno - 1 : view->height;
2996 break;
2998 case REQ_MOVE_UP:
2999 steps = -1;
3000 break;
3002 case REQ_MOVE_DOWN:
3003 steps = 1;
3004 break;
3006 default:
3007 die("request %d not handled in switch", request);
3008 }
3010 if (steps <= 0 && view->lineno == 0) {
3011 report("Cannot move beyond the first line");
3012 return;
3014 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3015 report("Cannot move beyond the last line");
3016 return;
3017 }
3019 /* Move the current line */
3020 view->lineno += steps;
3021 assert(0 <= view->lineno && view->lineno < view->lines);
3023 /* Check whether the view needs to be scrolled */
3024 if (view->lineno < view->offset ||
3025 view->lineno >= view->offset + view->height) {
3026 scroll_steps = steps;
3027 if (steps < 0 && -steps > view->offset) {
3028 scroll_steps = -view->offset;
3030 } else if (steps > 0) {
3031 if (view->lineno == view->lines - 1 &&
3032 view->lines > view->height) {
3033 scroll_steps = view->lines - view->offset - 1;
3034 if (scroll_steps >= view->height)
3035 scroll_steps -= view->height - 1;
3036 }
3037 }
3038 }
3040 if (!view_is_displayed(view)) {
3041 view->offset += scroll_steps;
3042 assert(0 <= view->offset && view->offset < view->lines);
3043 view->ops->select(view, &view->line[view->lineno]);
3044 return;
3045 }
3047 /* Repaint the old "current" line if we be scrolling */
3048 if (ABS(steps) < view->height)
3049 draw_view_line(view, view->lineno - steps - view->offset);
3051 if (scroll_steps) {
3052 do_scroll_view(view, scroll_steps);
3053 return;
3054 }
3056 /* Draw the current line */
3057 draw_view_line(view, view->lineno - view->offset);
3059 wnoutrefresh(view->win);
3060 report("");
3061 }
3064 /*
3065 * Searching
3066 */
3068 static void search_view(struct view *view, enum request request);
3070 static bool
3071 grep_text(struct view *view, const char *text[])
3072 {
3073 regmatch_t pmatch;
3074 size_t i;
3076 for (i = 0; text[i]; i++)
3077 if (*text[i] &&
3078 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3079 return TRUE;
3080 return FALSE;
3081 }
3083 static void
3084 select_view_line(struct view *view, unsigned long lineno)
3085 {
3086 unsigned long old_lineno = view->lineno;
3087 unsigned long old_offset = view->offset;
3089 if (goto_view_line(view, view->offset, lineno)) {
3090 if (view_is_displayed(view)) {
3091 if (old_offset != view->offset) {
3092 redraw_view(view);
3093 } else {
3094 draw_view_line(view, old_lineno - view->offset);
3095 draw_view_line(view, view->lineno - view->offset);
3096 wnoutrefresh(view->win);
3097 }
3098 } else {
3099 view->ops->select(view, &view->line[view->lineno]);
3100 }
3101 }
3102 }
3104 static void
3105 find_next(struct view *view, enum request request)
3106 {
3107 unsigned long lineno = view->lineno;
3108 int direction;
3110 if (!*view->grep) {
3111 if (!*opt_search)
3112 report("No previous search");
3113 else
3114 search_view(view, request);
3115 return;
3116 }
3118 switch (request) {
3119 case REQ_SEARCH:
3120 case REQ_FIND_NEXT:
3121 direction = 1;
3122 break;
3124 case REQ_SEARCH_BACK:
3125 case REQ_FIND_PREV:
3126 direction = -1;
3127 break;
3129 default:
3130 return;
3131 }
3133 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3134 lineno += direction;
3136 /* Note, lineno is unsigned long so will wrap around in which case it
3137 * will become bigger than view->lines. */
3138 for (; lineno < view->lines; lineno += direction) {
3139 if (view->ops->grep(view, &view->line[lineno])) {
3140 select_view_line(view, lineno);
3141 report("Line %ld matches '%s'", lineno + 1, view->grep);
3142 return;
3143 }
3144 }
3146 report("No match found for '%s'", view->grep);
3147 }
3149 static void
3150 search_view(struct view *view, enum request request)
3151 {
3152 int regex_err;
3154 if (view->regex) {
3155 regfree(view->regex);
3156 *view->grep = 0;
3157 } else {
3158 view->regex = calloc(1, sizeof(*view->regex));
3159 if (!view->regex)
3160 return;
3161 }
3163 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3164 if (regex_err != 0) {
3165 char buf[SIZEOF_STR] = "unknown error";
3167 regerror(regex_err, view->regex, buf, sizeof(buf));
3168 report("Search failed: %s", buf);
3169 return;
3170 }
3172 string_copy(view->grep, opt_search);
3174 find_next(view, request);
3175 }
3177 /*
3178 * Incremental updating
3179 */
3181 static void
3182 reset_view(struct view *view)
3183 {
3184 int i;
3186 for (i = 0; i < view->lines; i++)
3187 free(view->line[i].data);
3188 free(view->line);
3190 view->p_offset = view->offset;
3191 view->p_yoffset = view->yoffset;
3192 view->p_lineno = view->lineno;
3194 view->line = NULL;
3195 view->offset = 0;
3196 view->yoffset = 0;
3197 view->lines = 0;
3198 view->lineno = 0;
3199 view->vid[0] = 0;
3200 view->update_secs = 0;
3201 }
3203 static const char *
3204 format_arg(const char *name)
3205 {
3206 static struct {
3207 const char *name;
3208 size_t namelen;
3209 const char *value;
3210 const char *value_if_empty;
3211 } vars[] = {
3212 #define FORMAT_VAR(name, value, value_if_empty) \
3213 { name, STRING_SIZE(name), value, value_if_empty }
3214 FORMAT_VAR("%(directory)", opt_path, ""),
3215 FORMAT_VAR("%(file)", opt_file, ""),
3216 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3217 FORMAT_VAR("%(head)", ref_head, ""),
3218 FORMAT_VAR("%(commit)", ref_commit, ""),
3219 FORMAT_VAR("%(blob)", ref_blob, ""),
3220 FORMAT_VAR("%(branch)", ref_branch, ""),
3221 };
3222 int i;
3224 for (i = 0; i < ARRAY_SIZE(vars); i++)
3225 if (!strncmp(name, vars[i].name, vars[i].namelen))
3226 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3228 report("Unknown replacement: `%s`", name);
3229 return NULL;
3230 }
3232 static bool
3233 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3234 {
3235 char buf[SIZEOF_STR];
3236 int argc;
3238 argv_free(*dst_argv);
3240 for (argc = 0; src_argv[argc]; argc++) {
3241 const char *arg = src_argv[argc];
3242 size_t bufpos = 0;
3244 if (!strcmp(arg, "%(fileargs)")) {
3245 if (!argv_append_array(dst_argv, opt_file_argv))
3246 break;
3247 continue;
3249 } else if (!strcmp(arg, "%(diffargs)")) {
3250 if (!argv_append_array(dst_argv, opt_diff_argv))
3251 break;
3252 continue;
3254 } else if (!strcmp(arg, "%(revargs)") ||
3255 (first && !strcmp(arg, "%(commit)"))) {
3256 if (!argv_append_array(dst_argv, opt_rev_argv))
3257 break;
3258 continue;
3259 }
3261 while (arg) {
3262 char *next = strstr(arg, "%(");
3263 int len = next - arg;
3264 const char *value;
3266 if (!next || !replace) {
3267 len = strlen(arg);
3268 value = "";
3270 } else {
3271 value = format_arg(next);
3273 if (!value) {
3274 return FALSE;
3275 }
3276 }
3278 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3279 return FALSE;
3281 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3282 }
3284 if (!argv_append(dst_argv, buf))
3285 break;
3286 }
3288 return src_argv[argc] == NULL;
3289 }
3291 static bool
3292 restore_view_position(struct view *view)
3293 {
3294 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3295 return FALSE;
3297 /* Changing the view position cancels the restoring. */
3298 /* FIXME: Changing back to the first line is not detected. */
3299 if (view->offset != 0 || view->lineno != 0) {
3300 view->p_restore = FALSE;
3301 return FALSE;
3302 }
3304 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3305 view_is_displayed(view))
3306 werase(view->win);
3308 view->yoffset = view->p_yoffset;
3309 view->p_restore = FALSE;
3311 return TRUE;
3312 }
3314 static void
3315 end_update(struct view *view, bool force)
3316 {
3317 if (!view->pipe)
3318 return;
3319 while (!view->ops->read(view, NULL))
3320 if (!force)
3321 return;
3322 if (force)
3323 io_kill(view->pipe);
3324 io_done(view->pipe);
3325 view->pipe = NULL;
3326 }
3328 static void
3329 setup_update(struct view *view, const char *vid)
3330 {
3331 reset_view(view);
3332 string_copy_rev(view->vid, vid);
3333 view->pipe = &view->io;
3334 view->start_time = time(NULL);
3335 }
3337 static bool
3338 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3339 {
3340 view->dir = dir;
3341 return format_argv(&view->argv, argv, replace, !view->prev);
3342 }
3344 static bool
3345 prepare_update(struct view *view, const char *argv[], const char *dir)
3346 {
3347 if (view->pipe)
3348 end_update(view, TRUE);
3349 return prepare_io(view, dir, argv, FALSE);
3350 }
3352 static bool
3353 start_update(struct view *view, const char **argv, const char *dir)
3354 {
3355 if (view->pipe)
3356 io_done(view->pipe);
3357 return prepare_io(view, dir, argv, FALSE) &&
3358 io_run(&view->io, IO_RD, dir, view->argv);
3359 }
3361 static bool
3362 prepare_update_file(struct view *view, const char *name)
3363 {
3364 if (view->pipe)
3365 end_update(view, TRUE);
3366 argv_free(view->argv);
3367 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3368 }
3370 static bool
3371 begin_update(struct view *view, bool refresh)
3372 {
3373 if (view->pipe)
3374 end_update(view, TRUE);
3376 if (!refresh) {
3377 if (view->ops->prepare) {
3378 if (!view->ops->prepare(view))
3379 return FALSE;
3380 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3381 return FALSE;
3382 }
3384 /* Put the current ref_* value to the view title ref
3385 * member. This is needed by the blob view. Most other
3386 * views sets it automatically after loading because the
3387 * first line is a commit line. */
3388 string_copy_rev(view->ref, view->id);
3389 }
3391 if (view->argv && view->argv[0] &&
3392 !io_run(&view->io, IO_RD, view->dir, view->argv))
3393 return FALSE;
3395 setup_update(view, view->id);
3397 return TRUE;
3398 }
3400 static bool
3401 update_view(struct view *view)
3402 {
3403 char out_buffer[BUFSIZ * 2];
3404 char *line;
3405 /* Clear the view and redraw everything since the tree sorting
3406 * might have rearranged things. */
3407 bool redraw = view->lines == 0;
3408 bool can_read = TRUE;
3410 if (!view->pipe)
3411 return TRUE;
3413 if (!io_can_read(view->pipe)) {
3414 if (view->lines == 0 && view_is_displayed(view)) {
3415 time_t secs = time(NULL) - view->start_time;
3417 if (secs > 1 && secs > view->update_secs) {
3418 if (view->update_secs == 0)
3419 redraw_view(view);
3420 update_view_title(view);
3421 view->update_secs = secs;
3422 }
3423 }
3424 return TRUE;
3425 }
3427 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3428 if (opt_iconv_in != ICONV_NONE) {
3429 ICONV_CONST char *inbuf = line;
3430 size_t inlen = strlen(line) + 1;
3432 char *outbuf = out_buffer;
3433 size_t outlen = sizeof(out_buffer);
3435 size_t ret;
3437 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3438 if (ret != (size_t) -1)
3439 line = out_buffer;
3440 }
3442 if (!view->ops->read(view, line)) {
3443 report("Allocation failure");
3444 end_update(view, TRUE);
3445 return FALSE;
3446 }
3447 }
3449 {
3450 unsigned long lines = view->lines;
3451 int digits;
3453 for (digits = 0; lines; digits++)
3454 lines /= 10;
3456 /* Keep the displayed view in sync with line number scaling. */
3457 if (digits != view->digits) {
3458 view->digits = digits;
3459 if (opt_line_number || view->type == VIEW_BLAME)
3460 redraw = TRUE;
3461 }
3462 }
3464 if (io_error(view->pipe)) {
3465 report("Failed to read: %s", io_strerror(view->pipe));
3466 end_update(view, TRUE);
3468 } else if (io_eof(view->pipe)) {
3469 if (view_is_displayed(view))
3470 report("");
3471 end_update(view, FALSE);
3472 }
3474 if (restore_view_position(view))
3475 redraw = TRUE;
3477 if (!view_is_displayed(view))
3478 return TRUE;
3480 if (redraw)
3481 redraw_view_from(view, 0);
3482 else
3483 redraw_view_dirty(view);
3485 /* Update the title _after_ the redraw so that if the redraw picks up a
3486 * commit reference in view->ref it'll be available here. */
3487 update_view_title(view);
3488 return TRUE;
3489 }
3491 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3493 static struct line *
3494 add_line_data(struct view *view, void *data, enum line_type type)
3495 {
3496 struct line *line;
3498 if (!realloc_lines(&view->line, view->lines, 1))
3499 return NULL;
3501 line = &view->line[view->lines++];
3502 memset(line, 0, sizeof(*line));
3503 line->type = type;
3504 line->data = data;
3505 line->dirty = 1;
3507 return line;
3508 }
3510 static struct line *
3511 add_line_text(struct view *view, const char *text, enum line_type type)
3512 {
3513 char *data = text ? strdup(text) : NULL;
3515 return data ? add_line_data(view, data, type) : NULL;
3516 }
3518 static struct line *
3519 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3520 {
3521 char buf[SIZEOF_STR];
3522 va_list args;
3524 va_start(args, fmt);
3525 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3526 buf[0] = 0;
3527 va_end(args);
3529 return buf[0] ? add_line_text(view, buf, type) : NULL;
3530 }
3532 /*
3533 * View opening
3534 */
3536 enum open_flags {
3537 OPEN_DEFAULT = 0, /* Use default view switching. */
3538 OPEN_SPLIT = 1, /* Split current view. */
3539 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3540 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3541 OPEN_PREPARED = 32, /* Open already prepared command. */
3542 };
3544 static void
3545 open_view(struct view *prev, enum request request, enum open_flags flags)
3546 {
3547 bool split = !!(flags & OPEN_SPLIT);
3548 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3549 bool nomaximize = !!(flags & OPEN_REFRESH);
3550 struct view *view = VIEW(request);
3551 int nviews = displayed_views();
3552 struct view *base_view = display[0];
3554 if (view == prev && nviews == 1 && !reload) {
3555 report("Already in %s view", view->name);
3556 return;
3557 }
3559 if (view->git_dir && !opt_git_dir[0]) {
3560 report("The %s view is disabled in pager view", view->name);
3561 return;
3562 }
3564 if (split) {
3565 display[1] = view;
3566 current_view = 1;
3567 view->parent = prev;
3568 } else if (!nomaximize) {
3569 /* Maximize the current view. */
3570 memset(display, 0, sizeof(display));
3571 current_view = 0;
3572 display[current_view] = view;
3573 }
3575 /* No prev signals that this is the first loaded view. */
3576 if (prev && view != prev) {
3577 view->prev = prev;
3578 }
3580 /* Resize the view when switching between split- and full-screen,
3581 * or when switching between two different full-screen views. */
3582 if (nviews != displayed_views() ||
3583 (nviews == 1 && base_view != display[0]))
3584 resize_display();
3586 if (view->ops->open) {
3587 if (view->pipe)
3588 end_update(view, TRUE);
3589 if (!view->ops->open(view)) {
3590 report("Failed to load %s view", view->name);
3591 return;
3592 }
3593 restore_view_position(view);
3595 } else if ((reload || strcmp(view->vid, view->id)) &&
3596 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3597 report("Failed to load %s view", view->name);
3598 return;
3599 }
3601 if (split && prev->lineno - prev->offset >= prev->height) {
3602 /* Take the title line into account. */
3603 int lines = prev->lineno - prev->offset - prev->height + 1;
3605 /* Scroll the view that was split if the current line is
3606 * outside the new limited view. */
3607 do_scroll_view(prev, lines);
3608 }
3610 if (prev && view != prev && split && view_is_displayed(prev)) {
3611 /* "Blur" the previous view. */
3612 update_view_title(prev);
3613 }
3615 if (view->pipe && view->lines == 0) {
3616 /* Clear the old view and let the incremental updating refill
3617 * the screen. */
3618 werase(view->win);
3619 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3620 report("");
3621 } else if (view_is_displayed(view)) {
3622 redraw_view(view);
3623 report("");
3624 }
3625 }
3627 static void
3628 open_external_viewer(const char *argv[], const char *dir)
3629 {
3630 def_prog_mode(); /* save current tty modes */
3631 endwin(); /* restore original tty modes */
3632 io_run_fg(argv, dir);
3633 fprintf(stderr, "Press Enter to continue");
3634 getc(opt_tty);
3635 reset_prog_mode();
3636 redraw_display(TRUE);
3637 }
3639 static void
3640 open_mergetool(const char *file)
3641 {
3642 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3644 open_external_viewer(mergetool_argv, opt_cdup);
3645 }
3647 static void
3648 open_editor(const char *file)
3649 {
3650 const char *editor_argv[] = { "vi", file, NULL };
3651 const char *editor;
3653 editor = getenv("GIT_EDITOR");
3654 if (!editor && *opt_editor)
3655 editor = opt_editor;
3656 if (!editor)
3657 editor = getenv("VISUAL");
3658 if (!editor)
3659 editor = getenv("EDITOR");
3660 if (!editor)
3661 editor = "vi";
3663 editor_argv[0] = editor;
3664 open_external_viewer(editor_argv, opt_cdup);
3665 }
3667 static void
3668 open_run_request(enum request request)
3669 {
3670 struct run_request *req = get_run_request(request);
3671 const char **argv = NULL;
3673 if (!req) {
3674 report("Unknown run request");
3675 return;
3676 }
3678 if (format_argv(&argv, req->argv, TRUE, FALSE))
3679 open_external_viewer(argv, NULL);
3680 if (argv)
3681 argv_free(argv);
3682 free(argv);
3683 }
3685 /*
3686 * User request switch noodle
3687 */
3689 static int
3690 view_driver(struct view *view, enum request request)
3691 {
3692 int i;
3694 if (request == REQ_NONE)
3695 return TRUE;
3697 if (request > REQ_NONE) {
3698 open_run_request(request);
3699 view_request(view, REQ_REFRESH);
3700 return TRUE;
3701 }
3703 request = view_request(view, request);
3704 if (request == REQ_NONE)
3705 return TRUE;
3707 switch (request) {
3708 case REQ_MOVE_UP:
3709 case REQ_MOVE_DOWN:
3710 case REQ_MOVE_PAGE_UP:
3711 case REQ_MOVE_PAGE_DOWN:
3712 case REQ_MOVE_FIRST_LINE:
3713 case REQ_MOVE_LAST_LINE:
3714 move_view(view, request);
3715 break;
3717 case REQ_SCROLL_FIRST_COL:
3718 case REQ_SCROLL_LEFT:
3719 case REQ_SCROLL_RIGHT:
3720 case REQ_SCROLL_LINE_DOWN:
3721 case REQ_SCROLL_LINE_UP:
3722 case REQ_SCROLL_PAGE_DOWN:
3723 case REQ_SCROLL_PAGE_UP:
3724 scroll_view(view, request);
3725 break;
3727 case REQ_VIEW_BLAME:
3728 if (!opt_file[0]) {
3729 report("No file chosen, press %s to open tree view",
3730 get_key(view->keymap, REQ_VIEW_TREE));
3731 break;
3732 }
3733 open_view(view, request, OPEN_DEFAULT);
3734 break;
3736 case REQ_VIEW_BLOB:
3737 if (!ref_blob[0]) {
3738 report("No file chosen, press %s to open tree view",
3739 get_key(view->keymap, REQ_VIEW_TREE));
3740 break;
3741 }
3742 open_view(view, request, OPEN_DEFAULT);
3743 break;
3745 case REQ_VIEW_PAGER:
3746 if (view == NULL) {
3747 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3748 die("Failed to open stdin");
3749 open_view(view, request, OPEN_PREPARED);
3750 break;
3751 }
3753 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3754 report("No pager content, press %s to run command from prompt",
3755 get_key(view->keymap, REQ_PROMPT));
3756 break;
3757 }
3758 open_view(view, request, OPEN_DEFAULT);
3759 break;
3761 case REQ_VIEW_STAGE:
3762 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3763 report("No stage content, press %s to open the status view and choose file",
3764 get_key(view->keymap, REQ_VIEW_STATUS));
3765 break;
3766 }
3767 open_view(view, request, OPEN_DEFAULT);
3768 break;
3770 case REQ_VIEW_STATUS:
3771 if (opt_is_inside_work_tree == FALSE) {
3772 report("The status view requires a working tree");
3773 break;
3774 }
3775 open_view(view, request, OPEN_DEFAULT);
3776 break;
3778 case REQ_VIEW_MAIN:
3779 case REQ_VIEW_DIFF:
3780 case REQ_VIEW_LOG:
3781 case REQ_VIEW_TREE:
3782 case REQ_VIEW_HELP:
3783 case REQ_VIEW_BRANCH:
3784 open_view(view, request, OPEN_DEFAULT);
3785 break;
3787 case REQ_NEXT:
3788 case REQ_PREVIOUS:
3789 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3791 if (view->parent) {
3792 int line;
3794 view = view->parent;
3795 line = view->lineno;
3796 move_view(view, request);
3797 if (view_is_displayed(view))
3798 update_view_title(view);
3799 if (line != view->lineno)
3800 view_request(view, REQ_ENTER);
3801 } else {
3802 move_view(view, request);
3803 }
3804 break;
3806 case REQ_VIEW_NEXT:
3807 {
3808 int nviews = displayed_views();
3809 int next_view = (current_view + 1) % nviews;
3811 if (next_view == current_view) {
3812 report("Only one view is displayed");
3813 break;
3814 }
3816 current_view = next_view;
3817 /* Blur out the title of the previous view. */
3818 update_view_title(view);
3819 report("");
3820 break;
3821 }
3822 case REQ_REFRESH:
3823 report("Refreshing is not yet supported for the %s view", view->name);
3824 break;
3826 case REQ_MAXIMIZE:
3827 if (displayed_views() == 2)
3828 maximize_view(view);
3829 break;
3831 case REQ_OPTIONS:
3832 case REQ_TOGGLE_LINENO:
3833 case REQ_TOGGLE_DATE:
3834 case REQ_TOGGLE_AUTHOR:
3835 case REQ_TOGGLE_REV_GRAPH:
3836 case REQ_TOGGLE_REFS:
3837 toggle_option(request);
3838 break;
3840 case REQ_TOGGLE_SORT_FIELD:
3841 case REQ_TOGGLE_SORT_ORDER:
3842 report("Sorting is not yet supported for the %s view", view->name);
3843 break;
3845 case REQ_SEARCH:
3846 case REQ_SEARCH_BACK:
3847 search_view(view, request);
3848 break;
3850 case REQ_FIND_NEXT:
3851 case REQ_FIND_PREV:
3852 find_next(view, request);
3853 break;
3855 case REQ_STOP_LOADING:
3856 foreach_view(view, i) {
3857 if (view->pipe)
3858 report("Stopped loading the %s view", view->name),
3859 end_update(view, TRUE);
3860 }
3861 break;
3863 case REQ_SHOW_VERSION:
3864 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3865 return TRUE;
3867 case REQ_SCREEN_REDRAW:
3868 redraw_display(TRUE);
3869 break;
3871 case REQ_EDIT:
3872 report("Nothing to edit");
3873 break;
3875 case REQ_ENTER:
3876 report("Nothing to enter");
3877 break;
3879 case REQ_VIEW_CLOSE:
3880 /* XXX: Mark closed views by letting view->prev point to the
3881 * view itself. Parents to closed view should never be
3882 * followed. */
3883 if (view->prev && view->prev != view) {
3884 maximize_view(view->prev);
3885 view->prev = view;
3886 break;
3887 }
3888 /* Fall-through */
3889 case REQ_QUIT:
3890 return FALSE;
3892 default:
3893 report("Unknown key, press %s for help",
3894 get_key(view->keymap, REQ_VIEW_HELP));
3895 return TRUE;
3896 }
3898 return TRUE;
3899 }
3902 /*
3903 * View backend utilities
3904 */
3906 enum sort_field {
3907 ORDERBY_NAME,
3908 ORDERBY_DATE,
3909 ORDERBY_AUTHOR,
3910 };
3912 struct sort_state {
3913 const enum sort_field *fields;
3914 size_t size, current;
3915 bool reverse;
3916 };
3918 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3919 #define get_sort_field(state) ((state).fields[(state).current])
3920 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3922 static void
3923 sort_view(struct view *view, enum request request, struct sort_state *state,
3924 int (*compare)(const void *, const void *))
3925 {
3926 switch (request) {
3927 case REQ_TOGGLE_SORT_FIELD:
3928 state->current = (state->current + 1) % state->size;
3929 break;
3931 case REQ_TOGGLE_SORT_ORDER:
3932 state->reverse = !state->reverse;
3933 break;
3934 default:
3935 die("Not a sort request");
3936 }
3938 qsort(view->line, view->lines, sizeof(*view->line), compare);
3939 redraw_view(view);
3940 }
3942 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3944 /* Small author cache to reduce memory consumption. It uses binary
3945 * search to lookup or find place to position new entries. No entries
3946 * are ever freed. */
3947 static const char *
3948 get_author(const char *name)
3949 {
3950 static const char **authors;
3951 static size_t authors_size;
3952 int from = 0, to = authors_size - 1;
3954 while (from <= to) {
3955 size_t pos = (to + from) / 2;
3956 int cmp = strcmp(name, authors[pos]);
3958 if (!cmp)
3959 return authors[pos];
3961 if (cmp < 0)
3962 to = pos - 1;
3963 else
3964 from = pos + 1;
3965 }
3967 if (!realloc_authors(&authors, authors_size, 1))
3968 return NULL;
3969 name = strdup(name);
3970 if (!name)
3971 return NULL;
3973 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3974 authors[from] = name;
3975 authors_size++;
3977 return name;
3978 }
3980 static void
3981 parse_timesec(struct time *time, const char *sec)
3982 {
3983 time->sec = (time_t) atol(sec);
3984 }
3986 static void
3987 parse_timezone(struct time *time, const char *zone)
3988 {
3989 long tz;
3991 tz = ('0' - zone[1]) * 60 * 60 * 10;
3992 tz += ('0' - zone[2]) * 60 * 60;
3993 tz += ('0' - zone[3]) * 60 * 10;
3994 tz += ('0' - zone[4]) * 60;
3996 if (zone[0] == '-')
3997 tz = -tz;
3999 time->tz = tz;
4000 time->sec -= tz;
4001 }
4003 /* Parse author lines where the name may be empty:
4004 * author <email@address.tld> 1138474660 +0100
4005 */
4006 static void
4007 parse_author_line(char *ident, const char **author, struct time *time)
4008 {
4009 char *nameend = strchr(ident, '<');
4010 char *emailend = strchr(ident, '>');
4012 if (nameend && emailend)
4013 *nameend = *emailend = 0;
4014 ident = chomp_string(ident);
4015 if (!*ident) {
4016 if (nameend)
4017 ident = chomp_string(nameend + 1);
4018 if (!*ident)
4019 ident = "Unknown";
4020 }
4022 *author = get_author(ident);
4024 /* Parse epoch and timezone */
4025 if (emailend && emailend[1] == ' ') {
4026 char *secs = emailend + 2;
4027 char *zone = strchr(secs, ' ');
4029 parse_timesec(time, secs);
4031 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4032 parse_timezone(time, zone + 1);
4033 }
4034 }
4036 /*
4037 * Pager backend
4038 */
4040 static bool
4041 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4042 {
4043 if (opt_line_number && draw_lineno(view, lineno))
4044 return TRUE;
4046 draw_text(view, line->type, line->data);
4047 return TRUE;
4048 }
4050 static bool
4051 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4052 {
4053 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4054 char ref[SIZEOF_STR];
4056 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4057 return TRUE;
4059 /* This is the only fatal call, since it can "corrupt" the buffer. */
4060 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4061 return FALSE;
4063 return TRUE;
4064 }
4066 static void
4067 add_pager_refs(struct view *view, struct line *line)
4068 {
4069 char buf[SIZEOF_STR];
4070 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4071 struct ref_list *list;
4072 size_t bufpos = 0, i;
4073 const char *sep = "Refs: ";
4074 bool is_tag = FALSE;
4076 assert(line->type == LINE_COMMIT);
4078 list = get_ref_list(commit_id);
4079 if (!list) {
4080 if (view->type == VIEW_DIFF)
4081 goto try_add_describe_ref;
4082 return;
4083 }
4085 for (i = 0; i < list->size; i++) {
4086 struct ref *ref = list->refs[i];
4087 const char *fmt = ref->tag ? "%s[%s]" :
4088 ref->remote ? "%s<%s>" : "%s%s";
4090 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4091 return;
4092 sep = ", ";
4093 if (ref->tag)
4094 is_tag = TRUE;
4095 }
4097 if (!is_tag && view->type == VIEW_DIFF) {
4098 try_add_describe_ref:
4099 /* Add <tag>-g<commit_id> "fake" reference. */
4100 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4101 return;
4102 }
4104 if (bufpos == 0)
4105 return;
4107 add_line_text(view, buf, LINE_PP_REFS);
4108 }
4110 static bool
4111 pager_read(struct view *view, char *data)
4112 {
4113 struct line *line;
4115 if (!data)
4116 return TRUE;
4118 line = add_line_text(view, data, get_line_type(data));
4119 if (!line)
4120 return FALSE;
4122 if (line->type == LINE_COMMIT &&
4123 (view->type == VIEW_DIFF ||
4124 view->type == VIEW_LOG))
4125 add_pager_refs(view, line);
4127 return TRUE;
4128 }
4130 static enum request
4131 pager_request(struct view *view, enum request request, struct line *line)
4132 {
4133 int split = 0;
4135 if (request != REQ_ENTER)
4136 return request;
4138 if (line->type == LINE_COMMIT &&
4139 (view->type == VIEW_LOG ||
4140 view->type == VIEW_PAGER)) {
4141 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4142 split = 1;
4143 }
4145 /* Always scroll the view even if it was split. That way
4146 * you can use Enter to scroll through the log view and
4147 * split open each commit diff. */
4148 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4150 /* FIXME: A minor workaround. Scrolling the view will call report("")
4151 * but if we are scrolling a non-current view this won't properly
4152 * update the view title. */
4153 if (split)
4154 update_view_title(view);
4156 return REQ_NONE;
4157 }
4159 static bool
4160 pager_grep(struct view *view, struct line *line)
4161 {
4162 const char *text[] = { line->data, NULL };
4164 return grep_text(view, text);
4165 }
4167 static void
4168 pager_select(struct view *view, struct line *line)
4169 {
4170 if (line->type == LINE_COMMIT) {
4171 char *text = (char *)line->data + STRING_SIZE("commit ");
4173 if (view->type != VIEW_PAGER)
4174 string_copy_rev(view->ref, text);
4175 string_copy_rev(ref_commit, text);
4176 }
4177 }
4179 static struct view_ops pager_ops = {
4180 "line",
4181 NULL,
4182 NULL,
4183 pager_read,
4184 pager_draw,
4185 pager_request,
4186 pager_grep,
4187 pager_select,
4188 };
4190 static const char *log_argv[SIZEOF_ARG] = {
4191 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4192 };
4194 static enum request
4195 log_request(struct view *view, enum request request, struct line *line)
4196 {
4197 switch (request) {
4198 case REQ_REFRESH:
4199 load_refs();
4200 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4201 return REQ_NONE;
4202 default:
4203 return pager_request(view, request, line);
4204 }
4205 }
4207 static struct view_ops log_ops = {
4208 "line",
4209 log_argv,
4210 NULL,
4211 pager_read,
4212 pager_draw,
4213 log_request,
4214 pager_grep,
4215 pager_select,
4216 };
4218 static const char *diff_argv[SIZEOF_ARG] = {
4219 "git", "show", "--pretty=fuller", "--no-color", "--root",
4220 "--patch-with-stat", "--find-copies-harder", "-C",
4221 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4222 };
4224 static bool
4225 diff_read(struct view *view, char *data)
4226 {
4227 if (!data) {
4228 /* Fall back to retry if no diff will be shown. */
4229 if (view->lines == 0 && opt_file_argv) {
4230 int pos = argv_size(view->argv)
4231 - argv_size(opt_file_argv) - 1;
4233 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4234 for (; view->argv[pos]; pos++) {
4235 free((void *) view->argv[pos]);
4236 view->argv[pos] = NULL;
4237 }
4239 if (view->pipe)
4240 io_done(view->pipe);
4241 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4242 return FALSE;
4243 }
4244 }
4245 return TRUE;
4246 }
4248 return pager_read(view, data);
4249 }
4251 static struct view_ops diff_ops = {
4252 "line",
4253 diff_argv,
4254 NULL,
4255 diff_read,
4256 pager_draw,
4257 pager_request,
4258 pager_grep,
4259 pager_select,
4260 };
4262 /*
4263 * Help backend
4264 */
4266 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4268 static bool
4269 help_open_keymap_title(struct view *view, enum keymap keymap)
4270 {
4271 struct line *line;
4273 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4274 help_keymap_hidden[keymap] ? '+' : '-',
4275 enum_name(keymap_table[keymap]));
4276 if (line)
4277 line->other = keymap;
4279 return help_keymap_hidden[keymap];
4280 }
4282 static void
4283 help_open_keymap(struct view *view, enum keymap keymap)
4284 {
4285 const char *group = NULL;
4286 char buf[SIZEOF_STR];
4287 size_t bufpos;
4288 bool add_title = TRUE;
4289 int i;
4291 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4292 const char *key = NULL;
4294 if (req_info[i].request == REQ_NONE)
4295 continue;
4297 if (!req_info[i].request) {
4298 group = req_info[i].help;
4299 continue;
4300 }
4302 key = get_keys(keymap, req_info[i].request, TRUE);
4303 if (!key || !*key)
4304 continue;
4306 if (add_title && help_open_keymap_title(view, keymap))
4307 return;
4308 add_title = FALSE;
4310 if (group) {
4311 add_line_text(view, group, LINE_HELP_GROUP);
4312 group = NULL;
4313 }
4315 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4316 enum_name(req_info[i]), req_info[i].help);
4317 }
4319 group = "External commands:";
4321 for (i = 0; i < run_requests; i++) {
4322 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4323 const char *key;
4324 int argc;
4326 if (!req || req->keymap != keymap)
4327 continue;
4329 key = get_key_name(req->key);
4330 if (!*key)
4331 key = "(no key defined)";
4333 if (add_title && help_open_keymap_title(view, keymap))
4334 return;
4335 if (group) {
4336 add_line_text(view, group, LINE_HELP_GROUP);
4337 group = NULL;
4338 }
4340 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4341 if (!string_format_from(buf, &bufpos, "%s%s",
4342 argc ? " " : "", req->argv[argc]))
4343 return;
4345 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4346 }
4347 }
4349 static bool
4350 help_open(struct view *view)
4351 {
4352 enum keymap keymap;
4354 reset_view(view);
4355 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4356 add_line_text(view, "", LINE_DEFAULT);
4358 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4359 help_open_keymap(view, keymap);
4361 return TRUE;
4362 }
4364 static enum request
4365 help_request(struct view *view, enum request request, struct line *line)
4366 {
4367 switch (request) {
4368 case REQ_ENTER:
4369 if (line->type == LINE_HELP_KEYMAP) {
4370 help_keymap_hidden[line->other] =
4371 !help_keymap_hidden[line->other];
4372 view->p_restore = TRUE;
4373 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4374 }
4376 return REQ_NONE;
4377 default:
4378 return pager_request(view, request, line);
4379 }
4380 }
4382 static struct view_ops help_ops = {
4383 "line",
4384 NULL,
4385 help_open,
4386 NULL,
4387 pager_draw,
4388 help_request,
4389 pager_grep,
4390 pager_select,
4391 };
4394 /*
4395 * Tree backend
4396 */
4398 struct tree_stack_entry {
4399 struct tree_stack_entry *prev; /* Entry below this in the stack */
4400 unsigned long lineno; /* Line number to restore */
4401 char *name; /* Position of name in opt_path */
4402 };
4404 /* The top of the path stack. */
4405 static struct tree_stack_entry *tree_stack = NULL;
4406 unsigned long tree_lineno = 0;
4408 static void
4409 pop_tree_stack_entry(void)
4410 {
4411 struct tree_stack_entry *entry = tree_stack;
4413 tree_lineno = entry->lineno;
4414 entry->name[0] = 0;
4415 tree_stack = entry->prev;
4416 free(entry);
4417 }
4419 static void
4420 push_tree_stack_entry(const char *name, unsigned long lineno)
4421 {
4422 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4423 size_t pathlen = strlen(opt_path);
4425 if (!entry)
4426 return;
4428 entry->prev = tree_stack;
4429 entry->name = opt_path + pathlen;
4430 tree_stack = entry;
4432 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4433 pop_tree_stack_entry();
4434 return;
4435 }
4437 /* Move the current line to the first tree entry. */
4438 tree_lineno = 1;
4439 entry->lineno = lineno;
4440 }
4442 /* Parse output from git-ls-tree(1):
4443 *
4444 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4445 */
4447 #define SIZEOF_TREE_ATTR \
4448 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4450 #define SIZEOF_TREE_MODE \
4451 STRING_SIZE("100644 ")
4453 #define TREE_ID_OFFSET \
4454 STRING_SIZE("100644 blob ")
4456 struct tree_entry {
4457 char id[SIZEOF_REV];
4458 mode_t mode;
4459 struct time time; /* Date from the author ident. */
4460 const char *author; /* Author of the commit. */
4461 char name[1];
4462 };
4464 static const char *
4465 tree_path(const struct line *line)
4466 {
4467 return ((struct tree_entry *) line->data)->name;
4468 }
4470 static int
4471 tree_compare_entry(const struct line *line1, const struct line *line2)
4472 {
4473 if (line1->type != line2->type)
4474 return line1->type == LINE_TREE_DIR ? -1 : 1;
4475 return strcmp(tree_path(line1), tree_path(line2));
4476 }
4478 static const enum sort_field tree_sort_fields[] = {
4479 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4480 };
4481 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4483 static int
4484 tree_compare(const void *l1, const void *l2)
4485 {
4486 const struct line *line1 = (const struct line *) l1;
4487 const struct line *line2 = (const struct line *) l2;
4488 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4489 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4491 if (line1->type == LINE_TREE_HEAD)
4492 return -1;
4493 if (line2->type == LINE_TREE_HEAD)
4494 return 1;
4496 switch (get_sort_field(tree_sort_state)) {
4497 case ORDERBY_DATE:
4498 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4500 case ORDERBY_AUTHOR:
4501 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4503 case ORDERBY_NAME:
4504 default:
4505 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4506 }
4507 }
4510 static struct line *
4511 tree_entry(struct view *view, enum line_type type, const char *path,
4512 const char *mode, const char *id)
4513 {
4514 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4515 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4517 if (!entry || !line) {
4518 free(entry);
4519 return NULL;
4520 }
4522 strncpy(entry->name, path, strlen(path));
4523 if (mode)
4524 entry->mode = strtoul(mode, NULL, 8);
4525 if (id)
4526 string_copy_rev(entry->id, id);
4528 return line;
4529 }
4531 static bool
4532 tree_read_date(struct view *view, char *text, bool *read_date)
4533 {
4534 static const char *author_name;
4535 static struct time author_time;
4537 if (!text && *read_date) {
4538 *read_date = FALSE;
4539 return TRUE;
4541 } else if (!text) {
4542 char *path = *opt_path ? opt_path : ".";
4543 /* Find next entry to process */
4544 const char *log_file[] = {
4545 "git", "log", "--no-color", "--pretty=raw",
4546 "--cc", "--raw", view->id, "--", path, NULL
4547 };
4549 if (!view->lines) {
4550 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4551 report("Tree is empty");
4552 return TRUE;
4553 }
4555 if (!start_update(view, log_file, opt_cdup)) {
4556 report("Failed to load tree data");
4557 return TRUE;
4558 }
4560 *read_date = TRUE;
4561 return FALSE;
4563 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4564 parse_author_line(text + STRING_SIZE("author "),
4565 &author_name, &author_time);
4567 } else if (*text == ':') {
4568 char *pos;
4569 size_t annotated = 1;
4570 size_t i;
4572 pos = strchr(text, '\t');
4573 if (!pos)
4574 return TRUE;
4575 text = pos + 1;
4576 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4577 text += strlen(opt_path);
4578 pos = strchr(text, '/');
4579 if (pos)
4580 *pos = 0;
4582 for (i = 1; i < view->lines; i++) {
4583 struct line *line = &view->line[i];
4584 struct tree_entry *entry = line->data;
4586 annotated += !!entry->author;
4587 if (entry->author || strcmp(entry->name, text))
4588 continue;
4590 entry->author = author_name;
4591 entry->time = author_time;
4592 line->dirty = 1;
4593 break;
4594 }
4596 if (annotated == view->lines)
4597 io_kill(view->pipe);
4598 }
4599 return TRUE;
4600 }
4602 static bool
4603 tree_read(struct view *view, char *text)
4604 {
4605 static bool read_date = FALSE;
4606 struct tree_entry *data;
4607 struct line *entry, *line;
4608 enum line_type type;
4609 size_t textlen = text ? strlen(text) : 0;
4610 char *path = text + SIZEOF_TREE_ATTR;
4612 if (read_date || !text)
4613 return tree_read_date(view, text, &read_date);
4615 if (textlen <= SIZEOF_TREE_ATTR)
4616 return FALSE;
4617 if (view->lines == 0 &&
4618 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4619 return FALSE;
4621 /* Strip the path part ... */
4622 if (*opt_path) {
4623 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4624 size_t striplen = strlen(opt_path);
4626 if (pathlen > striplen)
4627 memmove(path, path + striplen,
4628 pathlen - striplen + 1);
4630 /* Insert "link" to parent directory. */
4631 if (view->lines == 1 &&
4632 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4633 return FALSE;
4634 }
4636 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4637 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4638 if (!entry)
4639 return FALSE;
4640 data = entry->data;
4642 /* Skip "Directory ..." and ".." line. */
4643 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4644 if (tree_compare_entry(line, entry) <= 0)
4645 continue;
4647 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4649 line->data = data;
4650 line->type = type;
4651 for (; line <= entry; line++)
4652 line->dirty = line->cleareol = 1;
4653 return TRUE;
4654 }
4656 if (tree_lineno > view->lineno) {
4657 view->lineno = tree_lineno;
4658 tree_lineno = 0;
4659 }
4661 return TRUE;
4662 }
4664 static bool
4665 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4666 {
4667 struct tree_entry *entry = line->data;
4669 if (line->type == LINE_TREE_HEAD) {
4670 if (draw_text(view, line->type, "Directory path /"))
4671 return TRUE;
4672 } else {
4673 if (draw_mode(view, entry->mode))
4674 return TRUE;
4676 if (opt_author && draw_author(view, entry->author))
4677 return TRUE;
4679 if (opt_date && draw_date(view, &entry->time))
4680 return TRUE;
4681 }
4683 draw_text(view, line->type, entry->name);
4684 return TRUE;
4685 }
4687 static void
4688 open_blob_editor(const char *id)
4689 {
4690 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4691 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4692 int fd = mkstemp(file);
4694 if (fd == -1)
4695 report("Failed to create temporary file");
4696 else if (!io_run_append(blob_argv, fd))
4697 report("Failed to save blob data to file");
4698 else
4699 open_editor(file);
4700 if (fd != -1)
4701 unlink(file);
4702 }
4704 static enum request
4705 tree_request(struct view *view, enum request request, struct line *line)
4706 {
4707 enum open_flags flags;
4708 struct tree_entry *entry = line->data;
4710 switch (request) {
4711 case REQ_VIEW_BLAME:
4712 if (line->type != LINE_TREE_FILE) {
4713 report("Blame only supported for files");
4714 return REQ_NONE;
4715 }
4717 string_copy(opt_ref, view->vid);
4718 return request;
4720 case REQ_EDIT:
4721 if (line->type != LINE_TREE_FILE) {
4722 report("Edit only supported for files");
4723 } else if (!is_head_commit(view->vid)) {
4724 open_blob_editor(entry->id);
4725 } else {
4726 open_editor(opt_file);
4727 }
4728 return REQ_NONE;
4730 case REQ_TOGGLE_SORT_FIELD:
4731 case REQ_TOGGLE_SORT_ORDER:
4732 sort_view(view, request, &tree_sort_state, tree_compare);
4733 return REQ_NONE;
4735 case REQ_PARENT:
4736 if (!*opt_path) {
4737 /* quit view if at top of tree */
4738 return REQ_VIEW_CLOSE;
4739 }
4740 /* fake 'cd ..' */
4741 line = &view->line[1];
4742 break;
4744 case REQ_ENTER:
4745 break;
4747 default:
4748 return request;
4749 }
4751 /* Cleanup the stack if the tree view is at a different tree. */
4752 while (!*opt_path && tree_stack)
4753 pop_tree_stack_entry();
4755 switch (line->type) {
4756 case LINE_TREE_DIR:
4757 /* Depending on whether it is a subdirectory or parent link
4758 * mangle the path buffer. */
4759 if (line == &view->line[1] && *opt_path) {
4760 pop_tree_stack_entry();
4762 } else {
4763 const char *basename = tree_path(line);
4765 push_tree_stack_entry(basename, view->lineno);
4766 }
4768 /* Trees and subtrees share the same ID, so they are not not
4769 * unique like blobs. */
4770 flags = OPEN_RELOAD;
4771 request = REQ_VIEW_TREE;
4772 break;
4774 case LINE_TREE_FILE:
4775 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4776 request = REQ_VIEW_BLOB;
4777 break;
4779 default:
4780 return REQ_NONE;
4781 }
4783 open_view(view, request, flags);
4784 if (request == REQ_VIEW_TREE)
4785 view->lineno = tree_lineno;
4787 return REQ_NONE;
4788 }
4790 static bool
4791 tree_grep(struct view *view, struct line *line)
4792 {
4793 struct tree_entry *entry = line->data;
4794 const char *text[] = {
4795 entry->name,
4796 opt_author ? entry->author : "",
4797 mkdate(&entry->time, opt_date),
4798 NULL
4799 };
4801 return grep_text(view, text);
4802 }
4804 static void
4805 tree_select(struct view *view, struct line *line)
4806 {
4807 struct tree_entry *entry = line->data;
4809 if (line->type == LINE_TREE_FILE) {
4810 string_copy_rev(ref_blob, entry->id);
4811 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4813 } else if (line->type != LINE_TREE_DIR) {
4814 return;
4815 }
4817 string_copy_rev(view->ref, entry->id);
4818 }
4820 static bool
4821 tree_prepare(struct view *view)
4822 {
4823 if (view->lines == 0 && opt_prefix[0]) {
4824 char *pos = opt_prefix;
4826 while (pos && *pos) {
4827 char *end = strchr(pos, '/');
4829 if (end)
4830 *end = 0;
4831 push_tree_stack_entry(pos, 0);
4832 pos = end;
4833 if (end) {
4834 *end = '/';
4835 pos++;
4836 }
4837 }
4839 } else if (strcmp(view->vid, view->id)) {
4840 opt_path[0] = 0;
4841 }
4843 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4844 }
4846 static const char *tree_argv[SIZEOF_ARG] = {
4847 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4848 };
4850 static struct view_ops tree_ops = {
4851 "file",
4852 tree_argv,
4853 NULL,
4854 tree_read,
4855 tree_draw,
4856 tree_request,
4857 tree_grep,
4858 tree_select,
4859 tree_prepare,
4860 };
4862 static bool
4863 blob_read(struct view *view, char *line)
4864 {
4865 if (!line)
4866 return TRUE;
4867 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4868 }
4870 static enum request
4871 blob_request(struct view *view, enum request request, struct line *line)
4872 {
4873 switch (request) {
4874 case REQ_EDIT:
4875 open_blob_editor(view->vid);
4876 return REQ_NONE;
4877 default:
4878 return pager_request(view, request, line);
4879 }
4880 }
4882 static const char *blob_argv[SIZEOF_ARG] = {
4883 "git", "cat-file", "blob", "%(blob)", NULL
4884 };
4886 static struct view_ops blob_ops = {
4887 "line",
4888 blob_argv,
4889 NULL,
4890 blob_read,
4891 pager_draw,
4892 blob_request,
4893 pager_grep,
4894 pager_select,
4895 };
4897 /*
4898 * Blame backend
4899 *
4900 * Loading the blame view is a two phase job:
4901 *
4902 * 1. File content is read either using opt_file from the
4903 * filesystem or using git-cat-file.
4904 * 2. Then blame information is incrementally added by
4905 * reading output from git-blame.
4906 */
4908 struct blame_commit {
4909 char id[SIZEOF_REV]; /* SHA1 ID. */
4910 char title[128]; /* First line of the commit message. */
4911 const char *author; /* Author of the commit. */
4912 struct time time; /* Date from the author ident. */
4913 char filename[128]; /* Name of file. */
4914 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4915 char parent_filename[128]; /* Parent/previous name of file. */
4916 };
4918 struct blame {
4919 struct blame_commit *commit;
4920 unsigned long lineno;
4921 char text[1];
4922 };
4924 static bool
4925 blame_open(struct view *view)
4926 {
4927 char path[SIZEOF_STR];
4928 size_t i;
4930 if (!view->prev && *opt_prefix) {
4931 string_copy(path, opt_file);
4932 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4933 return FALSE;
4934 }
4936 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4937 const char *blame_cat_file_argv[] = {
4938 "git", "cat-file", "blob", path, NULL
4939 };
4941 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4942 !start_update(view, blame_cat_file_argv, opt_cdup))
4943 return FALSE;
4944 }
4946 /* First pass: remove multiple references to the same commit. */
4947 for (i = 0; i < view->lines; i++) {
4948 struct blame *blame = view->line[i].data;
4950 if (blame->commit && blame->commit->id[0])
4951 blame->commit->id[0] = 0;
4952 else
4953 blame->commit = NULL;
4954 }
4956 /* Second pass: free existing references. */
4957 for (i = 0; i < view->lines; i++) {
4958 struct blame *blame = view->line[i].data;
4960 if (blame->commit)
4961 free(blame->commit);
4962 }
4964 setup_update(view, opt_file);
4965 string_format(view->ref, "%s ...", opt_file);
4967 return TRUE;
4968 }
4970 static struct blame_commit *
4971 get_blame_commit(struct view *view, const char *id)
4972 {
4973 size_t i;
4975 for (i = 0; i < view->lines; i++) {
4976 struct blame *blame = view->line[i].data;
4978 if (!blame->commit)
4979 continue;
4981 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4982 return blame->commit;
4983 }
4985 {
4986 struct blame_commit *commit = calloc(1, sizeof(*commit));
4988 if (commit)
4989 string_ncopy(commit->id, id, SIZEOF_REV);
4990 return commit;
4991 }
4992 }
4994 static bool
4995 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4996 {
4997 const char *pos = *posref;
4999 *posref = NULL;
5000 pos = strchr(pos + 1, ' ');
5001 if (!pos || !isdigit(pos[1]))
5002 return FALSE;
5003 *number = atoi(pos + 1);
5004 if (*number < min || *number > max)
5005 return FALSE;
5007 *posref = pos;
5008 return TRUE;
5009 }
5011 static struct blame_commit *
5012 parse_blame_commit(struct view *view, const char *text, int *blamed)
5013 {
5014 struct blame_commit *commit;
5015 struct blame *blame;
5016 const char *pos = text + SIZEOF_REV - 2;
5017 size_t orig_lineno = 0;
5018 size_t lineno;
5019 size_t group;
5021 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5022 return NULL;
5024 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5025 !parse_number(&pos, &lineno, 1, view->lines) ||
5026 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5027 return NULL;
5029 commit = get_blame_commit(view, text);
5030 if (!commit)
5031 return NULL;
5033 *blamed += group;
5034 while (group--) {
5035 struct line *line = &view->line[lineno + group - 1];
5037 blame = line->data;
5038 blame->commit = commit;
5039 blame->lineno = orig_lineno + group - 1;
5040 line->dirty = 1;
5041 }
5043 return commit;
5044 }
5046 static bool
5047 blame_read_file(struct view *view, const char *line, bool *read_file)
5048 {
5049 if (!line) {
5050 const char *blame_argv[] = {
5051 "git", "blame", "--incremental",
5052 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5053 };
5055 if (view->lines == 0 && !view->prev)
5056 die("No blame exist for %s", view->vid);
5058 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5059 report("Failed to load blame data");
5060 return TRUE;
5061 }
5063 *read_file = FALSE;
5064 return FALSE;
5066 } else {
5067 size_t linelen = strlen(line);
5068 struct blame *blame = malloc(sizeof(*blame) + linelen);
5070 if (!blame)
5071 return FALSE;
5073 blame->commit = NULL;
5074 strncpy(blame->text, line, linelen);
5075 blame->text[linelen] = 0;
5076 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5077 }
5078 }
5080 static bool
5081 match_blame_header(const char *name, char **line)
5082 {
5083 size_t namelen = strlen(name);
5084 bool matched = !strncmp(name, *line, namelen);
5086 if (matched)
5087 *line += namelen;
5089 return matched;
5090 }
5092 static bool
5093 blame_read(struct view *view, char *line)
5094 {
5095 static struct blame_commit *commit = NULL;
5096 static int blamed = 0;
5097 static bool read_file = TRUE;
5099 if (read_file)
5100 return blame_read_file(view, line, &read_file);
5102 if (!line) {
5103 /* Reset all! */
5104 commit = NULL;
5105 blamed = 0;
5106 read_file = TRUE;
5107 string_format(view->ref, "%s", view->vid);
5108 if (view_is_displayed(view)) {
5109 update_view_title(view);
5110 redraw_view_from(view, 0);
5111 }
5112 return TRUE;
5113 }
5115 if (!commit) {
5116 commit = parse_blame_commit(view, line, &blamed);
5117 string_format(view->ref, "%s %2d%%", view->vid,
5118 view->lines ? blamed * 100 / view->lines : 0);
5120 } else if (match_blame_header("author ", &line)) {
5121 commit->author = get_author(line);
5123 } else if (match_blame_header("author-time ", &line)) {
5124 parse_timesec(&commit->time, line);
5126 } else if (match_blame_header("author-tz ", &line)) {
5127 parse_timezone(&commit->time, line);
5129 } else if (match_blame_header("summary ", &line)) {
5130 string_ncopy(commit->title, line, strlen(line));
5132 } else if (match_blame_header("previous ", &line)) {
5133 if (strlen(line) <= SIZEOF_REV)
5134 return FALSE;
5135 string_copy_rev(commit->parent_id, line);
5136 line += SIZEOF_REV;
5137 string_ncopy(commit->parent_filename, line, strlen(line));
5139 } else if (match_blame_header("filename ", &line)) {
5140 string_ncopy(commit->filename, line, strlen(line));
5141 commit = NULL;
5142 }
5144 return TRUE;
5145 }
5147 static bool
5148 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5149 {
5150 struct blame *blame = line->data;
5151 struct time *time = NULL;
5152 const char *id = NULL, *author = NULL;
5154 if (blame->commit && *blame->commit->filename) {
5155 id = blame->commit->id;
5156 author = blame->commit->author;
5157 time = &blame->commit->time;
5158 }
5160 if (opt_date && draw_date(view, time))
5161 return TRUE;
5163 if (opt_author && draw_author(view, author))
5164 return TRUE;
5166 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5167 return TRUE;
5169 if (draw_lineno(view, lineno))
5170 return TRUE;
5172 draw_text(view, LINE_DEFAULT, blame->text);
5173 return TRUE;
5174 }
5176 static bool
5177 check_blame_commit(struct blame *blame, bool check_null_id)
5178 {
5179 if (!blame->commit)
5180 report("Commit data not loaded yet");
5181 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5182 report("No commit exist for the selected line");
5183 else
5184 return TRUE;
5185 return FALSE;
5186 }
5188 static void
5189 setup_blame_parent_line(struct view *view, struct blame *blame)
5190 {
5191 char from[SIZEOF_REF + SIZEOF_STR];
5192 char to[SIZEOF_REF + SIZEOF_STR];
5193 const char *diff_tree_argv[] = {
5194 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5195 "-U0", from, to, "--", NULL
5196 };
5197 struct io io;
5198 int parent_lineno = -1;
5199 int blamed_lineno = -1;
5200 char *line;
5202 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5203 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5204 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5205 return;
5207 while ((line = io_get(&io, '\n', TRUE))) {
5208 if (*line == '@') {
5209 char *pos = strchr(line, '+');
5211 parent_lineno = atoi(line + 4);
5212 if (pos)
5213 blamed_lineno = atoi(pos + 1);
5215 } else if (*line == '+' && parent_lineno != -1) {
5216 if (blame->lineno == blamed_lineno - 1 &&
5217 !strcmp(blame->text, line + 1)) {
5218 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5219 break;
5220 }
5221 blamed_lineno++;
5222 }
5223 }
5225 io_done(&io);
5226 }
5228 static enum request
5229 blame_request(struct view *view, enum request request, struct line *line)
5230 {
5231 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5232 struct blame *blame = line->data;
5234 switch (request) {
5235 case REQ_VIEW_BLAME:
5236 if (check_blame_commit(blame, TRUE)) {
5237 string_copy(opt_ref, blame->commit->id);
5238 string_copy(opt_file, blame->commit->filename);
5239 if (blame->lineno)
5240 view->lineno = blame->lineno;
5241 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5242 }
5243 break;
5245 case REQ_PARENT:
5246 if (!check_blame_commit(blame, TRUE))
5247 break;
5248 if (!*blame->commit->parent_id) {
5249 report("The selected commit has no parents");
5250 } else {
5251 string_copy_rev(opt_ref, blame->commit->parent_id);
5252 string_copy(opt_file, blame->commit->parent_filename);
5253 setup_blame_parent_line(view, blame);
5254 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5255 }
5256 break;
5258 case REQ_ENTER:
5259 if (!check_blame_commit(blame, FALSE))
5260 break;
5262 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5263 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5264 break;
5266 if (!strcmp(blame->commit->id, NULL_ID)) {
5267 struct view *diff = VIEW(REQ_VIEW_DIFF);
5268 const char *diff_index_argv[] = {
5269 "git", "diff-index", "--root", "--patch-with-stat",
5270 "-C", "-M", "HEAD", "--", view->vid, NULL
5271 };
5273 if (!*blame->commit->parent_id) {
5274 diff_index_argv[1] = "diff";
5275 diff_index_argv[2] = "--no-color";
5276 diff_index_argv[6] = "--";
5277 diff_index_argv[7] = "/dev/null";
5278 }
5280 if (!prepare_update(diff, diff_index_argv, NULL)) {
5281 report("Failed to allocate diff command");
5282 break;
5283 }
5284 flags |= OPEN_PREPARED;
5285 }
5287 open_view(view, REQ_VIEW_DIFF, flags);
5288 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5289 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5290 break;
5292 default:
5293 return request;
5294 }
5296 return REQ_NONE;
5297 }
5299 static bool
5300 blame_grep(struct view *view, struct line *line)
5301 {
5302 struct blame *blame = line->data;
5303 struct blame_commit *commit = blame->commit;
5304 const char *text[] = {
5305 blame->text,
5306 commit ? commit->title : "",
5307 commit ? commit->id : "",
5308 commit && opt_author ? commit->author : "",
5309 commit ? mkdate(&commit->time, opt_date) : "",
5310 NULL
5311 };
5313 return grep_text(view, text);
5314 }
5316 static void
5317 blame_select(struct view *view, struct line *line)
5318 {
5319 struct blame *blame = line->data;
5320 struct blame_commit *commit = blame->commit;
5322 if (!commit)
5323 return;
5325 if (!strcmp(commit->id, NULL_ID))
5326 string_ncopy(ref_commit, "HEAD", 4);
5327 else
5328 string_copy_rev(ref_commit, commit->id);
5329 }
5331 static struct view_ops blame_ops = {
5332 "line",
5333 NULL,
5334 blame_open,
5335 blame_read,
5336 blame_draw,
5337 blame_request,
5338 blame_grep,
5339 blame_select,
5340 };
5342 /*
5343 * Branch backend
5344 */
5346 struct branch {
5347 const char *author; /* Author of the last commit. */
5348 struct time time; /* Date of the last activity. */
5349 const struct ref *ref; /* Name and commit ID information. */
5350 };
5352 static const struct ref branch_all;
5354 static const enum sort_field branch_sort_fields[] = {
5355 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5356 };
5357 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5359 static int
5360 branch_compare(const void *l1, const void *l2)
5361 {
5362 const struct branch *branch1 = ((const struct line *) l1)->data;
5363 const struct branch *branch2 = ((const struct line *) l2)->data;
5365 switch (get_sort_field(branch_sort_state)) {
5366 case ORDERBY_DATE:
5367 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5369 case ORDERBY_AUTHOR:
5370 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5372 case ORDERBY_NAME:
5373 default:
5374 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5375 }
5376 }
5378 static bool
5379 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5380 {
5381 struct branch *branch = line->data;
5382 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5384 if (opt_date && draw_date(view, &branch->time))
5385 return TRUE;
5387 if (opt_author && draw_author(view, branch->author))
5388 return TRUE;
5390 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name);
5391 return TRUE;
5392 }
5394 static enum request
5395 branch_request(struct view *view, enum request request, struct line *line)
5396 {
5397 struct branch *branch = line->data;
5399 switch (request) {
5400 case REQ_REFRESH:
5401 load_refs();
5402 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5403 return REQ_NONE;
5405 case REQ_TOGGLE_SORT_FIELD:
5406 case REQ_TOGGLE_SORT_ORDER:
5407 sort_view(view, request, &branch_sort_state, branch_compare);
5408 return REQ_NONE;
5410 case REQ_ENTER:
5411 {
5412 const struct ref *ref = branch->ref;
5413 const char *all_branches_argv[] = {
5414 "git", "log", "--no-color", "--pretty=raw", "--parents",
5415 "--topo-order",
5416 ref == &branch_all ? "--all" : ref->name, NULL
5417 };
5418 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5420 if (!prepare_update(main_view, all_branches_argv, NULL))
5421 report("Failed to load view of all branches");
5422 else
5423 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5424 return REQ_NONE;
5425 }
5426 default:
5427 return request;
5428 }
5429 }
5431 static bool
5432 branch_read(struct view *view, char *line)
5433 {
5434 static char id[SIZEOF_REV];
5435 struct branch *reference;
5436 size_t i;
5438 if (!line)
5439 return TRUE;
5441 switch (get_line_type(line)) {
5442 case LINE_COMMIT:
5443 string_copy_rev(id, line + STRING_SIZE("commit "));
5444 return TRUE;
5446 case LINE_AUTHOR:
5447 for (i = 0, reference = NULL; i < view->lines; i++) {
5448 struct branch *branch = view->line[i].data;
5450 if (strcmp(branch->ref->id, id))
5451 continue;
5453 view->line[i].dirty = TRUE;
5454 if (reference) {
5455 branch->author = reference->author;
5456 branch->time = reference->time;
5457 continue;
5458 }
5460 parse_author_line(line + STRING_SIZE("author "),
5461 &branch->author, &branch->time);
5462 reference = branch;
5463 }
5464 return TRUE;
5466 default:
5467 return TRUE;
5468 }
5470 }
5472 static bool
5473 branch_open_visitor(void *data, const struct ref *ref)
5474 {
5475 struct view *view = data;
5476 struct branch *branch;
5478 if (ref->tag || ref->ltag || ref->remote)
5479 return TRUE;
5481 branch = calloc(1, sizeof(*branch));
5482 if (!branch)
5483 return FALSE;
5485 branch->ref = ref;
5486 return !!add_line_data(view, branch, LINE_DEFAULT);
5487 }
5489 static bool
5490 branch_open(struct view *view)
5491 {
5492 const char *branch_log[] = {
5493 "git", "log", "--no-color", "--pretty=raw",
5494 "--simplify-by-decoration", "--all", NULL
5495 };
5497 if (!start_update(view, branch_log, NULL)) {
5498 report("Failed to load branch data");
5499 return TRUE;
5500 }
5502 setup_update(view, view->id);
5503 branch_open_visitor(view, &branch_all);
5504 foreach_ref(branch_open_visitor, view);
5505 view->p_restore = TRUE;
5507 return TRUE;
5508 }
5510 static bool
5511 branch_grep(struct view *view, struct line *line)
5512 {
5513 struct branch *branch = line->data;
5514 const char *text[] = {
5515 branch->ref->name,
5516 branch->author,
5517 NULL
5518 };
5520 return grep_text(view, text);
5521 }
5523 static void
5524 branch_select(struct view *view, struct line *line)
5525 {
5526 struct branch *branch = line->data;
5528 string_copy_rev(view->ref, branch->ref->id);
5529 string_copy_rev(ref_commit, branch->ref->id);
5530 string_copy_rev(ref_head, branch->ref->id);
5531 string_copy_rev(ref_branch, branch->ref->name);
5532 }
5534 static struct view_ops branch_ops = {
5535 "branch",
5536 NULL,
5537 branch_open,
5538 branch_read,
5539 branch_draw,
5540 branch_request,
5541 branch_grep,
5542 branch_select,
5543 };
5545 /*
5546 * Status backend
5547 */
5549 struct status {
5550 char status;
5551 struct {
5552 mode_t mode;
5553 char rev[SIZEOF_REV];
5554 char name[SIZEOF_STR];
5555 } old;
5556 struct {
5557 mode_t mode;
5558 char rev[SIZEOF_REV];
5559 char name[SIZEOF_STR];
5560 } new;
5561 };
5563 static char status_onbranch[SIZEOF_STR];
5564 static struct status stage_status;
5565 static enum line_type stage_line_type;
5566 static size_t stage_chunks;
5567 static int *stage_chunk;
5569 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5571 /* This should work even for the "On branch" line. */
5572 static inline bool
5573 status_has_none(struct view *view, struct line *line)
5574 {
5575 return line < view->line + view->lines && !line[1].data;
5576 }
5578 /* Get fields from the diff line:
5579 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5580 */
5581 static inline bool
5582 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5583 {
5584 const char *old_mode = buf + 1;
5585 const char *new_mode = buf + 8;
5586 const char *old_rev = buf + 15;
5587 const char *new_rev = buf + 56;
5588 const char *status = buf + 97;
5590 if (bufsize < 98 ||
5591 old_mode[-1] != ':' ||
5592 new_mode[-1] != ' ' ||
5593 old_rev[-1] != ' ' ||
5594 new_rev[-1] != ' ' ||
5595 status[-1] != ' ')
5596 return FALSE;
5598 file->status = *status;
5600 string_copy_rev(file->old.rev, old_rev);
5601 string_copy_rev(file->new.rev, new_rev);
5603 file->old.mode = strtoul(old_mode, NULL, 8);
5604 file->new.mode = strtoul(new_mode, NULL, 8);
5606 file->old.name[0] = file->new.name[0] = 0;
5608 return TRUE;
5609 }
5611 static bool
5612 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5613 {
5614 struct status *unmerged = NULL;
5615 char *buf;
5616 struct io io;
5618 if (!io_run(&io, IO_RD, opt_cdup, argv))
5619 return FALSE;
5621 add_line_data(view, NULL, type);
5623 while ((buf = io_get(&io, 0, TRUE))) {
5624 struct status *file = unmerged;
5626 if (!file) {
5627 file = calloc(1, sizeof(*file));
5628 if (!file || !add_line_data(view, file, type))
5629 goto error_out;
5630 }
5632 /* Parse diff info part. */
5633 if (status) {
5634 file->status = status;
5635 if (status == 'A')
5636 string_copy(file->old.rev, NULL_ID);
5638 } else if (!file->status || file == unmerged) {
5639 if (!status_get_diff(file, buf, strlen(buf)))
5640 goto error_out;
5642 buf = io_get(&io, 0, TRUE);
5643 if (!buf)
5644 break;
5646 /* Collapse all modified entries that follow an
5647 * associated unmerged entry. */
5648 if (unmerged == file) {
5649 unmerged->status = 'U';
5650 unmerged = NULL;
5651 } else if (file->status == 'U') {
5652 unmerged = file;
5653 }
5654 }
5656 /* Grab the old name for rename/copy. */
5657 if (!*file->old.name &&
5658 (file->status == 'R' || file->status == 'C')) {
5659 string_ncopy(file->old.name, buf, strlen(buf));
5661 buf = io_get(&io, 0, TRUE);
5662 if (!buf)
5663 break;
5664 }
5666 /* git-ls-files just delivers a NUL separated list of
5667 * file names similar to the second half of the
5668 * git-diff-* output. */
5669 string_ncopy(file->new.name, buf, strlen(buf));
5670 if (!*file->old.name)
5671 string_copy(file->old.name, file->new.name);
5672 file = NULL;
5673 }
5675 if (io_error(&io)) {
5676 error_out:
5677 io_done(&io);
5678 return FALSE;
5679 }
5681 if (!view->line[view->lines - 1].data)
5682 add_line_data(view, NULL, LINE_STAT_NONE);
5684 io_done(&io);
5685 return TRUE;
5686 }
5688 /* Don't show unmerged entries in the staged section. */
5689 static const char *status_diff_index_argv[] = {
5690 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5691 "--cached", "-M", "HEAD", NULL
5692 };
5694 static const char *status_diff_files_argv[] = {
5695 "git", "diff-files", "-z", NULL
5696 };
5698 static const char *status_list_other_argv[] = {
5699 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5700 };
5702 static const char *status_list_no_head_argv[] = {
5703 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5704 };
5706 static const char *update_index_argv[] = {
5707 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5708 };
5710 /* Restore the previous line number to stay in the context or select a
5711 * line with something that can be updated. */
5712 static void
5713 status_restore(struct view *view)
5714 {
5715 if (view->p_lineno >= view->lines)
5716 view->p_lineno = view->lines - 1;
5717 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5718 view->p_lineno++;
5719 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5720 view->p_lineno--;
5722 /* If the above fails, always skip the "On branch" line. */
5723 if (view->p_lineno < view->lines)
5724 view->lineno = view->p_lineno;
5725 else
5726 view->lineno = 1;
5728 if (view->lineno < view->offset)
5729 view->offset = view->lineno;
5730 else if (view->offset + view->height <= view->lineno)
5731 view->offset = view->lineno - view->height + 1;
5733 view->p_restore = FALSE;
5734 }
5736 static void
5737 status_update_onbranch(void)
5738 {
5739 static const char *paths[][2] = {
5740 { "rebase-apply/rebasing", "Rebasing" },
5741 { "rebase-apply/applying", "Applying mailbox" },
5742 { "rebase-apply/", "Rebasing mailbox" },
5743 { "rebase-merge/interactive", "Interactive rebase" },
5744 { "rebase-merge/", "Rebase merge" },
5745 { "MERGE_HEAD", "Merging" },
5746 { "BISECT_LOG", "Bisecting" },
5747 { "HEAD", "On branch" },
5748 };
5749 char buf[SIZEOF_STR];
5750 struct stat stat;
5751 int i;
5753 if (is_initial_commit()) {
5754 string_copy(status_onbranch, "Initial commit");
5755 return;
5756 }
5758 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5759 char *head = opt_head;
5761 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5762 lstat(buf, &stat) < 0)
5763 continue;
5765 if (!*opt_head) {
5766 struct io io;
5768 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5769 io_read_buf(&io, buf, sizeof(buf))) {
5770 head = buf;
5771 if (!prefixcmp(head, "refs/heads/"))
5772 head += STRING_SIZE("refs/heads/");
5773 }
5774 }
5776 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5777 string_copy(status_onbranch, opt_head);
5778 return;
5779 }
5781 string_copy(status_onbranch, "Not currently on any branch");
5782 }
5784 /* First parse staged info using git-diff-index(1), then parse unstaged
5785 * info using git-diff-files(1), and finally untracked files using
5786 * git-ls-files(1). */
5787 static bool
5788 status_open(struct view *view)
5789 {
5790 reset_view(view);
5792 add_line_data(view, NULL, LINE_STAT_HEAD);
5793 status_update_onbranch();
5795 io_run_bg(update_index_argv);
5797 if (is_initial_commit()) {
5798 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5799 return FALSE;
5800 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5801 return FALSE;
5802 }
5804 if (!opt_untracked_dirs_content)
5805 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5807 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5808 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5809 return FALSE;
5811 /* Restore the exact position or use the specialized restore
5812 * mode? */
5813 if (!view->p_restore)
5814 status_restore(view);
5815 return TRUE;
5816 }
5818 static bool
5819 status_draw(struct view *view, struct line *line, unsigned int lineno)
5820 {
5821 struct status *status = line->data;
5822 enum line_type type;
5823 const char *text;
5825 if (!status) {
5826 switch (line->type) {
5827 case LINE_STAT_STAGED:
5828 type = LINE_STAT_SECTION;
5829 text = "Changes to be committed:";
5830 break;
5832 case LINE_STAT_UNSTAGED:
5833 type = LINE_STAT_SECTION;
5834 text = "Changed but not updated:";
5835 break;
5837 case LINE_STAT_UNTRACKED:
5838 type = LINE_STAT_SECTION;
5839 text = "Untracked files:";
5840 break;
5842 case LINE_STAT_NONE:
5843 type = LINE_DEFAULT;
5844 text = " (no files)";
5845 break;
5847 case LINE_STAT_HEAD:
5848 type = LINE_STAT_HEAD;
5849 text = status_onbranch;
5850 break;
5852 default:
5853 return FALSE;
5854 }
5855 } else {
5856 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5858 buf[0] = status->status;
5859 if (draw_text(view, line->type, buf))
5860 return TRUE;
5861 type = LINE_DEFAULT;
5862 text = status->new.name;
5863 }
5865 draw_text(view, type, text);
5866 return TRUE;
5867 }
5869 static enum request
5870 status_load_error(struct view *view, struct view *stage, const char *path)
5871 {
5872 if (displayed_views() == 2 || display[current_view] != view)
5873 maximize_view(view);
5874 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5875 return REQ_NONE;
5876 }
5878 static enum request
5879 status_enter(struct view *view, struct line *line)
5880 {
5881 struct status *status = line->data;
5882 const char *oldpath = status ? status->old.name : NULL;
5883 /* Diffs for unmerged entries are empty when passing the new
5884 * path, so leave it empty. */
5885 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5886 const char *info;
5887 enum open_flags split;
5888 struct view *stage = VIEW(REQ_VIEW_STAGE);
5890 if (line->type == LINE_STAT_NONE ||
5891 (!status && line[1].type == LINE_STAT_NONE)) {
5892 report("No file to diff");
5893 return REQ_NONE;
5894 }
5896 switch (line->type) {
5897 case LINE_STAT_STAGED:
5898 if (is_initial_commit()) {
5899 const char *no_head_diff_argv[] = {
5900 "git", "diff", "--no-color", "--patch-with-stat",
5901 "--", "/dev/null", newpath, NULL
5902 };
5904 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5905 return status_load_error(view, stage, newpath);
5906 } else {
5907 const char *index_show_argv[] = {
5908 "git", "diff-index", "--root", "--patch-with-stat",
5909 "-C", "-M", "--cached", "HEAD", "--",
5910 oldpath, newpath, NULL
5911 };
5913 if (!prepare_update(stage, index_show_argv, opt_cdup))
5914 return status_load_error(view, stage, newpath);
5915 }
5917 if (status)
5918 info = "Staged changes to %s";
5919 else
5920 info = "Staged changes";
5921 break;
5923 case LINE_STAT_UNSTAGED:
5924 {
5925 const char *files_show_argv[] = {
5926 "git", "diff-files", "--root", "--patch-with-stat",
5927 "-C", "-M", "--", oldpath, newpath, NULL
5928 };
5930 if (!prepare_update(stage, files_show_argv, opt_cdup))
5931 return status_load_error(view, stage, newpath);
5932 if (status)
5933 info = "Unstaged changes to %s";
5934 else
5935 info = "Unstaged changes";
5936 break;
5937 }
5938 case LINE_STAT_UNTRACKED:
5939 if (!newpath) {
5940 report("No file to show");
5941 return REQ_NONE;
5942 }
5944 if (!suffixcmp(status->new.name, -1, "/")) {
5945 report("Cannot display a directory");
5946 return REQ_NONE;
5947 }
5949 if (!prepare_update_file(stage, newpath))
5950 return status_load_error(view, stage, newpath);
5951 info = "Untracked file %s";
5952 break;
5954 case LINE_STAT_HEAD:
5955 return REQ_NONE;
5957 default:
5958 die("line type %d not handled in switch", line->type);
5959 }
5961 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5962 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5963 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5964 if (status) {
5965 stage_status = *status;
5966 } else {
5967 memset(&stage_status, 0, sizeof(stage_status));
5968 }
5970 stage_line_type = line->type;
5971 stage_chunks = 0;
5972 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5973 }
5975 return REQ_NONE;
5976 }
5978 static bool
5979 status_exists(struct status *status, enum line_type type)
5980 {
5981 struct view *view = VIEW(REQ_VIEW_STATUS);
5982 unsigned long lineno;
5984 for (lineno = 0; lineno < view->lines; lineno++) {
5985 struct line *line = &view->line[lineno];
5986 struct status *pos = line->data;
5988 if (line->type != type)
5989 continue;
5990 if (!pos && (!status || !status->status) && line[1].data) {
5991 select_view_line(view, lineno);
5992 return TRUE;
5993 }
5994 if (pos && !strcmp(status->new.name, pos->new.name)) {
5995 select_view_line(view, lineno);
5996 return TRUE;
5997 }
5998 }
6000 return FALSE;
6001 }
6004 static bool
6005 status_update_prepare(struct io *io, enum line_type type)
6006 {
6007 const char *staged_argv[] = {
6008 "git", "update-index", "-z", "--index-info", NULL
6009 };
6010 const char *others_argv[] = {
6011 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6012 };
6014 switch (type) {
6015 case LINE_STAT_STAGED:
6016 return io_run(io, IO_WR, opt_cdup, staged_argv);
6018 case LINE_STAT_UNSTAGED:
6019 case LINE_STAT_UNTRACKED:
6020 return io_run(io, IO_WR, opt_cdup, others_argv);
6022 default:
6023 die("line type %d not handled in switch", type);
6024 return FALSE;
6025 }
6026 }
6028 static bool
6029 status_update_write(struct io *io, struct status *status, enum line_type type)
6030 {
6031 char buf[SIZEOF_STR];
6032 size_t bufsize = 0;
6034 switch (type) {
6035 case LINE_STAT_STAGED:
6036 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6037 status->old.mode,
6038 status->old.rev,
6039 status->old.name, 0))
6040 return FALSE;
6041 break;
6043 case LINE_STAT_UNSTAGED:
6044 case LINE_STAT_UNTRACKED:
6045 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6046 return FALSE;
6047 break;
6049 default:
6050 die("line type %d not handled in switch", type);
6051 }
6053 return io_write(io, buf, bufsize);
6054 }
6056 static bool
6057 status_update_file(struct status *status, enum line_type type)
6058 {
6059 struct io io;
6060 bool result;
6062 if (!status_update_prepare(&io, type))
6063 return FALSE;
6065 result = status_update_write(&io, status, type);
6066 return io_done(&io) && result;
6067 }
6069 static bool
6070 status_update_files(struct view *view, struct line *line)
6071 {
6072 char buf[sizeof(view->ref)];
6073 struct io io;
6074 bool result = TRUE;
6075 struct line *pos = view->line + view->lines;
6076 int files = 0;
6077 int file, done;
6078 int cursor_y = -1, cursor_x = -1;
6080 if (!status_update_prepare(&io, line->type))
6081 return FALSE;
6083 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6084 files++;
6086 string_copy(buf, view->ref);
6087 getsyx(cursor_y, cursor_x);
6088 for (file = 0, done = 5; result && file < files; line++, file++) {
6089 int almost_done = file * 100 / files;
6091 if (almost_done > done) {
6092 done = almost_done;
6093 string_format(view->ref, "updating file %u of %u (%d%% done)",
6094 file, files, done);
6095 update_view_title(view);
6096 setsyx(cursor_y, cursor_x);
6097 doupdate();
6098 }
6099 result = status_update_write(&io, line->data, line->type);
6100 }
6101 string_copy(view->ref, buf);
6103 return io_done(&io) && result;
6104 }
6106 static bool
6107 status_update(struct view *view)
6108 {
6109 struct line *line = &view->line[view->lineno];
6111 assert(view->lines);
6113 if (!line->data) {
6114 /* This should work even for the "On branch" line. */
6115 if (line < view->line + view->lines && !line[1].data) {
6116 report("Nothing to update");
6117 return FALSE;
6118 }
6120 if (!status_update_files(view, line + 1)) {
6121 report("Failed to update file status");
6122 return FALSE;
6123 }
6125 } else if (!status_update_file(line->data, line->type)) {
6126 report("Failed to update file status");
6127 return FALSE;
6128 }
6130 return TRUE;
6131 }
6133 static bool
6134 status_revert(struct status *status, enum line_type type, bool has_none)
6135 {
6136 if (!status || type != LINE_STAT_UNSTAGED) {
6137 if (type == LINE_STAT_STAGED) {
6138 report("Cannot revert changes to staged files");
6139 } else if (type == LINE_STAT_UNTRACKED) {
6140 report("Cannot revert changes to untracked files");
6141 } else if (has_none) {
6142 report("Nothing to revert");
6143 } else {
6144 report("Cannot revert changes to multiple files");
6145 }
6147 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6148 char mode[10] = "100644";
6149 const char *reset_argv[] = {
6150 "git", "update-index", "--cacheinfo", mode,
6151 status->old.rev, status->old.name, NULL
6152 };
6153 const char *checkout_argv[] = {
6154 "git", "checkout", "--", status->old.name, NULL
6155 };
6157 if (status->status == 'U') {
6158 string_format(mode, "%5o", status->old.mode);
6160 if (status->old.mode == 0 && status->new.mode == 0) {
6161 reset_argv[2] = "--force-remove";
6162 reset_argv[3] = status->old.name;
6163 reset_argv[4] = NULL;
6164 }
6166 if (!io_run_fg(reset_argv, opt_cdup))
6167 return FALSE;
6168 if (status->old.mode == 0 && status->new.mode == 0)
6169 return TRUE;
6170 }
6172 return io_run_fg(checkout_argv, opt_cdup);
6173 }
6175 return FALSE;
6176 }
6178 static enum request
6179 status_request(struct view *view, enum request request, struct line *line)
6180 {
6181 struct status *status = line->data;
6183 switch (request) {
6184 case REQ_STATUS_UPDATE:
6185 if (!status_update(view))
6186 return REQ_NONE;
6187 break;
6189 case REQ_STATUS_REVERT:
6190 if (!status_revert(status, line->type, status_has_none(view, line)))
6191 return REQ_NONE;
6192 break;
6194 case REQ_STATUS_MERGE:
6195 if (!status || status->status != 'U') {
6196 report("Merging only possible for files with unmerged status ('U').");
6197 return REQ_NONE;
6198 }
6199 open_mergetool(status->new.name);
6200 break;
6202 case REQ_EDIT:
6203 if (!status)
6204 return request;
6205 if (status->status == 'D') {
6206 report("File has been deleted.");
6207 return REQ_NONE;
6208 }
6210 open_editor(status->new.name);
6211 break;
6213 case REQ_VIEW_BLAME:
6214 if (status)
6215 opt_ref[0] = 0;
6216 return request;
6218 case REQ_ENTER:
6219 /* After returning the status view has been split to
6220 * show the stage view. No further reloading is
6221 * necessary. */
6222 return status_enter(view, line);
6224 case REQ_REFRESH:
6225 /* Simply reload the view. */
6226 break;
6228 default:
6229 return request;
6230 }
6232 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6234 return REQ_NONE;
6235 }
6237 static void
6238 status_select(struct view *view, struct line *line)
6239 {
6240 struct status *status = line->data;
6241 char file[SIZEOF_STR] = "all files";
6242 const char *text;
6243 const char *key;
6245 if (status && !string_format(file, "'%s'", status->new.name))
6246 return;
6248 if (!status && line[1].type == LINE_STAT_NONE)
6249 line++;
6251 switch (line->type) {
6252 case LINE_STAT_STAGED:
6253 text = "Press %s to unstage %s for commit";
6254 break;
6256 case LINE_STAT_UNSTAGED:
6257 text = "Press %s to stage %s for commit";
6258 break;
6260 case LINE_STAT_UNTRACKED:
6261 text = "Press %s to stage %s for addition";
6262 break;
6264 case LINE_STAT_HEAD:
6265 case LINE_STAT_NONE:
6266 text = "Nothing to update";
6267 break;
6269 default:
6270 die("line type %d not handled in switch", line->type);
6271 }
6273 if (status && status->status == 'U') {
6274 text = "Press %s to resolve conflict in %s";
6275 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6277 } else {
6278 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6279 }
6281 string_format(view->ref, text, key, file);
6282 if (status)
6283 string_copy(opt_file, status->new.name);
6284 }
6286 static bool
6287 status_grep(struct view *view, struct line *line)
6288 {
6289 struct status *status = line->data;
6291 if (status) {
6292 const char buf[2] = { status->status, 0 };
6293 const char *text[] = { status->new.name, buf, NULL };
6295 return grep_text(view, text);
6296 }
6298 return FALSE;
6299 }
6301 static struct view_ops status_ops = {
6302 "file",
6303 NULL,
6304 status_open,
6305 NULL,
6306 status_draw,
6307 status_request,
6308 status_grep,
6309 status_select,
6310 };
6313 static bool
6314 stage_diff_write(struct io *io, struct line *line, struct line *end)
6315 {
6316 while (line < end) {
6317 if (!io_write(io, line->data, strlen(line->data)) ||
6318 !io_write(io, "\n", 1))
6319 return FALSE;
6320 line++;
6321 if (line->type == LINE_DIFF_CHUNK ||
6322 line->type == LINE_DIFF_HEADER)
6323 break;
6324 }
6326 return TRUE;
6327 }
6329 static struct line *
6330 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6331 {
6332 for (; view->line < line; line--)
6333 if (line->type == type)
6334 return line;
6336 return NULL;
6337 }
6339 static bool
6340 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6341 {
6342 const char *apply_argv[SIZEOF_ARG] = {
6343 "git", "apply", "--whitespace=nowarn", NULL
6344 };
6345 struct line *diff_hdr;
6346 struct io io;
6347 int argc = 3;
6349 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6350 if (!diff_hdr)
6351 return FALSE;
6353 if (!revert)
6354 apply_argv[argc++] = "--cached";
6355 if (revert || stage_line_type == LINE_STAT_STAGED)
6356 apply_argv[argc++] = "-R";
6357 apply_argv[argc++] = "-";
6358 apply_argv[argc++] = NULL;
6359 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6360 return FALSE;
6362 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6363 !stage_diff_write(&io, chunk, view->line + view->lines))
6364 chunk = NULL;
6366 io_done(&io);
6367 io_run_bg(update_index_argv);
6369 return chunk ? TRUE : FALSE;
6370 }
6372 static bool
6373 stage_update(struct view *view, struct line *line)
6374 {
6375 struct line *chunk = NULL;
6377 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6378 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6380 if (chunk) {
6381 if (!stage_apply_chunk(view, chunk, FALSE)) {
6382 report("Failed to apply chunk");
6383 return FALSE;
6384 }
6386 } else if (!stage_status.status) {
6387 view = VIEW(REQ_VIEW_STATUS);
6389 for (line = view->line; line < view->line + view->lines; line++)
6390 if (line->type == stage_line_type)
6391 break;
6393 if (!status_update_files(view, line + 1)) {
6394 report("Failed to update files");
6395 return FALSE;
6396 }
6398 } else if (!status_update_file(&stage_status, stage_line_type)) {
6399 report("Failed to update file");
6400 return FALSE;
6401 }
6403 return TRUE;
6404 }
6406 static bool
6407 stage_revert(struct view *view, struct line *line)
6408 {
6409 struct line *chunk = NULL;
6411 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6412 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6414 if (chunk) {
6415 if (!prompt_yesno("Are you sure you want to revert changes?"))
6416 return FALSE;
6418 if (!stage_apply_chunk(view, chunk, TRUE)) {
6419 report("Failed to revert chunk");
6420 return FALSE;
6421 }
6422 return TRUE;
6424 } else {
6425 return status_revert(stage_status.status ? &stage_status : NULL,
6426 stage_line_type, FALSE);
6427 }
6428 }
6431 static void
6432 stage_next(struct view *view, struct line *line)
6433 {
6434 int i;
6436 if (!stage_chunks) {
6437 for (line = view->line; line < view->line + view->lines; line++) {
6438 if (line->type != LINE_DIFF_CHUNK)
6439 continue;
6441 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6442 report("Allocation failure");
6443 return;
6444 }
6446 stage_chunk[stage_chunks++] = line - view->line;
6447 }
6448 }
6450 for (i = 0; i < stage_chunks; i++) {
6451 if (stage_chunk[i] > view->lineno) {
6452 do_scroll_view(view, stage_chunk[i] - view->lineno);
6453 report("Chunk %d of %d", i + 1, stage_chunks);
6454 return;
6455 }
6456 }
6458 report("No next chunk found");
6459 }
6461 static enum request
6462 stage_request(struct view *view, enum request request, struct line *line)
6463 {
6464 switch (request) {
6465 case REQ_STATUS_UPDATE:
6466 if (!stage_update(view, line))
6467 return REQ_NONE;
6468 break;
6470 case REQ_STATUS_REVERT:
6471 if (!stage_revert(view, line))
6472 return REQ_NONE;
6473 break;
6475 case REQ_STAGE_NEXT:
6476 if (stage_line_type == LINE_STAT_UNTRACKED) {
6477 report("File is untracked; press %s to add",
6478 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6479 return REQ_NONE;
6480 }
6481 stage_next(view, line);
6482 return REQ_NONE;
6484 case REQ_EDIT:
6485 if (!stage_status.new.name[0])
6486 return request;
6487 if (stage_status.status == 'D') {
6488 report("File has been deleted.");
6489 return REQ_NONE;
6490 }
6492 open_editor(stage_status.new.name);
6493 break;
6495 case REQ_REFRESH:
6496 /* Reload everything ... */
6497 break;
6499 case REQ_VIEW_BLAME:
6500 if (stage_status.new.name[0]) {
6501 string_copy(opt_file, stage_status.new.name);
6502 opt_ref[0] = 0;
6503 }
6504 return request;
6506 case REQ_ENTER:
6507 return pager_request(view, request, line);
6509 default:
6510 return request;
6511 }
6513 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6514 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6516 /* Check whether the staged entry still exists, and close the
6517 * stage view if it doesn't. */
6518 if (!status_exists(&stage_status, stage_line_type)) {
6519 status_restore(VIEW(REQ_VIEW_STATUS));
6520 return REQ_VIEW_CLOSE;
6521 }
6523 if (stage_line_type == LINE_STAT_UNTRACKED) {
6524 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6525 report("Cannot display a directory");
6526 return REQ_NONE;
6527 }
6529 if (!prepare_update_file(view, stage_status.new.name)) {
6530 report("Failed to open file: %s", strerror(errno));
6531 return REQ_NONE;
6532 }
6533 }
6534 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6536 return REQ_NONE;
6537 }
6539 static struct view_ops stage_ops = {
6540 "line",
6541 NULL,
6542 NULL,
6543 pager_read,
6544 pager_draw,
6545 stage_request,
6546 pager_grep,
6547 pager_select,
6548 };
6551 /*
6552 * Revision graph
6553 */
6555 struct commit {
6556 char id[SIZEOF_REV]; /* SHA1 ID. */
6557 char title[128]; /* First line of the commit message. */
6558 const char *author; /* Author of the commit. */
6559 struct time time; /* Date from the author ident. */
6560 struct ref_list *refs; /* Repository references. */
6561 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6562 size_t graph_size; /* The width of the graph array. */
6563 bool has_parents; /* Rewritten --parents seen. */
6564 };
6566 /* Size of rev graph with no "padding" columns */
6567 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6569 struct rev_graph {
6570 struct rev_graph *prev, *next, *parents;
6571 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6572 size_t size;
6573 struct commit *commit;
6574 size_t pos;
6575 unsigned int boundary:1;
6576 };
6578 /* Parents of the commit being visualized. */
6579 static struct rev_graph graph_parents[4];
6581 /* The current stack of revisions on the graph. */
6582 static struct rev_graph graph_stacks[4] = {
6583 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6584 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6585 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6586 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6587 };
6589 static inline bool
6590 graph_parent_is_merge(struct rev_graph *graph)
6591 {
6592 return graph->parents->size > 1;
6593 }
6595 static inline void
6596 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6597 {
6598 struct commit *commit = graph->commit;
6600 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6601 commit->graph[commit->graph_size++] = symbol;
6602 }
6604 static void
6605 clear_rev_graph(struct rev_graph *graph)
6606 {
6607 graph->boundary = 0;
6608 graph->size = graph->pos = 0;
6609 graph->commit = NULL;
6610 memset(graph->parents, 0, sizeof(*graph->parents));
6611 }
6613 static void
6614 done_rev_graph(struct rev_graph *graph)
6615 {
6616 if (graph_parent_is_merge(graph) &&
6617 graph->pos < graph->size - 1 &&
6618 graph->next->size == graph->size + graph->parents->size - 1) {
6619 size_t i = graph->pos + graph->parents->size - 1;
6621 graph->commit->graph_size = i * 2;
6622 while (i < graph->next->size - 1) {
6623 append_to_rev_graph(graph, ' ');
6624 append_to_rev_graph(graph, '\\');
6625 i++;
6626 }
6627 }
6629 clear_rev_graph(graph);
6630 }
6632 static void
6633 push_rev_graph(struct rev_graph *graph, const char *parent)
6634 {
6635 int i;
6637 /* "Collapse" duplicate parents lines.
6638 *
6639 * FIXME: This needs to also update update the drawn graph but
6640 * for now it just serves as a method for pruning graph lines. */
6641 for (i = 0; i < graph->size; i++)
6642 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6643 return;
6645 if (graph->size < SIZEOF_REVITEMS) {
6646 string_copy_rev(graph->rev[graph->size++], parent);
6647 }
6648 }
6650 static chtype
6651 get_rev_graph_symbol(struct rev_graph *graph)
6652 {
6653 chtype symbol;
6655 if (graph->boundary)
6656 symbol = REVGRAPH_BOUND;
6657 else if (graph->parents->size == 0)
6658 symbol = REVGRAPH_INIT;
6659 else if (graph_parent_is_merge(graph))
6660 symbol = REVGRAPH_MERGE;
6661 else if (graph->pos >= graph->size)
6662 symbol = REVGRAPH_BRANCH;
6663 else
6664 symbol = REVGRAPH_COMMIT;
6666 return symbol;
6667 }
6669 static void
6670 draw_rev_graph(struct rev_graph *graph)
6671 {
6672 struct rev_filler {
6673 chtype separator, line;
6674 };
6675 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6676 static struct rev_filler fillers[] = {
6677 { ' ', '|' },
6678 { '`', '.' },
6679 { '\'', ' ' },
6680 { '/', ' ' },
6681 };
6682 chtype symbol = get_rev_graph_symbol(graph);
6683 struct rev_filler *filler;
6684 size_t i;
6686 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6687 filler = &fillers[DEFAULT];
6689 for (i = 0; i < graph->pos; i++) {
6690 append_to_rev_graph(graph, filler->line);
6691 if (graph_parent_is_merge(graph->prev) &&
6692 graph->prev->pos == i)
6693 filler = &fillers[RSHARP];
6695 append_to_rev_graph(graph, filler->separator);
6696 }
6698 /* Place the symbol for this revision. */
6699 append_to_rev_graph(graph, symbol);
6701 if (graph->prev->size > graph->size)
6702 filler = &fillers[RDIAG];
6703 else
6704 filler = &fillers[DEFAULT];
6706 i++;
6708 for (; i < graph->size; i++) {
6709 append_to_rev_graph(graph, filler->separator);
6710 append_to_rev_graph(graph, filler->line);
6711 if (graph_parent_is_merge(graph->prev) &&
6712 i < graph->prev->pos + graph->parents->size)
6713 filler = &fillers[RSHARP];
6714 if (graph->prev->size > graph->size)
6715 filler = &fillers[LDIAG];
6716 }
6718 if (graph->prev->size > graph->size) {
6719 append_to_rev_graph(graph, filler->separator);
6720 if (filler->line != ' ')
6721 append_to_rev_graph(graph, filler->line);
6722 }
6723 }
6725 /* Prepare the next rev graph */
6726 static void
6727 prepare_rev_graph(struct rev_graph *graph)
6728 {
6729 size_t i;
6731 /* First, traverse all lines of revisions up to the active one. */
6732 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6733 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6734 break;
6736 push_rev_graph(graph->next, graph->rev[graph->pos]);
6737 }
6739 /* Interleave the new revision parent(s). */
6740 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6741 push_rev_graph(graph->next, graph->parents->rev[i]);
6743 /* Lastly, put any remaining revisions. */
6744 for (i = graph->pos + 1; i < graph->size; i++)
6745 push_rev_graph(graph->next, graph->rev[i]);
6746 }
6748 static void
6749 update_rev_graph(struct view *view, struct rev_graph *graph)
6750 {
6751 /* If this is the finalizing update ... */
6752 if (graph->commit)
6753 prepare_rev_graph(graph);
6755 /* Graph visualization needs a one rev look-ahead,
6756 * so the first update doesn't visualize anything. */
6757 if (!graph->prev->commit)
6758 return;
6760 if (view->lines > 2)
6761 view->line[view->lines - 3].dirty = 1;
6762 if (view->lines > 1)
6763 view->line[view->lines - 2].dirty = 1;
6764 draw_rev_graph(graph->prev);
6765 done_rev_graph(graph->prev->prev);
6766 }
6769 /*
6770 * Main view backend
6771 */
6773 static const char *main_argv[SIZEOF_ARG] = {
6774 "git", "log", "--no-color", "--pretty=raw", "--parents",
6775 "--topo-order", "%(diffargs)", "%(revargs)",
6776 "--", "%(fileargs)", NULL
6777 };
6779 static bool
6780 main_draw(struct view *view, struct line *line, unsigned int lineno)
6781 {
6782 struct commit *commit = line->data;
6784 if (!commit->author)
6785 return FALSE;
6787 if (opt_date && draw_date(view, &commit->time))
6788 return TRUE;
6790 if (opt_author && draw_author(view, commit->author))
6791 return TRUE;
6793 if (opt_rev_graph && commit->graph_size &&
6794 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6795 return TRUE;
6797 if (opt_show_refs && commit->refs) {
6798 size_t i;
6800 for (i = 0; i < commit->refs->size; i++) {
6801 struct ref *ref = commit->refs->refs[i];
6802 enum line_type type;
6804 if (ref->head)
6805 type = LINE_MAIN_HEAD;
6806 else if (ref->ltag)
6807 type = LINE_MAIN_LOCAL_TAG;
6808 else if (ref->tag)
6809 type = LINE_MAIN_TAG;
6810 else if (ref->tracked)
6811 type = LINE_MAIN_TRACKED;
6812 else if (ref->remote)
6813 type = LINE_MAIN_REMOTE;
6814 else
6815 type = LINE_MAIN_REF;
6817 if (draw_text(view, type, "[") ||
6818 draw_text(view, type, ref->name) ||
6819 draw_text(view, type, "]"))
6820 return TRUE;
6822 if (draw_text(view, LINE_DEFAULT, " "))
6823 return TRUE;
6824 }
6825 }
6827 draw_text(view, LINE_DEFAULT, commit->title);
6828 return TRUE;
6829 }
6831 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6832 static bool
6833 main_read(struct view *view, char *line)
6834 {
6835 static struct rev_graph *graph = graph_stacks;
6836 enum line_type type;
6837 struct commit *commit;
6839 if (!line) {
6840 int i;
6842 if (!view->lines && !view->prev)
6843 die("No revisions match the given arguments.");
6844 if (view->lines > 0) {
6845 commit = view->line[view->lines - 1].data;
6846 view->line[view->lines - 1].dirty = 1;
6847 if (!commit->author) {
6848 view->lines--;
6849 free(commit);
6850 graph->commit = NULL;
6851 }
6852 }
6853 update_rev_graph(view, graph);
6855 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6856 clear_rev_graph(&graph_stacks[i]);
6857 return TRUE;
6858 }
6860 type = get_line_type(line);
6861 if (type == LINE_COMMIT) {
6862 commit = calloc(1, sizeof(struct commit));
6863 if (!commit)
6864 return FALSE;
6866 line += STRING_SIZE("commit ");
6867 if (*line == '-') {
6868 graph->boundary = 1;
6869 line++;
6870 }
6872 string_copy_rev(commit->id, line);
6873 commit->refs = get_ref_list(commit->id);
6874 graph->commit = commit;
6875 add_line_data(view, commit, LINE_MAIN_COMMIT);
6877 while ((line = strchr(line, ' '))) {
6878 line++;
6879 push_rev_graph(graph->parents, line);
6880 commit->has_parents = TRUE;
6881 }
6882 return TRUE;
6883 }
6885 if (!view->lines)
6886 return TRUE;
6887 commit = view->line[view->lines - 1].data;
6889 switch (type) {
6890 case LINE_PARENT:
6891 if (commit->has_parents)
6892 break;
6893 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6894 break;
6896 case LINE_AUTHOR:
6897 parse_author_line(line + STRING_SIZE("author "),
6898 &commit->author, &commit->time);
6899 update_rev_graph(view, graph);
6900 graph = graph->next;
6901 break;
6903 default:
6904 /* Fill in the commit title if it has not already been set. */
6905 if (commit->title[0])
6906 break;
6908 /* Require titles to start with a non-space character at the
6909 * offset used by git log. */
6910 if (strncmp(line, " ", 4))
6911 break;
6912 line += 4;
6913 /* Well, if the title starts with a whitespace character,
6914 * try to be forgiving. Otherwise we end up with no title. */
6915 while (isspace(*line))
6916 line++;
6917 if (*line == '\0')
6918 break;
6919 /* FIXME: More graceful handling of titles; append "..." to
6920 * shortened titles, etc. */
6922 string_expand(commit->title, sizeof(commit->title), line, 1);
6923 view->line[view->lines - 1].dirty = 1;
6924 }
6926 return TRUE;
6927 }
6929 static enum request
6930 main_request(struct view *view, enum request request, struct line *line)
6931 {
6932 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6934 switch (request) {
6935 case REQ_ENTER:
6936 if (view_is_displayed(view) && display[0] != view)
6937 maximize_view(view);
6938 open_view(view, REQ_VIEW_DIFF, flags);
6939 break;
6940 case REQ_REFRESH:
6941 load_refs();
6942 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6943 break;
6944 default:
6945 return request;
6946 }
6948 return REQ_NONE;
6949 }
6951 static bool
6952 grep_refs(struct ref_list *list, regex_t *regex)
6953 {
6954 regmatch_t pmatch;
6955 size_t i;
6957 if (!opt_show_refs || !list)
6958 return FALSE;
6960 for (i = 0; i < list->size; i++) {
6961 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6962 return TRUE;
6963 }
6965 return FALSE;
6966 }
6968 static bool
6969 main_grep(struct view *view, struct line *line)
6970 {
6971 struct commit *commit = line->data;
6972 const char *text[] = {
6973 commit->title,
6974 opt_author ? commit->author : "",
6975 mkdate(&commit->time, opt_date),
6976 NULL
6977 };
6979 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6980 }
6982 static void
6983 main_select(struct view *view, struct line *line)
6984 {
6985 struct commit *commit = line->data;
6987 string_copy_rev(view->ref, commit->id);
6988 string_copy_rev(ref_commit, view->ref);
6989 }
6991 static struct view_ops main_ops = {
6992 "commit",
6993 main_argv,
6994 NULL,
6995 main_read,
6996 main_draw,
6997 main_request,
6998 main_grep,
6999 main_select,
7000 };
7003 /*
7004 * Status management
7005 */
7007 /* Whether or not the curses interface has been initialized. */
7008 static bool cursed = FALSE;
7010 /* Terminal hacks and workarounds. */
7011 static bool use_scroll_redrawwin;
7012 static bool use_scroll_status_wclear;
7014 /* The status window is used for polling keystrokes. */
7015 static WINDOW *status_win;
7017 /* Reading from the prompt? */
7018 static bool input_mode = FALSE;
7020 static bool status_empty = FALSE;
7022 /* Update status and title window. */
7023 static void
7024 report(const char *msg, ...)
7025 {
7026 struct view *view = display[current_view];
7028 if (input_mode)
7029 return;
7031 if (!view) {
7032 char buf[SIZEOF_STR];
7033 va_list args;
7035 va_start(args, msg);
7036 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7037 buf[sizeof(buf) - 1] = 0;
7038 buf[sizeof(buf) - 2] = '.';
7039 buf[sizeof(buf) - 3] = '.';
7040 buf[sizeof(buf) - 4] = '.';
7041 }
7042 va_end(args);
7043 die("%s", buf);
7044 }
7046 if (!status_empty || *msg) {
7047 va_list args;
7049 va_start(args, msg);
7051 wmove(status_win, 0, 0);
7052 if (view->has_scrolled && use_scroll_status_wclear)
7053 wclear(status_win);
7054 if (*msg) {
7055 vwprintw(status_win, msg, args);
7056 status_empty = FALSE;
7057 } else {
7058 status_empty = TRUE;
7059 }
7060 wclrtoeol(status_win);
7061 wnoutrefresh(status_win);
7063 va_end(args);
7064 }
7066 update_view_title(view);
7067 }
7069 static void
7070 init_display(void)
7071 {
7072 const char *term;
7073 int x, y;
7075 /* Initialize the curses library */
7076 if (isatty(STDIN_FILENO)) {
7077 cursed = !!initscr();
7078 opt_tty = stdin;
7079 } else {
7080 /* Leave stdin and stdout alone when acting as a pager. */
7081 opt_tty = fopen("/dev/tty", "r+");
7082 if (!opt_tty)
7083 die("Failed to open /dev/tty");
7084 cursed = !!newterm(NULL, opt_tty, opt_tty);
7085 }
7087 if (!cursed)
7088 die("Failed to initialize curses");
7090 nonl(); /* Disable conversion and detect newlines from input. */
7091 cbreak(); /* Take input chars one at a time, no wait for \n */
7092 noecho(); /* Don't echo input */
7093 leaveok(stdscr, FALSE);
7095 if (has_colors())
7096 init_colors();
7098 getmaxyx(stdscr, y, x);
7099 status_win = newwin(1, 0, y - 1, 0);
7100 if (!status_win)
7101 die("Failed to create status window");
7103 /* Enable keyboard mapping */
7104 keypad(status_win, TRUE);
7105 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7107 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7108 set_tabsize(opt_tab_size);
7109 #else
7110 TABSIZE = opt_tab_size;
7111 #endif
7113 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7114 if (term && !strcmp(term, "gnome-terminal")) {
7115 /* In the gnome-terminal-emulator, the message from
7116 * scrolling up one line when impossible followed by
7117 * scrolling down one line causes corruption of the
7118 * status line. This is fixed by calling wclear. */
7119 use_scroll_status_wclear = TRUE;
7120 use_scroll_redrawwin = FALSE;
7122 } else if (term && !strcmp(term, "xrvt-xpm")) {
7123 /* No problems with full optimizations in xrvt-(unicode)
7124 * and aterm. */
7125 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7127 } else {
7128 /* When scrolling in (u)xterm the last line in the
7129 * scrolling direction will update slowly. */
7130 use_scroll_redrawwin = TRUE;
7131 use_scroll_status_wclear = FALSE;
7132 }
7133 }
7135 static int
7136 get_input(int prompt_position)
7137 {
7138 struct view *view;
7139 int i, key, cursor_y, cursor_x;
7141 if (prompt_position)
7142 input_mode = TRUE;
7144 while (TRUE) {
7145 bool loading = FALSE;
7147 foreach_view (view, i) {
7148 update_view(view);
7149 if (view_is_displayed(view) && view->has_scrolled &&
7150 use_scroll_redrawwin)
7151 redrawwin(view->win);
7152 view->has_scrolled = FALSE;
7153 if (view->pipe)
7154 loading = TRUE;
7155 }
7157 /* Update the cursor position. */
7158 if (prompt_position) {
7159 getbegyx(status_win, cursor_y, cursor_x);
7160 cursor_x = prompt_position;
7161 } else {
7162 view = display[current_view];
7163 getbegyx(view->win, cursor_y, cursor_x);
7164 cursor_x = view->width - 1;
7165 cursor_y += view->lineno - view->offset;
7166 }
7167 setsyx(cursor_y, cursor_x);
7169 /* Refresh, accept single keystroke of input */
7170 doupdate();
7171 nodelay(status_win, loading);
7172 key = wgetch(status_win);
7174 /* wgetch() with nodelay() enabled returns ERR when
7175 * there's no input. */
7176 if (key == ERR) {
7178 } else if (key == KEY_RESIZE) {
7179 int height, width;
7181 getmaxyx(stdscr, height, width);
7183 wresize(status_win, 1, width);
7184 mvwin(status_win, height - 1, 0);
7185 wnoutrefresh(status_win);
7186 resize_display();
7187 redraw_display(TRUE);
7189 } else {
7190 input_mode = FALSE;
7191 return key;
7192 }
7193 }
7194 }
7196 static char *
7197 prompt_input(const char *prompt, input_handler handler, void *data)
7198 {
7199 enum input_status status = INPUT_OK;
7200 static char buf[SIZEOF_STR];
7201 size_t pos = 0;
7203 buf[pos] = 0;
7205 while (status == INPUT_OK || status == INPUT_SKIP) {
7206 int key;
7208 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7209 wclrtoeol(status_win);
7211 key = get_input(pos + 1);
7212 switch (key) {
7213 case KEY_RETURN:
7214 case KEY_ENTER:
7215 case '\n':
7216 status = pos ? INPUT_STOP : INPUT_CANCEL;
7217 break;
7219 case KEY_BACKSPACE:
7220 if (pos > 0)
7221 buf[--pos] = 0;
7222 else
7223 status = INPUT_CANCEL;
7224 break;
7226 case KEY_ESC:
7227 status = INPUT_CANCEL;
7228 break;
7230 default:
7231 if (pos >= sizeof(buf)) {
7232 report("Input string too long");
7233 return NULL;
7234 }
7236 status = handler(data, buf, key);
7237 if (status == INPUT_OK)
7238 buf[pos++] = (char) key;
7239 }
7240 }
7242 /* Clear the status window */
7243 status_empty = FALSE;
7244 report("");
7246 if (status == INPUT_CANCEL)
7247 return NULL;
7249 buf[pos++] = 0;
7251 return buf;
7252 }
7254 static enum input_status
7255 prompt_yesno_handler(void *data, char *buf, int c)
7256 {
7257 if (c == 'y' || c == 'Y')
7258 return INPUT_STOP;
7259 if (c == 'n' || c == 'N')
7260 return INPUT_CANCEL;
7261 return INPUT_SKIP;
7262 }
7264 static bool
7265 prompt_yesno(const char *prompt)
7266 {
7267 char prompt2[SIZEOF_STR];
7269 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7270 return FALSE;
7272 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7273 }
7275 static enum input_status
7276 read_prompt_handler(void *data, char *buf, int c)
7277 {
7278 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7279 }
7281 static char *
7282 read_prompt(const char *prompt)
7283 {
7284 return prompt_input(prompt, read_prompt_handler, NULL);
7285 }
7287 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7288 {
7289 enum input_status status = INPUT_OK;
7290 int size = 0;
7292 while (items[size].text)
7293 size++;
7295 while (status == INPUT_OK) {
7296 const struct menu_item *item = &items[*selected];
7297 int key;
7298 int i;
7300 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7301 prompt, *selected + 1, size);
7302 if (item->hotkey)
7303 wprintw(status_win, "[%c] ", (char) item->hotkey);
7304 wprintw(status_win, "%s", item->text);
7305 wclrtoeol(status_win);
7307 key = get_input(COLS - 1);
7308 switch (key) {
7309 case KEY_RETURN:
7310 case KEY_ENTER:
7311 case '\n':
7312 status = INPUT_STOP;
7313 break;
7315 case KEY_LEFT:
7316 case KEY_UP:
7317 *selected = *selected - 1;
7318 if (*selected < 0)
7319 *selected = size - 1;
7320 break;
7322 case KEY_RIGHT:
7323 case KEY_DOWN:
7324 *selected = (*selected + 1) % size;
7325 break;
7327 case KEY_ESC:
7328 status = INPUT_CANCEL;
7329 break;
7331 default:
7332 for (i = 0; items[i].text; i++)
7333 if (items[i].hotkey == key) {
7334 *selected = i;
7335 status = INPUT_STOP;
7336 break;
7337 }
7338 }
7339 }
7341 /* Clear the status window */
7342 status_empty = FALSE;
7343 report("");
7345 return status != INPUT_CANCEL;
7346 }
7348 /*
7349 * Repository properties
7350 */
7352 static struct ref **refs = NULL;
7353 static size_t refs_size = 0;
7354 static struct ref *refs_head = NULL;
7356 static struct ref_list **ref_lists = NULL;
7357 static size_t ref_lists_size = 0;
7359 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7360 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7361 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7363 static int
7364 compare_refs(const void *ref1_, const void *ref2_)
7365 {
7366 const struct ref *ref1 = *(const struct ref **)ref1_;
7367 const struct ref *ref2 = *(const struct ref **)ref2_;
7369 if (ref1->tag != ref2->tag)
7370 return ref2->tag - ref1->tag;
7371 if (ref1->ltag != ref2->ltag)
7372 return ref2->ltag - ref2->ltag;
7373 if (ref1->head != ref2->head)
7374 return ref2->head - ref1->head;
7375 if (ref1->tracked != ref2->tracked)
7376 return ref2->tracked - ref1->tracked;
7377 if (ref1->remote != ref2->remote)
7378 return ref2->remote - ref1->remote;
7379 return strcmp(ref1->name, ref2->name);
7380 }
7382 static void
7383 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7384 {
7385 size_t i;
7387 for (i = 0; i < refs_size; i++)
7388 if (!visitor(data, refs[i]))
7389 break;
7390 }
7392 static struct ref *
7393 get_ref_head()
7394 {
7395 return refs_head;
7396 }
7398 static struct ref_list *
7399 get_ref_list(const char *id)
7400 {
7401 struct ref_list *list;
7402 size_t i;
7404 for (i = 0; i < ref_lists_size; i++)
7405 if (!strcmp(id, ref_lists[i]->id))
7406 return ref_lists[i];
7408 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7409 return NULL;
7410 list = calloc(1, sizeof(*list));
7411 if (!list)
7412 return NULL;
7414 for (i = 0; i < refs_size; i++) {
7415 if (!strcmp(id, refs[i]->id) &&
7416 realloc_refs_list(&list->refs, list->size, 1))
7417 list->refs[list->size++] = refs[i];
7418 }
7420 if (!list->refs) {
7421 free(list);
7422 return NULL;
7423 }
7425 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7426 ref_lists[ref_lists_size++] = list;
7427 return list;
7428 }
7430 static int
7431 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7432 {
7433 struct ref *ref = NULL;
7434 bool tag = FALSE;
7435 bool ltag = FALSE;
7436 bool remote = FALSE;
7437 bool tracked = FALSE;
7438 bool head = FALSE;
7439 int from = 0, to = refs_size - 1;
7441 if (!prefixcmp(name, "refs/tags/")) {
7442 if (!suffixcmp(name, namelen, "^{}")) {
7443 namelen -= 3;
7444 name[namelen] = 0;
7445 } else {
7446 ltag = TRUE;
7447 }
7449 tag = TRUE;
7450 namelen -= STRING_SIZE("refs/tags/");
7451 name += STRING_SIZE("refs/tags/");
7453 } else if (!prefixcmp(name, "refs/remotes/")) {
7454 remote = TRUE;
7455 namelen -= STRING_SIZE("refs/remotes/");
7456 name += STRING_SIZE("refs/remotes/");
7457 tracked = !strcmp(opt_remote, name);
7459 } else if (!prefixcmp(name, "refs/heads/")) {
7460 namelen -= STRING_SIZE("refs/heads/");
7461 name += STRING_SIZE("refs/heads/");
7462 if (!strncmp(opt_head, name, namelen))
7463 return OK;
7465 } else if (!strcmp(name, "HEAD")) {
7466 head = TRUE;
7467 if (*opt_head) {
7468 namelen = strlen(opt_head);
7469 name = opt_head;
7470 }
7471 }
7473 /* If we are reloading or it's an annotated tag, replace the
7474 * previous SHA1 with the resolved commit id; relies on the fact
7475 * git-ls-remote lists the commit id of an annotated tag right
7476 * before the commit id it points to. */
7477 while (from <= to) {
7478 size_t pos = (to + from) / 2;
7479 int cmp = strcmp(name, refs[pos]->name);
7481 if (!cmp) {
7482 ref = refs[pos];
7483 break;
7484 }
7486 if (cmp < 0)
7487 to = pos - 1;
7488 else
7489 from = pos + 1;
7490 }
7492 if (!ref) {
7493 if (!realloc_refs(&refs, refs_size, 1))
7494 return ERR;
7495 ref = calloc(1, sizeof(*ref) + namelen);
7496 if (!ref)
7497 return ERR;
7498 memmove(refs + from + 1, refs + from,
7499 (refs_size - from) * sizeof(*refs));
7500 refs[from] = ref;
7501 strncpy(ref->name, name, namelen);
7502 refs_size++;
7503 }
7505 ref->head = head;
7506 ref->tag = tag;
7507 ref->ltag = ltag;
7508 ref->remote = remote;
7509 ref->tracked = tracked;
7510 string_copy_rev(ref->id, id);
7512 if (head)
7513 refs_head = ref;
7514 return OK;
7515 }
7517 static int
7518 load_refs(void)
7519 {
7520 const char *head_argv[] = {
7521 "git", "symbolic-ref", "HEAD", NULL
7522 };
7523 static const char *ls_remote_argv[SIZEOF_ARG] = {
7524 "git", "ls-remote", opt_git_dir, NULL
7525 };
7526 static bool init = FALSE;
7527 size_t i;
7529 if (!init) {
7530 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7531 die("TIG_LS_REMOTE contains too many arguments");
7532 init = TRUE;
7533 }
7535 if (!*opt_git_dir)
7536 return OK;
7538 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7539 !prefixcmp(opt_head, "refs/heads/")) {
7540 char *offset = opt_head + STRING_SIZE("refs/heads/");
7542 memmove(opt_head, offset, strlen(offset) + 1);
7543 }
7545 refs_head = NULL;
7546 for (i = 0; i < refs_size; i++)
7547 refs[i]->id[0] = 0;
7549 if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7550 return ERR;
7552 /* Update the ref lists to reflect changes. */
7553 for (i = 0; i < ref_lists_size; i++) {
7554 struct ref_list *list = ref_lists[i];
7555 size_t old, new;
7557 for (old = new = 0; old < list->size; old++)
7558 if (!strcmp(list->id, list->refs[old]->id))
7559 list->refs[new++] = list->refs[old];
7560 list->size = new;
7561 }
7563 return OK;
7564 }
7566 static void
7567 set_remote_branch(const char *name, const char *value, size_t valuelen)
7568 {
7569 if (!strcmp(name, ".remote")) {
7570 string_ncopy(opt_remote, value, valuelen);
7572 } else if (*opt_remote && !strcmp(name, ".merge")) {
7573 size_t from = strlen(opt_remote);
7575 if (!prefixcmp(value, "refs/heads/"))
7576 value += STRING_SIZE("refs/heads/");
7578 if (!string_format_from(opt_remote, &from, "/%s", value))
7579 opt_remote[0] = 0;
7580 }
7581 }
7583 static void
7584 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7585 {
7586 const char *argv[SIZEOF_ARG] = { name, "=" };
7587 int argc = 1 + (cmd == option_set_command);
7588 enum option_code error;
7590 if (!argv_from_string(argv, &argc, value))
7591 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7592 else
7593 error = cmd(argc, argv);
7595 if (error != OPT_OK)
7596 warn("Option 'tig.%s': %s", name, option_errors[error]);
7597 }
7599 static bool
7600 set_environment_variable(const char *name, const char *value)
7601 {
7602 size_t len = strlen(name) + 1 + strlen(value) + 1;
7603 char *env = malloc(len);
7605 if (env &&
7606 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7607 putenv(env) == 0)
7608 return TRUE;
7609 free(env);
7610 return FALSE;
7611 }
7613 static void
7614 set_work_tree(const char *value)
7615 {
7616 char cwd[SIZEOF_STR];
7618 if (!getcwd(cwd, sizeof(cwd)))
7619 die("Failed to get cwd path: %s", strerror(errno));
7620 if (chdir(opt_git_dir) < 0)
7621 die("Failed to chdir(%s): %s", strerror(errno));
7622 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7623 die("Failed to get git path: %s", strerror(errno));
7624 if (chdir(cwd) < 0)
7625 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7626 if (chdir(value) < 0)
7627 die("Failed to chdir(%s): %s", value, strerror(errno));
7628 if (!getcwd(cwd, sizeof(cwd)))
7629 die("Failed to get cwd path: %s", strerror(errno));
7630 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7631 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7632 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7633 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7634 opt_is_inside_work_tree = TRUE;
7635 }
7637 static int
7638 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7639 {
7640 if (!strcmp(name, "i18n.commitencoding"))
7641 string_ncopy(opt_encoding, value, valuelen);
7643 else if (!strcmp(name, "core.editor"))
7644 string_ncopy(opt_editor, value, valuelen);
7646 else if (!strcmp(name, "core.worktree"))
7647 set_work_tree(value);
7649 else if (!prefixcmp(name, "tig.color."))
7650 set_repo_config_option(name + 10, value, option_color_command);
7652 else if (!prefixcmp(name, "tig.bind."))
7653 set_repo_config_option(name + 9, value, option_bind_command);
7655 else if (!prefixcmp(name, "tig."))
7656 set_repo_config_option(name + 4, value, option_set_command);
7658 else if (*opt_head && !prefixcmp(name, "branch.") &&
7659 !strncmp(name + 7, opt_head, strlen(opt_head)))
7660 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7662 return OK;
7663 }
7665 static int
7666 load_git_config(void)
7667 {
7668 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7670 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7671 }
7673 static int
7674 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7675 {
7676 if (!opt_git_dir[0]) {
7677 string_ncopy(opt_git_dir, name, namelen);
7679 } else if (opt_is_inside_work_tree == -1) {
7680 /* This can be 3 different values depending on the
7681 * version of git being used. If git-rev-parse does not
7682 * understand --is-inside-work-tree it will simply echo
7683 * the option else either "true" or "false" is printed.
7684 * Default to true for the unknown case. */
7685 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7687 } else if (*name == '.') {
7688 string_ncopy(opt_cdup, name, namelen);
7690 } else {
7691 string_ncopy(opt_prefix, name, namelen);
7692 }
7694 return OK;
7695 }
7697 static int
7698 load_repo_info(void)
7699 {
7700 const char *rev_parse_argv[] = {
7701 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7702 "--show-cdup", "--show-prefix", NULL
7703 };
7705 return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7706 }
7709 /*
7710 * Main
7711 */
7713 static const char usage[] =
7714 "tig " TIG_VERSION " (" __DATE__ ")\n"
7715 "\n"
7716 "Usage: tig [options] [revs] [--] [paths]\n"
7717 " or: tig show [options] [revs] [--] [paths]\n"
7718 " or: tig blame [rev] path\n"
7719 " or: tig status\n"
7720 " or: tig < [git command output]\n"
7721 "\n"
7722 "Options:\n"
7723 " -v, --version Show version and exit\n"
7724 " -h, --help Show help message and exit";
7726 static void __NORETURN
7727 quit(int sig)
7728 {
7729 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7730 if (cursed)
7731 endwin();
7732 exit(0);
7733 }
7735 static void __NORETURN
7736 die(const char *err, ...)
7737 {
7738 va_list args;
7740 endwin();
7742 va_start(args, err);
7743 fputs("tig: ", stderr);
7744 vfprintf(stderr, err, args);
7745 fputs("\n", stderr);
7746 va_end(args);
7748 exit(1);
7749 }
7751 static void
7752 warn(const char *msg, ...)
7753 {
7754 va_list args;
7756 va_start(args, msg);
7757 fputs("tig warning: ", stderr);
7758 vfprintf(stderr, msg, args);
7759 fputs("\n", stderr);
7760 va_end(args);
7761 }
7763 static int
7764 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7765 {
7766 const char ***filter_args = data;
7768 return argv_append(filter_args, name) ? OK : ERR;
7769 }
7771 static void
7772 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7773 {
7774 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7775 const char **all_argv = NULL;
7777 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7778 !argv_append_array(&all_argv, argv) ||
7779 !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7780 die("Failed to split arguments");
7781 argv_free(all_argv);
7782 free(all_argv);
7783 }
7785 static void
7786 filter_options(const char *argv[])
7787 {
7788 filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7789 filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7790 filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7791 }
7793 static enum request
7794 parse_options(int argc, const char *argv[])
7795 {
7796 enum request request = REQ_VIEW_MAIN;
7797 const char *subcommand;
7798 bool seen_dashdash = FALSE;
7799 const char **filter_argv = NULL;
7800 int i;
7802 if (!isatty(STDIN_FILENO))
7803 return REQ_VIEW_PAGER;
7805 if (argc <= 1)
7806 return REQ_VIEW_MAIN;
7808 subcommand = argv[1];
7809 if (!strcmp(subcommand, "status")) {
7810 if (argc > 2)
7811 warn("ignoring arguments after `%s'", subcommand);
7812 return REQ_VIEW_STATUS;
7814 } else if (!strcmp(subcommand, "blame")) {
7815 if (argc <= 2 || argc > 4)
7816 die("invalid number of options to blame\n\n%s", usage);
7818 i = 2;
7819 if (argc == 4) {
7820 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7821 i++;
7822 }
7824 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7825 return REQ_VIEW_BLAME;
7827 } else if (!strcmp(subcommand, "show")) {
7828 request = REQ_VIEW_DIFF;
7830 } else {
7831 subcommand = NULL;
7832 }
7834 for (i = 1 + !!subcommand; i < argc; i++) {
7835 const char *opt = argv[i];
7837 if (seen_dashdash) {
7838 argv_append(&opt_file_argv, opt);
7839 continue;
7841 } else if (!strcmp(opt, "--")) {
7842 seen_dashdash = TRUE;
7843 continue;
7845 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7846 printf("tig version %s\n", TIG_VERSION);
7847 quit(0);
7849 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7850 printf("%s\n", usage);
7851 quit(0);
7853 } else if (!strcmp(opt, "--all")) {
7854 argv_append(&opt_rev_argv, opt);
7855 continue;
7856 }
7858 if (!argv_append(&filter_argv, opt))
7859 die("command too long");
7860 }
7862 if (filter_argv)
7863 filter_options(filter_argv);
7865 return request;
7866 }
7868 int
7869 main(int argc, const char *argv[])
7870 {
7871 const char *codeset = "UTF-8";
7872 enum request request = parse_options(argc, argv);
7873 struct view *view;
7874 size_t i;
7876 signal(SIGINT, quit);
7877 signal(SIGPIPE, SIG_IGN);
7879 if (setlocale(LC_ALL, "")) {
7880 codeset = nl_langinfo(CODESET);
7881 }
7883 if (load_repo_info() == ERR)
7884 die("Failed to load repo info.");
7886 if (load_options() == ERR)
7887 die("Failed to load user config.");
7889 if (load_git_config() == ERR)
7890 die("Failed to load repo config.");
7892 /* Require a git repository unless when running in pager mode. */
7893 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7894 die("Not a git repository");
7896 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7897 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7898 if (opt_iconv_in == ICONV_NONE)
7899 die("Failed to initialize character set conversion");
7900 }
7902 if (codeset && strcmp(codeset, "UTF-8")) {
7903 opt_iconv_out = iconv_open(codeset, "UTF-8");
7904 if (opt_iconv_out == ICONV_NONE)
7905 die("Failed to initialize character set conversion");
7906 }
7908 if (load_refs() == ERR)
7909 die("Failed to load refs.");
7911 foreach_view (view, i) {
7912 if (getenv(view->cmd_env))
7913 warn("Use of the %s environment variable is deprecated,"
7914 " use options or TIG_DIFF_ARGS instead",
7915 view->cmd_env);
7916 if (!argv_from_env(view->ops->argv, view->cmd_env))
7917 die("Too many arguments in the `%s` environment variable",
7918 view->cmd_env);
7919 }
7921 init_display();
7923 while (view_driver(display[current_view], request)) {
7924 int key = get_input(0);
7926 view = display[current_view];
7927 request = get_keybinding(view->keymap, key);
7929 /* Some low-level request handling. This keeps access to
7930 * status_win restricted. */
7931 switch (request) {
7932 case REQ_NONE:
7933 report("Unknown key, press %s for help",
7934 get_key(view->keymap, REQ_VIEW_HELP));
7935 break;
7936 case REQ_PROMPT:
7937 {
7938 char *cmd = read_prompt(":");
7940 if (cmd && isdigit(*cmd)) {
7941 int lineno = view->lineno + 1;
7943 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7944 select_view_line(view, lineno - 1);
7945 report("");
7946 } else {
7947 report("Unable to parse '%s' as a line number", cmd);
7948 }
7950 } else if (cmd) {
7951 struct view *next = VIEW(REQ_VIEW_PAGER);
7952 const char *argv[SIZEOF_ARG] = { "git" };
7953 int argc = 1;
7955 /* When running random commands, initially show the
7956 * command in the title. However, it maybe later be
7957 * overwritten if a commit line is selected. */
7958 string_ncopy(next->ref, cmd, strlen(cmd));
7960 if (!argv_from_string(argv, &argc, cmd)) {
7961 report("Too many arguments");
7962 } else if (!prepare_update(next, argv, NULL)) {
7963 report("Failed to format command");
7964 } else {
7965 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7966 }
7967 }
7969 request = REQ_NONE;
7970 break;
7971 }
7972 case REQ_SEARCH:
7973 case REQ_SEARCH_BACK:
7974 {
7975 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7976 char *search = read_prompt(prompt);
7978 if (search)
7979 string_ncopy(opt_search, search, strlen(search));
7980 else if (*opt_search)
7981 request = request == REQ_SEARCH ?
7982 REQ_FIND_NEXT :
7983 REQ_FIND_PREV;
7984 else
7985 request = REQ_NONE;
7986 break;
7987 }
7988 default:
7989 break;
7990 }
7991 }
7993 quit(0);
7995 return 0;
7996 }