1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #include <ncurses.h>
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
68 #define ABS(x) ((x) >= 0 ? (x) : -(x))
69 #define MIN(x, y) ((x) < (y) ? (x) : (y))
70 #define MAX(x, y) ((x) > (y) ? (x) : (y))
72 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
73 #define STRING_SIZE(x) (sizeof(x) - 1)
75 #define SIZEOF_STR 1024 /* Default string size. */
76 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
77 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
78 #define SIZEOF_ARG 32 /* Default argument array size. */
80 /* Revision graph */
82 #define REVGRAPH_INIT 'I'
83 #define REVGRAPH_MERGE 'M'
84 #define REVGRAPH_BRANCH '+'
85 #define REVGRAPH_COMMIT '*'
86 #define REVGRAPH_BOUND '^'
88 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
90 /* This color name can be used to refer to the default term colors. */
91 #define COLOR_DEFAULT (-1)
93 #define ICONV_NONE ((iconv_t) -1)
94 #ifndef ICONV_CONST
95 #define ICONV_CONST /* nothing */
96 #endif
98 /* The format and size of the date column in the main view. */
99 #define DATE_FORMAT "%Y-%m-%d %H:%M"
100 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
101 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
103 #define ID_COLS 8
104 #define AUTHOR_COLS 19
106 #define MIN_VIEW_HEIGHT 4
108 #define NULL_ID "0000000000000000000000000000000000000000"
110 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
112 /* Some ASCII-shorthands fitted into the ncurses namespace. */
113 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
114 #define KEY_TAB '\t'
115 #define KEY_RETURN '\r'
116 #define KEY_ESC 27
119 struct ref {
120 char id[SIZEOF_REV]; /* Commit SHA1 ID */
121 unsigned int head:1; /* Is it the current HEAD? */
122 unsigned int tag:1; /* Is it a tag? */
123 unsigned int ltag:1; /* If so, is the tag local? */
124 unsigned int remote:1; /* Is it a remote ref? */
125 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
126 char name[1]; /* Ref name; tag or head names are shortened. */
127 };
129 struct ref_list {
130 char id[SIZEOF_REV]; /* Commit SHA1 ID */
131 size_t size; /* Number of refs. */
132 struct ref **refs; /* References for this ID. */
133 };
135 static struct ref *get_ref_head();
136 static struct ref_list *get_ref_list(const char *id);
137 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
138 static int load_refs(void);
140 enum input_status {
141 INPUT_OK,
142 INPUT_SKIP,
143 INPUT_STOP,
144 INPUT_CANCEL
145 };
147 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
149 static char *prompt_input(const char *prompt, input_handler handler, void *data);
150 static bool prompt_yesno(const char *prompt);
152 struct menu_item {
153 int hotkey;
154 const char *text;
155 void *data;
156 };
158 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
160 /*
161 * Allocation helpers ... Entering macro hell to never be seen again.
162 */
164 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
165 static type * \
166 name(type **mem, size_t size, size_t increase) \
167 { \
168 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
169 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
170 type *tmp = *mem; \
171 \
172 if (mem == NULL || num_chunks != num_chunks_new) { \
173 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
174 if (tmp) \
175 *mem = tmp; \
176 } \
177 \
178 return tmp; \
179 }
181 /*
182 * String helpers
183 */
185 static inline void
186 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187 {
188 if (srclen > dstlen - 1)
189 srclen = dstlen - 1;
191 strncpy(dst, src, srclen);
192 dst[srclen] = 0;
193 }
195 /* Shorthands for safely copying into a fixed buffer. */
197 #define string_copy(dst, src) \
198 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
200 #define string_ncopy(dst, src, srclen) \
201 string_ncopy_do(dst, sizeof(dst), src, srclen)
203 #define string_copy_rev(dst, src) \
204 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
206 #define string_add(dst, from, src) \
207 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
209 static size_t
210 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
211 {
212 size_t size, pos;
214 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
215 if (src[pos] == '\t') {
216 size_t expanded = tabsize - (size % tabsize);
218 if (expanded + size >= dstlen - 1)
219 expanded = dstlen - size - 1;
220 memcpy(dst + size, " ", expanded);
221 size += expanded;
222 } else {
223 dst[size++] = src[pos];
224 }
225 }
227 dst[size] = 0;
228 return pos;
229 }
231 static char *
232 chomp_string(char *name)
233 {
234 int namelen;
236 while (isspace(*name))
237 name++;
239 namelen = strlen(name) - 1;
240 while (namelen > 0 && isspace(name[namelen]))
241 name[namelen--] = 0;
243 return name;
244 }
246 static bool
247 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
248 {
249 va_list args;
250 size_t pos = bufpos ? *bufpos : 0;
252 va_start(args, fmt);
253 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
254 va_end(args);
256 if (bufpos)
257 *bufpos = pos;
259 return pos >= bufsize ? FALSE : TRUE;
260 }
262 #define string_format(buf, fmt, args...) \
263 string_nformat(buf, sizeof(buf), NULL, fmt, args)
265 #define string_format_from(buf, from, fmt, args...) \
266 string_nformat(buf, sizeof(buf), from, fmt, args)
268 static int
269 string_enum_compare(const char *str1, const char *str2, int len)
270 {
271 size_t i;
273 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
275 /* Diff-Header == DIFF_HEADER */
276 for (i = 0; i < len; i++) {
277 if (toupper(str1[i]) == toupper(str2[i]))
278 continue;
280 if (string_enum_sep(str1[i]) &&
281 string_enum_sep(str2[i]))
282 continue;
284 return str1[i] - str2[i];
285 }
287 return 0;
288 }
290 #define enum_equals(entry, str, len) \
291 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
293 struct enum_map {
294 const char *name;
295 int namelen;
296 int value;
297 };
299 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
301 static char *
302 enum_map_name(const char *name, size_t namelen)
303 {
304 static char buf[SIZEOF_STR];
305 int bufpos;
307 for (bufpos = 0; bufpos <= namelen; bufpos++) {
308 buf[bufpos] = tolower(name[bufpos]);
309 if (buf[bufpos] == '_')
310 buf[bufpos] = '-';
311 }
313 buf[bufpos] = 0;
314 return buf;
315 }
317 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
319 static bool
320 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
321 {
322 size_t namelen = strlen(name);
323 int i;
325 for (i = 0; i < map_size; i++)
326 if (enum_equals(map[i], name, namelen)) {
327 *value = map[i].value;
328 return TRUE;
329 }
331 return FALSE;
332 }
334 #define map_enum(attr, map, name) \
335 map_enum_do(map, ARRAY_SIZE(map), attr, name)
337 #define prefixcmp(str1, str2) \
338 strncmp(str1, str2, STRING_SIZE(str2))
340 static inline int
341 suffixcmp(const char *str, int slen, const char *suffix)
342 {
343 size_t len = slen >= 0 ? slen : strlen(str);
344 size_t suffixlen = strlen(suffix);
346 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
347 }
350 /*
351 * Unicode / UTF-8 handling
352 *
353 * NOTE: Much of the following code for dealing with Unicode is derived from
354 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
355 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
356 */
358 static inline int
359 unicode_width(unsigned long c, int tab_size)
360 {
361 if (c >= 0x1100 &&
362 (c <= 0x115f /* Hangul Jamo */
363 || c == 0x2329
364 || c == 0x232a
365 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
366 /* CJK ... Yi */
367 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
368 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
369 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
370 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
371 || (c >= 0xffe0 && c <= 0xffe6)
372 || (c >= 0x20000 && c <= 0x2fffd)
373 || (c >= 0x30000 && c <= 0x3fffd)))
374 return 2;
376 if (c == '\t')
377 return tab_size;
379 return 1;
380 }
382 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
383 * Illegal bytes are set one. */
384 static const unsigned char utf8_bytes[256] = {
385 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
386 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
387 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
392 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
393 };
395 static inline unsigned char
396 utf8_char_length(const char *string, const char *end)
397 {
398 int c = *(unsigned char *) string;
400 return utf8_bytes[c];
401 }
403 /* Decode UTF-8 multi-byte representation into a Unicode character. */
404 static inline unsigned long
405 utf8_to_unicode(const char *string, size_t length)
406 {
407 unsigned long unicode;
409 switch (length) {
410 case 1:
411 unicode = string[0];
412 break;
413 case 2:
414 unicode = (string[0] & 0x1f) << 6;
415 unicode += (string[1] & 0x3f);
416 break;
417 case 3:
418 unicode = (string[0] & 0x0f) << 12;
419 unicode += ((string[1] & 0x3f) << 6);
420 unicode += (string[2] & 0x3f);
421 break;
422 case 4:
423 unicode = (string[0] & 0x0f) << 18;
424 unicode += ((string[1] & 0x3f) << 12);
425 unicode += ((string[2] & 0x3f) << 6);
426 unicode += (string[3] & 0x3f);
427 break;
428 case 5:
429 unicode = (string[0] & 0x0f) << 24;
430 unicode += ((string[1] & 0x3f) << 18);
431 unicode += ((string[2] & 0x3f) << 12);
432 unicode += ((string[3] & 0x3f) << 6);
433 unicode += (string[4] & 0x3f);
434 break;
435 case 6:
436 unicode = (string[0] & 0x01) << 30;
437 unicode += ((string[1] & 0x3f) << 24);
438 unicode += ((string[2] & 0x3f) << 18);
439 unicode += ((string[3] & 0x3f) << 12);
440 unicode += ((string[4] & 0x3f) << 6);
441 unicode += (string[5] & 0x3f);
442 break;
443 default:
444 return 0;
445 }
447 /* Invalid characters could return the special 0xfffd value but NUL
448 * should be just as good. */
449 return unicode > 0xffff ? 0 : unicode;
450 }
452 /* Calculates how much of string can be shown within the given maximum width
453 * and sets trimmed parameter to non-zero value if all of string could not be
454 * shown. If the reserve flag is TRUE, it will reserve at least one
455 * trailing character, which can be useful when drawing a delimiter.
456 *
457 * Returns the number of bytes to output from string to satisfy max_width. */
458 static size_t
459 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
460 {
461 const char *string = *start;
462 const char *end = strchr(string, '\0');
463 unsigned char last_bytes = 0;
464 size_t last_ucwidth = 0;
466 *width = 0;
467 *trimmed = 0;
469 while (string < end) {
470 unsigned char bytes = utf8_char_length(string, end);
471 size_t ucwidth;
472 unsigned long unicode;
474 if (string + bytes > end)
475 break;
477 /* Change representation to figure out whether
478 * it is a single- or double-width character. */
480 unicode = utf8_to_unicode(string, bytes);
481 /* FIXME: Graceful handling of invalid Unicode character. */
482 if (!unicode)
483 break;
485 ucwidth = unicode_width(unicode, tab_size);
486 if (skip > 0) {
487 skip -= ucwidth <= skip ? ucwidth : skip;
488 *start += bytes;
489 }
490 *width += ucwidth;
491 if (*width > max_width) {
492 *trimmed = 1;
493 *width -= ucwidth;
494 if (reserve && *width == max_width) {
495 string -= last_bytes;
496 *width -= last_ucwidth;
497 }
498 break;
499 }
501 string += bytes;
502 last_bytes = ucwidth ? bytes : 0;
503 last_ucwidth = ucwidth;
504 }
506 return string - *start;
507 }
510 #define DATE_INFO \
511 DATE_(NO), \
512 DATE_(DEFAULT), \
513 DATE_(LOCAL), \
514 DATE_(RELATIVE), \
515 DATE_(SHORT)
517 enum date {
518 #define DATE_(name) DATE_##name
519 DATE_INFO
520 #undef DATE_
521 };
523 static const struct enum_map date_map[] = {
524 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
525 DATE_INFO
526 #undef DATE_
527 };
529 struct time {
530 time_t sec;
531 int tz;
532 };
534 static inline int timecmp(const struct time *t1, const struct time *t2)
535 {
536 return t1->sec - t2->sec;
537 }
539 static const char *
540 mkdate(const struct time *time, enum date date)
541 {
542 static char buf[DATE_COLS + 1];
543 static const struct enum_map reldate[] = {
544 { "second", 1, 60 * 2 },
545 { "minute", 60, 60 * 60 * 2 },
546 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
547 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
548 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
549 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
550 };
551 struct tm tm;
553 if (!date || !time || !time->sec)
554 return "";
556 if (date == DATE_RELATIVE) {
557 struct timeval now;
558 time_t date = time->sec + time->tz;
559 time_t seconds;
560 int i;
562 gettimeofday(&now, NULL);
563 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
564 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
565 if (seconds >= reldate[i].value)
566 continue;
568 seconds /= reldate[i].namelen;
569 if (!string_format(buf, "%ld %s%s %s",
570 seconds, reldate[i].name,
571 seconds > 1 ? "s" : "",
572 now.tv_sec >= date ? "ago" : "ahead"))
573 break;
574 return buf;
575 }
576 }
578 if (date == DATE_LOCAL) {
579 time_t date = time->sec + time->tz;
580 localtime_r(&date, &tm);
581 }
582 else {
583 gmtime_r(&time->sec, &tm);
584 }
585 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
586 }
589 #define AUTHOR_VALUES \
590 AUTHOR_(NO), \
591 AUTHOR_(FULL), \
592 AUTHOR_(ABBREVIATED)
594 enum author {
595 #define AUTHOR_(name) AUTHOR_##name
596 AUTHOR_VALUES,
597 #undef AUTHOR_
598 AUTHOR_DEFAULT = AUTHOR_FULL
599 };
601 static const struct enum_map author_map[] = {
602 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
603 AUTHOR_VALUES
604 #undef AUTHOR_
605 };
607 static const char *
608 get_author_initials(const char *author)
609 {
610 static char initials[AUTHOR_COLS * 6 + 1];
611 size_t pos = 0;
612 const char *end = strchr(author, '\0');
614 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
616 memset(initials, 0, sizeof(initials));
617 while (author < end) {
618 unsigned char bytes;
619 size_t i;
621 while (is_initial_sep(*author))
622 author++;
624 bytes = utf8_char_length(author, end);
625 if (bytes < sizeof(initials) - 1 - pos) {
626 while (bytes--) {
627 initials[pos++] = *author++;
628 }
629 }
631 for (i = pos; author < end && !is_initial_sep(*author); author++) {
632 if (i < sizeof(initials) - 1)
633 initials[i++] = *author;
634 }
636 initials[i++] = 0;
637 }
639 return initials;
640 }
643 static bool
644 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
645 {
646 int valuelen;
648 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
649 bool advance = cmd[valuelen] != 0;
651 cmd[valuelen] = 0;
652 argv[(*argc)++] = chomp_string(cmd);
653 cmd = chomp_string(cmd + valuelen + advance);
654 }
656 if (*argc < SIZEOF_ARG)
657 argv[*argc] = NULL;
658 return *argc < SIZEOF_ARG;
659 }
661 static bool
662 argv_from_env(const char **argv, const char *name)
663 {
664 char *env = argv ? getenv(name) : NULL;
665 int argc = 0;
667 if (env && *env)
668 env = strdup(env);
669 return !env || argv_from_string(argv, &argc, env);
670 }
672 static void
673 argv_free(const char *argv[])
674 {
675 int argc;
677 if (!argv)
678 return;
679 for (argc = 0; argv[argc]; argc++)
680 free((void *) argv[argc]);
681 argv[0] = NULL;
682 }
684 static size_t
685 argv_size(const char **argv)
686 {
687 int argc = 0;
689 while (argv && argv[argc])
690 argc++;
692 return argc;
693 }
695 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
697 static bool
698 argv_append(const char ***argv, const char *arg)
699 {
700 size_t argc = argv_size(*argv);
702 if (!argv_realloc(argv, argc, 2))
703 return FALSE;
705 (*argv)[argc++] = strdup(arg);
706 (*argv)[argc] = NULL;
707 return TRUE;
708 }
710 static bool
711 argv_append_array(const char ***dst_argv, const char *src_argv[])
712 {
713 int i;
715 for (i = 0; src_argv && src_argv[i]; i++)
716 if (!argv_append(dst_argv, src_argv[i]))
717 return FALSE;
718 return TRUE;
719 }
721 static bool
722 argv_copy(const char ***dst, const char *src[])
723 {
724 int argc;
726 for (argc = 0; src[argc]; argc++)
727 if (!argv_append(dst, src[argc]))
728 return FALSE;
729 return TRUE;
730 }
733 /*
734 * Executing external commands.
735 */
737 enum io_type {
738 IO_FD, /* File descriptor based IO. */
739 IO_BG, /* Execute command in the background. */
740 IO_FG, /* Execute command with same std{in,out,err}. */
741 IO_RD, /* Read only fork+exec IO. */
742 IO_WR, /* Write only fork+exec IO. */
743 IO_AP, /* Append fork+exec output to file. */
744 };
746 struct io {
747 int pipe; /* Pipe end for reading or writing. */
748 pid_t pid; /* PID of spawned process. */
749 int error; /* Error status. */
750 char *buf; /* Read buffer. */
751 size_t bufalloc; /* Allocated buffer size. */
752 size_t bufsize; /* Buffer content size. */
753 char *bufpos; /* Current buffer position. */
754 unsigned int eof:1; /* Has end of file been reached. */
755 };
757 static void
758 io_init(struct io *io)
759 {
760 memset(io, 0, sizeof(*io));
761 io->pipe = -1;
762 }
764 static bool
765 io_open(struct io *io, const char *fmt, ...)
766 {
767 char name[SIZEOF_STR] = "";
768 bool fits;
769 va_list args;
771 io_init(io);
773 va_start(args, fmt);
774 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
775 va_end(args);
777 if (!fits) {
778 io->error = ENAMETOOLONG;
779 return FALSE;
780 }
781 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
782 if (io->pipe == -1)
783 io->error = errno;
784 return io->pipe != -1;
785 }
787 static bool
788 io_kill(struct io *io)
789 {
790 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
791 }
793 static bool
794 io_done(struct io *io)
795 {
796 pid_t pid = io->pid;
798 if (io->pipe != -1)
799 close(io->pipe);
800 free(io->buf);
801 io_init(io);
803 while (pid > 0) {
804 int status;
805 pid_t waiting = waitpid(pid, &status, 0);
807 if (waiting < 0) {
808 if (errno == EINTR)
809 continue;
810 io->error = errno;
811 return FALSE;
812 }
814 return waiting == pid &&
815 !WIFSIGNALED(status) &&
816 WIFEXITED(status) &&
817 !WEXITSTATUS(status);
818 }
820 return TRUE;
821 }
823 static bool
824 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
825 {
826 int pipefds[2] = { -1, -1 };
827 va_list args;
829 io_init(io);
831 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
832 io->error = errno;
833 return FALSE;
834 } else if (type == IO_AP) {
835 va_start(args, argv);
836 pipefds[1] = va_arg(args, int);
837 va_end(args);
838 }
840 if ((io->pid = fork())) {
841 if (io->pid == -1)
842 io->error = errno;
843 if (pipefds[!(type == IO_WR)] != -1)
844 close(pipefds[!(type == IO_WR)]);
845 if (io->pid != -1) {
846 io->pipe = pipefds[!!(type == IO_WR)];
847 return TRUE;
848 }
850 } else {
851 if (type != IO_FG) {
852 int devnull = open("/dev/null", O_RDWR);
853 int readfd = type == IO_WR ? pipefds[0] : devnull;
854 int writefd = (type == IO_RD || type == IO_AP)
855 ? pipefds[1] : devnull;
857 dup2(readfd, STDIN_FILENO);
858 dup2(writefd, STDOUT_FILENO);
859 dup2(devnull, STDERR_FILENO);
861 close(devnull);
862 if (pipefds[0] != -1)
863 close(pipefds[0]);
864 if (pipefds[1] != -1)
865 close(pipefds[1]);
866 }
868 if (dir && *dir && chdir(dir) == -1)
869 exit(errno);
871 execvp(argv[0], (char *const*) argv);
872 exit(errno);
873 }
875 if (pipefds[!!(type == IO_WR)] != -1)
876 close(pipefds[!!(type == IO_WR)]);
877 return FALSE;
878 }
880 static bool
881 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
882 {
883 struct io io;
885 return io_run(&io, type, dir, argv, fd) && io_done(&io);
886 }
888 static bool
889 io_run_bg(const char **argv)
890 {
891 return io_complete(IO_BG, argv, NULL, -1);
892 }
894 static bool
895 io_run_fg(const char **argv, const char *dir)
896 {
897 return io_complete(IO_FG, argv, dir, -1);
898 }
900 static bool
901 io_run_append(const char **argv, int fd)
902 {
903 return io_complete(IO_AP, argv, NULL, fd);
904 }
906 static bool
907 io_eof(struct io *io)
908 {
909 return io->eof;
910 }
912 static int
913 io_error(struct io *io)
914 {
915 return io->error;
916 }
918 static char *
919 io_strerror(struct io *io)
920 {
921 return strerror(io->error);
922 }
924 static bool
925 io_can_read(struct io *io)
926 {
927 struct timeval tv = { 0, 500 };
928 fd_set fds;
930 FD_ZERO(&fds);
931 FD_SET(io->pipe, &fds);
933 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
934 }
936 static ssize_t
937 io_read(struct io *io, void *buf, size_t bufsize)
938 {
939 do {
940 ssize_t readsize = read(io->pipe, buf, bufsize);
942 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
943 continue;
944 else if (readsize == -1)
945 io->error = errno;
946 else if (readsize == 0)
947 io->eof = 1;
948 return readsize;
949 } while (1);
950 }
952 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
954 static char *
955 io_get(struct io *io, int c, bool can_read)
956 {
957 char *eol;
958 ssize_t readsize;
960 while (TRUE) {
961 if (io->bufsize > 0) {
962 eol = memchr(io->bufpos, c, io->bufsize);
963 if (eol) {
964 char *line = io->bufpos;
966 *eol = 0;
967 io->bufpos = eol + 1;
968 io->bufsize -= io->bufpos - line;
969 return line;
970 }
971 }
973 if (io_eof(io)) {
974 if (io->bufsize) {
975 io->bufpos[io->bufsize] = 0;
976 io->bufsize = 0;
977 return io->bufpos;
978 }
979 return NULL;
980 }
982 if (!can_read)
983 return NULL;
985 if (io->bufsize > 0 && io->bufpos > io->buf)
986 memmove(io->buf, io->bufpos, io->bufsize);
988 if (io->bufalloc == io->bufsize) {
989 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
990 return NULL;
991 io->bufalloc += BUFSIZ;
992 }
994 io->bufpos = io->buf;
995 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
996 if (io_error(io))
997 return NULL;
998 io->bufsize += readsize;
999 }
1000 }
1002 static bool
1003 io_write(struct io *io, const void *buf, size_t bufsize)
1004 {
1005 size_t written = 0;
1007 while (!io_error(io) && written < bufsize) {
1008 ssize_t size;
1010 size = write(io->pipe, buf + written, bufsize - written);
1011 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1012 continue;
1013 else if (size == -1)
1014 io->error = errno;
1015 else
1016 written += size;
1017 }
1019 return written == bufsize;
1020 }
1022 static bool
1023 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024 {
1025 char *result = io_get(io, '\n', TRUE);
1027 if (result) {
1028 result = chomp_string(result);
1029 string_ncopy_do(buf, bufsize, result, strlen(result));
1030 }
1032 return io_done(io) && result;
1033 }
1035 static bool
1036 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037 {
1038 struct io io;
1040 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1041 }
1043 typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
1045 static int
1046 io_load(struct io *io, const char *separators,
1047 io_read_fn read_property, void *data)
1048 {
1049 char *name;
1050 int state = OK;
1052 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1053 char *value;
1054 size_t namelen;
1055 size_t valuelen;
1057 name = chomp_string(name);
1058 namelen = strcspn(name, separators);
1060 if (name[namelen]) {
1061 name[namelen] = 0;
1062 value = chomp_string(name + namelen + 1);
1063 valuelen = strlen(value);
1065 } else {
1066 value = "";
1067 valuelen = 0;
1068 }
1070 state = read_property(name, namelen, value, valuelen, data);
1071 }
1073 if (state != ERR && io_error(io))
1074 state = ERR;
1075 io_done(io);
1077 return state;
1078 }
1080 static int
1081 io_run_load(const char **argv, const char *separators,
1082 io_read_fn read_property, void *data)
1083 {
1084 struct io io;
1086 if (!io_run(&io, IO_RD, NULL, argv))
1087 return ERR;
1088 return io_load(&io, separators, read_property, data);
1089 }
1092 /*
1093 * User requests
1094 */
1096 #define REQ_INFO \
1097 /* XXX: Keep the view request first and in sync with views[]. */ \
1098 REQ_GROUP("View switching") \
1099 REQ_(VIEW_MAIN, "Show main view"), \
1100 REQ_(VIEW_DIFF, "Show diff view"), \
1101 REQ_(VIEW_LOG, "Show log view"), \
1102 REQ_(VIEW_TREE, "Show tree view"), \
1103 REQ_(VIEW_BLOB, "Show blob view"), \
1104 REQ_(VIEW_BLAME, "Show blame view"), \
1105 REQ_(VIEW_BRANCH, "Show branch view"), \
1106 REQ_(VIEW_HELP, "Show help page"), \
1107 REQ_(VIEW_PAGER, "Show pager view"), \
1108 REQ_(VIEW_STATUS, "Show status view"), \
1109 REQ_(VIEW_STAGE, "Show stage view"), \
1110 \
1111 REQ_GROUP("View manipulation") \
1112 REQ_(ENTER, "Enter current line and scroll"), \
1113 REQ_(NEXT, "Move to next"), \
1114 REQ_(PREVIOUS, "Move to previous"), \
1115 REQ_(PARENT, "Move to parent"), \
1116 REQ_(VIEW_NEXT, "Move focus to next view"), \
1117 REQ_(REFRESH, "Reload and refresh"), \
1118 REQ_(MAXIMIZE, "Maximize the current view"), \
1119 REQ_(VIEW_CLOSE, "Close the current view"), \
1120 REQ_(QUIT, "Close all views and quit"), \
1121 \
1122 REQ_GROUP("View specific requests") \
1123 REQ_(STATUS_UPDATE, "Update file status"), \
1124 REQ_(STATUS_REVERT, "Revert file changes"), \
1125 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1126 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1127 \
1128 REQ_GROUP("Cursor navigation") \
1129 REQ_(MOVE_UP, "Move cursor one line up"), \
1130 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1131 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1132 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1133 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1134 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1135 \
1136 REQ_GROUP("Scrolling") \
1137 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1138 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1139 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1140 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1141 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1142 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1143 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1144 \
1145 REQ_GROUP("Searching") \
1146 REQ_(SEARCH, "Search the view"), \
1147 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1148 REQ_(FIND_NEXT, "Find next search match"), \
1149 REQ_(FIND_PREV, "Find previous search match"), \
1150 \
1151 REQ_GROUP("Option manipulation") \
1152 REQ_(OPTIONS, "Open option menu"), \
1153 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1154 REQ_(TOGGLE_DATE, "Toggle date display"), \
1155 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1156 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1157 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1158 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160 \
1161 REQ_GROUP("Misc") \
1162 REQ_(PROMPT, "Bring up the prompt"), \
1163 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1164 REQ_(SHOW_VERSION, "Show version information"), \
1165 REQ_(STOP_LOADING, "Stop all loading views"), \
1166 REQ_(EDIT, "Open in editor"), \
1167 REQ_(NONE, "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175 /* Offset all requests to avoid conflicts with ncurses getch values. */
1176 REQ_UNKNOWN = KEY_MAX + 1,
1177 REQ_OFFSET,
1178 REQ_INFO
1180 #undef REQ_GROUP
1181 #undef REQ_
1182 };
1184 struct request_info {
1185 enum request request;
1186 const char *name;
1187 int namelen;
1188 const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194 REQ_INFO
1195 #undef REQ_GROUP
1196 #undef REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1201 {
1202 int namelen = strlen(name);
1203 int i;
1205 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206 if (enum_equals(req_info[i], name, namelen))
1207 return req_info[i].request;
1209 return REQ_UNKNOWN;
1210 }
1213 /*
1214 * Options
1215 */
1217 /* Option and state variables. */
1218 static enum date opt_date = DATE_DEFAULT;
1219 static enum author opt_author = AUTHOR_DEFAULT;
1220 static bool opt_line_number = FALSE;
1221 static bool opt_line_graphics = TRUE;
1222 static bool opt_rev_graph = FALSE;
1223 static bool opt_show_refs = TRUE;
1224 static bool opt_untracked_dirs_content = TRUE;
1225 static int opt_num_interval = 5;
1226 static double opt_hscroll = 0.50;
1227 static double opt_scale_split_view = 2.0 / 3.0;
1228 static int opt_tab_size = 8;
1229 static int opt_author_cols = AUTHOR_COLS;
1230 static char opt_path[SIZEOF_STR] = "";
1231 static char opt_file[SIZEOF_STR] = "";
1232 static char opt_ref[SIZEOF_REF] = "";
1233 static char opt_head[SIZEOF_REF] = "";
1234 static char opt_remote[SIZEOF_REF] = "";
1235 static char opt_encoding[20] = "UTF-8";
1236 static iconv_t opt_iconv_in = ICONV_NONE;
1237 static iconv_t opt_iconv_out = ICONV_NONE;
1238 static char opt_search[SIZEOF_STR] = "";
1239 static char opt_cdup[SIZEOF_STR] = "";
1240 static char opt_prefix[SIZEOF_STR] = "";
1241 static char opt_git_dir[SIZEOF_STR] = "";
1242 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1243 static char opt_editor[SIZEOF_STR] = "";
1244 static FILE *opt_tty = NULL;
1245 static const char **opt_diff_argv = NULL;
1246 static const char **opt_rev_argv = NULL;
1247 static const char **opt_file_argv = NULL;
1249 #define is_initial_commit() (!get_ref_head())
1250 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1253 /*
1254 * Line-oriented content detection.
1255 */
1257 #define LINE_INFO \
1258 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1273 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1274 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1275 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1279 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1280 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1281 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1282 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1283 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1284 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1285 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1289 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1290 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1291 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1293 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1296 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1297 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1301 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1302 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1306 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1307 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1308 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1309 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1310 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1311 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1312 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1313 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1315 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1316 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1318 enum line_type {
1319 #define LINE(type, line, fg, bg, attr) \
1320 LINE_##type
1321 LINE_INFO,
1322 LINE_NONE
1323 #undef LINE
1324 };
1326 struct line_info {
1327 const char *name; /* Option name. */
1328 int namelen; /* Size of option name. */
1329 const char *line; /* The start of line to match. */
1330 int linelen; /* Size of string to match. */
1331 int fg, bg, attr; /* Color and text attributes for the lines. */
1332 };
1334 static struct line_info line_info[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1337 LINE_INFO
1338 #undef LINE
1339 };
1341 static enum line_type
1342 get_line_type(const char *line)
1343 {
1344 int linelen = strlen(line);
1345 enum line_type type;
1347 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1348 /* Case insensitive search matches Signed-off-by lines better. */
1349 if (linelen >= line_info[type].linelen &&
1350 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1351 return type;
1353 return LINE_DEFAULT;
1354 }
1356 static inline int
1357 get_line_attr(enum line_type type)
1358 {
1359 assert(type < ARRAY_SIZE(line_info));
1360 return COLOR_PAIR(type) | line_info[type].attr;
1361 }
1363 static struct line_info *
1364 get_line_info(const char *name)
1365 {
1366 size_t namelen = strlen(name);
1367 enum line_type type;
1369 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1370 if (enum_equals(line_info[type], name, namelen))
1371 return &line_info[type];
1373 return NULL;
1374 }
1376 static void
1377 init_colors(void)
1378 {
1379 int default_bg = line_info[LINE_DEFAULT].bg;
1380 int default_fg = line_info[LINE_DEFAULT].fg;
1381 enum line_type type;
1383 start_color();
1385 if (assume_default_colors(default_fg, default_bg) == ERR) {
1386 default_bg = COLOR_BLACK;
1387 default_fg = COLOR_WHITE;
1388 }
1390 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1391 struct line_info *info = &line_info[type];
1392 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1393 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1395 init_pair(type, fg, bg);
1396 }
1397 }
1399 struct line {
1400 enum line_type type;
1402 /* State flags */
1403 unsigned int selected:1;
1404 unsigned int dirty:1;
1405 unsigned int cleareol:1;
1406 unsigned int other:16;
1408 void *data; /* User data */
1409 };
1412 /*
1413 * Keys
1414 */
1416 struct keybinding {
1417 int alias;
1418 enum request request;
1419 };
1421 static struct keybinding default_keybindings[] = {
1422 /* View switching */
1423 { 'm', REQ_VIEW_MAIN },
1424 { 'd', REQ_VIEW_DIFF },
1425 { 'l', REQ_VIEW_LOG },
1426 { 't', REQ_VIEW_TREE },
1427 { 'f', REQ_VIEW_BLOB },
1428 { 'B', REQ_VIEW_BLAME },
1429 { 'H', REQ_VIEW_BRANCH },
1430 { 'p', REQ_VIEW_PAGER },
1431 { 'h', REQ_VIEW_HELP },
1432 { 'S', REQ_VIEW_STATUS },
1433 { 'c', REQ_VIEW_STAGE },
1435 /* View manipulation */
1436 { 'q', REQ_VIEW_CLOSE },
1437 { KEY_TAB, REQ_VIEW_NEXT },
1438 { KEY_RETURN, REQ_ENTER },
1439 { KEY_UP, REQ_PREVIOUS },
1440 { KEY_CTL('P'), REQ_PREVIOUS },
1441 { KEY_DOWN, REQ_NEXT },
1442 { KEY_CTL('N'), REQ_NEXT },
1443 { 'R', REQ_REFRESH },
1444 { KEY_F(5), REQ_REFRESH },
1445 { 'O', REQ_MAXIMIZE },
1447 /* Cursor navigation */
1448 { 'k', REQ_MOVE_UP },
1449 { 'j', REQ_MOVE_DOWN },
1450 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1451 { KEY_END, REQ_MOVE_LAST_LINE },
1452 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1453 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1454 { ' ', REQ_MOVE_PAGE_DOWN },
1455 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1456 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1457 { 'b', REQ_MOVE_PAGE_UP },
1458 { '-', REQ_MOVE_PAGE_UP },
1460 /* Scrolling */
1461 { '|', REQ_SCROLL_FIRST_COL },
1462 { KEY_LEFT, REQ_SCROLL_LEFT },
1463 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1464 { KEY_IC, REQ_SCROLL_LINE_UP },
1465 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1466 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1467 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1468 { 'w', REQ_SCROLL_PAGE_UP },
1469 { 's', REQ_SCROLL_PAGE_DOWN },
1471 /* Searching */
1472 { '/', REQ_SEARCH },
1473 { '?', REQ_SEARCH_BACK },
1474 { 'n', REQ_FIND_NEXT },
1475 { 'N', REQ_FIND_PREV },
1477 /* Misc */
1478 { 'Q', REQ_QUIT },
1479 { 'z', REQ_STOP_LOADING },
1480 { 'v', REQ_SHOW_VERSION },
1481 { 'r', REQ_SCREEN_REDRAW },
1482 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1483 { 'o', REQ_OPTIONS },
1484 { '.', REQ_TOGGLE_LINENO },
1485 { 'D', REQ_TOGGLE_DATE },
1486 { 'A', REQ_TOGGLE_AUTHOR },
1487 { 'g', REQ_TOGGLE_REV_GRAPH },
1488 { 'F', REQ_TOGGLE_REFS },
1489 { 'I', REQ_TOGGLE_SORT_ORDER },
1490 { 'i', REQ_TOGGLE_SORT_FIELD },
1491 { ':', REQ_PROMPT },
1492 { 'u', REQ_STATUS_UPDATE },
1493 { '!', REQ_STATUS_REVERT },
1494 { 'M', REQ_STATUS_MERGE },
1495 { '@', REQ_STAGE_NEXT },
1496 { ',', REQ_PARENT },
1497 { 'e', REQ_EDIT },
1498 };
1500 #define KEYMAP_INFO \
1501 KEYMAP_(GENERIC), \
1502 KEYMAP_(MAIN), \
1503 KEYMAP_(DIFF), \
1504 KEYMAP_(LOG), \
1505 KEYMAP_(TREE), \
1506 KEYMAP_(BLOB), \
1507 KEYMAP_(BLAME), \
1508 KEYMAP_(BRANCH), \
1509 KEYMAP_(PAGER), \
1510 KEYMAP_(HELP), \
1511 KEYMAP_(STATUS), \
1512 KEYMAP_(STAGE)
1514 enum keymap {
1515 #define KEYMAP_(name) KEYMAP_##name
1516 KEYMAP_INFO
1517 #undef KEYMAP_
1518 };
1520 static const struct enum_map keymap_table[] = {
1521 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1522 KEYMAP_INFO
1523 #undef KEYMAP_
1524 };
1526 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1528 struct keybinding_table {
1529 struct keybinding *data;
1530 size_t size;
1531 };
1533 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1535 static void
1536 add_keybinding(enum keymap keymap, enum request request, int key)
1537 {
1538 struct keybinding_table *table = &keybindings[keymap];
1539 size_t i;
1541 for (i = 0; i < keybindings[keymap].size; i++) {
1542 if (keybindings[keymap].data[i].alias == key) {
1543 keybindings[keymap].data[i].request = request;
1544 return;
1545 }
1546 }
1548 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1549 if (!table->data)
1550 die("Failed to allocate keybinding");
1551 table->data[table->size].alias = key;
1552 table->data[table->size++].request = request;
1554 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1555 int i;
1557 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1558 if (default_keybindings[i].alias == key)
1559 default_keybindings[i].request = REQ_NONE;
1560 }
1561 }
1563 /* Looks for a key binding first in the given map, then in the generic map, and
1564 * lastly in the default keybindings. */
1565 static enum request
1566 get_keybinding(enum keymap keymap, int key)
1567 {
1568 size_t i;
1570 for (i = 0; i < keybindings[keymap].size; i++)
1571 if (keybindings[keymap].data[i].alias == key)
1572 return keybindings[keymap].data[i].request;
1574 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1575 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1576 return keybindings[KEYMAP_GENERIC].data[i].request;
1578 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1579 if (default_keybindings[i].alias == key)
1580 return default_keybindings[i].request;
1582 return (enum request) key;
1583 }
1586 struct key {
1587 const char *name;
1588 int value;
1589 };
1591 static const struct key key_table[] = {
1592 { "Enter", KEY_RETURN },
1593 { "Space", ' ' },
1594 { "Backspace", KEY_BACKSPACE },
1595 { "Tab", KEY_TAB },
1596 { "Escape", KEY_ESC },
1597 { "Left", KEY_LEFT },
1598 { "Right", KEY_RIGHT },
1599 { "Up", KEY_UP },
1600 { "Down", KEY_DOWN },
1601 { "Insert", KEY_IC },
1602 { "Delete", KEY_DC },
1603 { "Hash", '#' },
1604 { "Home", KEY_HOME },
1605 { "End", KEY_END },
1606 { "PageUp", KEY_PPAGE },
1607 { "PageDown", KEY_NPAGE },
1608 { "F1", KEY_F(1) },
1609 { "F2", KEY_F(2) },
1610 { "F3", KEY_F(3) },
1611 { "F4", KEY_F(4) },
1612 { "F5", KEY_F(5) },
1613 { "F6", KEY_F(6) },
1614 { "F7", KEY_F(7) },
1615 { "F8", KEY_F(8) },
1616 { "F9", KEY_F(9) },
1617 { "F10", KEY_F(10) },
1618 { "F11", KEY_F(11) },
1619 { "F12", KEY_F(12) },
1620 };
1622 static int
1623 get_key_value(const char *name)
1624 {
1625 int i;
1627 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1628 if (!strcasecmp(key_table[i].name, name))
1629 return key_table[i].value;
1631 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1632 return (int)name[1] & 0x1f;
1633 if (strlen(name) == 1 && isprint(*name))
1634 return (int) *name;
1635 return ERR;
1636 }
1638 static const char *
1639 get_key_name(int key_value)
1640 {
1641 static char key_char[] = "'X'\0";
1642 const char *seq = NULL;
1643 int key;
1645 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1646 if (key_table[key].value == key_value)
1647 seq = key_table[key].name;
1649 if (seq == NULL && key_value < 0x7f) {
1650 char *s = key_char + 1;
1652 if (key_value >= 0x20) {
1653 *s++ = key_value;
1654 } else {
1655 *s++ = '^';
1656 *s++ = 0x40 | (key_value & 0x1f);
1657 }
1658 *s++ = '\'';
1659 *s++ = '\0';
1660 seq = key_char;
1661 }
1663 return seq ? seq : "(no key)";
1664 }
1666 static bool
1667 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1668 {
1669 const char *sep = *pos > 0 ? ", " : "";
1670 const char *keyname = get_key_name(keybinding->alias);
1672 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1673 }
1675 static bool
1676 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1677 enum keymap keymap, bool all)
1678 {
1679 int i;
1681 for (i = 0; i < keybindings[keymap].size; i++) {
1682 if (keybindings[keymap].data[i].request == request) {
1683 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1684 return FALSE;
1685 if (!all)
1686 break;
1687 }
1688 }
1690 return TRUE;
1691 }
1693 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1695 static const char *
1696 get_keys(enum keymap keymap, enum request request, bool all)
1697 {
1698 static char buf[BUFSIZ];
1699 size_t pos = 0;
1700 int i;
1702 buf[pos] = 0;
1704 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1705 return "Too many keybindings!";
1706 if (pos > 0 && !all)
1707 return buf;
1709 if (keymap != KEYMAP_GENERIC) {
1710 /* Only the generic keymap includes the default keybindings when
1711 * listing all keys. */
1712 if (all)
1713 return buf;
1715 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1716 return "Too many keybindings!";
1717 if (pos)
1718 return buf;
1719 }
1721 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1722 if (default_keybindings[i].request == request) {
1723 if (!append_key(buf, &pos, &default_keybindings[i]))
1724 return "Too many keybindings!";
1725 if (!all)
1726 return buf;
1727 }
1728 }
1730 return buf;
1731 }
1733 struct run_request {
1734 enum keymap keymap;
1735 int key;
1736 const char **argv;
1737 };
1739 static struct run_request *run_request;
1740 static size_t run_requests;
1742 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1744 static enum request
1745 add_run_request(enum keymap keymap, int key, const char **argv)
1746 {
1747 struct run_request *req;
1749 if (!realloc_run_requests(&run_request, run_requests, 1))
1750 return REQ_NONE;
1752 req = &run_request[run_requests];
1753 req->keymap = keymap;
1754 req->key = key;
1755 req->argv = NULL;
1757 if (!argv_copy(&req->argv, argv))
1758 return REQ_NONE;
1760 return REQ_NONE + ++run_requests;
1761 }
1763 static struct run_request *
1764 get_run_request(enum request request)
1765 {
1766 if (request <= REQ_NONE)
1767 return NULL;
1768 return &run_request[request - REQ_NONE - 1];
1769 }
1771 static void
1772 add_builtin_run_requests(void)
1773 {
1774 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1775 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1776 const char *commit[] = { "git", "commit", NULL };
1777 const char *gc[] = { "git", "gc", NULL };
1778 struct run_request reqs[] = {
1779 { KEYMAP_MAIN, 'C', cherry_pick },
1780 { KEYMAP_STATUS, 'C', commit },
1781 { KEYMAP_BRANCH, 'C', checkout },
1782 { KEYMAP_GENERIC, 'G', gc },
1783 };
1784 int i;
1786 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1787 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1789 if (req != reqs[i].key)
1790 continue;
1791 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1792 if (req != REQ_NONE)
1793 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1794 }
1795 }
1797 /*
1798 * User config file handling.
1799 */
1801 #define OPT_ERR_INFO \
1802 OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
1803 OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
1804 OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
1805 OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
1806 OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
1807 OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
1808 OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
1809 OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
1810 OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
1811 OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
1812 OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
1813 OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
1814 OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
1815 OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
1816 OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
1817 OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
1819 enum option_code {
1820 #define OPT_ERR_(name, msg) OPT_ERR_ ## name
1821 OPT_ERR_INFO
1822 #undef OPT_ERR_
1823 OPT_OK
1824 };
1826 static const char *option_errors[] = {
1827 #define OPT_ERR_(name, msg) msg
1828 OPT_ERR_INFO
1829 #undef OPT_ERR_
1830 };
1832 static const struct enum_map color_map[] = {
1833 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1834 COLOR_MAP(DEFAULT),
1835 COLOR_MAP(BLACK),
1836 COLOR_MAP(BLUE),
1837 COLOR_MAP(CYAN),
1838 COLOR_MAP(GREEN),
1839 COLOR_MAP(MAGENTA),
1840 COLOR_MAP(RED),
1841 COLOR_MAP(WHITE),
1842 COLOR_MAP(YELLOW),
1843 };
1845 static const struct enum_map attr_map[] = {
1846 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1847 ATTR_MAP(NORMAL),
1848 ATTR_MAP(BLINK),
1849 ATTR_MAP(BOLD),
1850 ATTR_MAP(DIM),
1851 ATTR_MAP(REVERSE),
1852 ATTR_MAP(STANDOUT),
1853 ATTR_MAP(UNDERLINE),
1854 };
1856 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1858 static enum option_code
1859 parse_step(double *opt, const char *arg)
1860 {
1861 *opt = atoi(arg);
1862 if (!strchr(arg, '%'))
1863 return OPT_OK;
1865 /* "Shift down" so 100% and 1 does not conflict. */
1866 *opt = (*opt - 1) / 100;
1867 if (*opt >= 1.0) {
1868 *opt = 0.99;
1869 return OPT_ERR_INVALID_STEP_VALUE;
1870 }
1871 if (*opt < 0.0) {
1872 *opt = 1;
1873 return OPT_ERR_INVALID_STEP_VALUE;
1874 }
1875 return OPT_OK;
1876 }
1878 static enum option_code
1879 parse_int(int *opt, const char *arg, int min, int max)
1880 {
1881 int value = atoi(arg);
1883 if (min <= value && value <= max) {
1884 *opt = value;
1885 return OPT_OK;
1886 }
1888 return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
1889 }
1891 static bool
1892 set_color(int *color, const char *name)
1893 {
1894 if (map_enum(color, color_map, name))
1895 return TRUE;
1896 if (!prefixcmp(name, "color"))
1897 return parse_int(color, name + 5, 0, 255) == OK;
1898 return FALSE;
1899 }
1901 /* Wants: object fgcolor bgcolor [attribute] */
1902 static enum option_code
1903 option_color_command(int argc, const char *argv[])
1904 {
1905 struct line_info *info;
1907 if (argc < 3)
1908 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1910 info = get_line_info(argv[0]);
1911 if (!info) {
1912 static const struct enum_map obsolete[] = {
1913 ENUM_MAP("main-delim", LINE_DELIMITER),
1914 ENUM_MAP("main-date", LINE_DATE),
1915 ENUM_MAP("main-author", LINE_AUTHOR),
1916 };
1917 int index;
1919 if (!map_enum(&index, obsolete, argv[0])) {
1920 return OPT_ERR_UNKNOWN_COLOR_NAME;
1921 }
1922 info = &line_info[index];
1923 }
1925 if (!set_color(&info->fg, argv[1]) ||
1926 !set_color(&info->bg, argv[2])) {
1927 return OPT_ERR_UNKNOWN_COLOR;
1928 }
1930 info->attr = 0;
1931 while (argc-- > 3) {
1932 int attr;
1934 if (!set_attribute(&attr, argv[argc])) {
1935 return OPT_ERR_UNKNOWN_ATTRIBUTE;
1936 }
1937 info->attr |= attr;
1938 }
1940 return OPT_OK;
1941 }
1943 static enum option_code
1944 parse_bool(bool *opt, const char *arg)
1945 {
1946 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1947 ? TRUE : FALSE;
1948 return OPT_OK;
1949 }
1951 static enum option_code
1952 parse_enum_do(unsigned int *opt, const char *arg,
1953 const struct enum_map *map, size_t map_size)
1954 {
1955 bool is_true;
1957 assert(map_size > 1);
1959 if (map_enum_do(map, map_size, (int *) opt, arg))
1960 return OPT_OK;
1962 parse_bool(&is_true, arg);
1963 *opt = is_true ? map[1].value : map[0].value;
1964 return OPT_OK;
1965 }
1967 #define parse_enum(opt, arg, map) \
1968 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1970 static enum option_code
1971 parse_string(char *opt, const char *arg, size_t optsize)
1972 {
1973 int arglen = strlen(arg);
1975 switch (arg[0]) {
1976 case '\"':
1977 case '\'':
1978 if (arglen == 1 || arg[arglen - 1] != arg[0])
1979 return OPT_ERR_UNMATCHED_QUOTATION;
1980 arg += 1; arglen -= 2;
1981 default:
1982 string_ncopy_do(opt, optsize, arg, arglen);
1983 return OPT_OK;
1984 }
1985 }
1987 /* Wants: name = value */
1988 static enum option_code
1989 option_set_command(int argc, const char *argv[])
1990 {
1991 if (argc != 3)
1992 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1994 if (strcmp(argv[1], "="))
1995 return OPT_ERR_NO_VALUE_ASSIGNED;
1997 if (!strcmp(argv[0], "show-author"))
1998 return parse_enum(&opt_author, argv[2], author_map);
2000 if (!strcmp(argv[0], "show-date"))
2001 return parse_enum(&opt_date, argv[2], date_map);
2003 if (!strcmp(argv[0], "show-rev-graph"))
2004 return parse_bool(&opt_rev_graph, argv[2]);
2006 if (!strcmp(argv[0], "show-refs"))
2007 return parse_bool(&opt_show_refs, argv[2]);
2009 if (!strcmp(argv[0], "show-line-numbers"))
2010 return parse_bool(&opt_line_number, argv[2]);
2012 if (!strcmp(argv[0], "line-graphics"))
2013 return parse_bool(&opt_line_graphics, argv[2]);
2015 if (!strcmp(argv[0], "line-number-interval"))
2016 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2018 if (!strcmp(argv[0], "author-width"))
2019 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2021 if (!strcmp(argv[0], "horizontal-scroll"))
2022 return parse_step(&opt_hscroll, argv[2]);
2024 if (!strcmp(argv[0], "split-view-height"))
2025 return parse_step(&opt_scale_split_view, argv[2]);
2027 if (!strcmp(argv[0], "tab-size"))
2028 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2030 if (!strcmp(argv[0], "commit-encoding"))
2031 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2033 if (!strcmp(argv[0], "status-untracked-dirs"))
2034 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2036 return OPT_ERR_UNKNOWN_VARIABLE_NAME;
2037 }
2039 /* Wants: mode request key */
2040 static enum option_code
2041 option_bind_command(int argc, const char *argv[])
2042 {
2043 enum request request;
2044 int keymap = -1;
2045 int key;
2047 if (argc < 3)
2048 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
2050 if (!set_keymap(&keymap, argv[0]))
2051 return OPT_ERR_UNKNOWN_KEY_MAP;
2053 key = get_key_value(argv[1]);
2054 if (key == ERR)
2055 return OPT_ERR_UNKNOWN_KEY;
2057 request = get_request(argv[2]);
2058 if (request == REQ_UNKNOWN) {
2059 static const struct enum_map obsolete[] = {
2060 ENUM_MAP("cherry-pick", REQ_NONE),
2061 ENUM_MAP("screen-resize", REQ_NONE),
2062 ENUM_MAP("tree-parent", REQ_PARENT),
2063 };
2064 int alias;
2066 if (map_enum(&alias, obsolete, argv[2])) {
2067 if (alias != REQ_NONE)
2068 add_keybinding(keymap, alias, key);
2069 return OPT_ERR_OBSOLETE_REQUEST_NAME;
2070 }
2071 }
2072 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2073 request = add_run_request(keymap, key, argv + 2);
2074 if (request == REQ_UNKNOWN)
2075 return OPT_ERR_UNKNOWN_REQUEST_NAME;
2077 add_keybinding(keymap, request, key);
2079 return OPT_OK;
2080 }
2082 static enum option_code
2083 set_option(const char *opt, char *value)
2084 {
2085 const char *argv[SIZEOF_ARG];
2086 int argc = 0;
2088 if (!argv_from_string(argv, &argc, value))
2089 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
2091 if (!strcmp(opt, "color"))
2092 return option_color_command(argc, argv);
2094 if (!strcmp(opt, "set"))
2095 return option_set_command(argc, argv);
2097 if (!strcmp(opt, "bind"))
2098 return option_bind_command(argc, argv);
2100 return OPT_ERR_UNKNOWN_OPTION_COMMAND;
2101 }
2103 struct config_state {
2104 int lineno;
2105 bool errors;
2106 };
2108 static int
2109 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2110 {
2111 struct config_state *config = data;
2112 enum option_code status = OPT_ERR_NO_OPTION_VALUE;
2114 config->lineno++;
2116 /* Check for comment markers, since read_properties() will
2117 * only ensure opt and value are split at first " \t". */
2118 optlen = strcspn(opt, "#");
2119 if (optlen == 0)
2120 return OK;
2122 if (opt[optlen] == 0) {
2123 /* Look for comment endings in the value. */
2124 size_t len = strcspn(value, "#");
2126 if (len < valuelen) {
2127 valuelen = len;
2128 value[valuelen] = 0;
2129 }
2131 status = set_option(opt, value);
2132 }
2134 if (status != OPT_OK) {
2135 warn("Error on line %d, near '%.*s': %s",
2136 config->lineno, (int) optlen, opt, option_errors[status]);
2137 config->errors = TRUE;
2138 }
2140 /* Always keep going if errors are encountered. */
2141 return OK;
2142 }
2144 static void
2145 load_option_file(const char *path)
2146 {
2147 struct config_state config = { 0, FALSE };
2148 struct io io;
2150 /* It's OK that the file doesn't exist. */
2151 if (!io_open(&io, "%s", path))
2152 return;
2154 if (io_load(&io, " \t", read_option, &config) == ERR ||
2155 config.errors == TRUE)
2156 warn("Errors while loading %s.", path);
2157 }
2159 static int
2160 load_options(void)
2161 {
2162 const char *home = getenv("HOME");
2163 const char *tigrc_user = getenv("TIGRC_USER");
2164 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2165 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2166 char buf[SIZEOF_STR];
2168 if (!tigrc_system)
2169 tigrc_system = SYSCONFDIR "/tigrc";
2170 load_option_file(tigrc_system);
2172 if (!tigrc_user) {
2173 if (!home || !string_format(buf, "%s/.tigrc", home))
2174 return ERR;
2175 tigrc_user = buf;
2176 }
2177 load_option_file(tigrc_user);
2179 /* Add _after_ loading config files to avoid adding run requests
2180 * that conflict with keybindings. */
2181 add_builtin_run_requests();
2183 if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2184 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2185 int argc = 0;
2187 if (!string_format(buf, "%s", tig_diff_opts) ||
2188 !argv_from_string(diff_opts, &argc, buf))
2189 die("TIG_DIFF_OPTS contains too many arguments");
2190 else if (!argv_copy(&opt_diff_argv, diff_opts))
2191 die("Failed to format TIG_DIFF_OPTS arguments");
2192 }
2194 return OK;
2195 }
2198 /*
2199 * The viewer
2200 */
2202 struct view;
2203 struct view_ops;
2205 /* The display array of active views and the index of the current view. */
2206 static struct view *display[2];
2207 static unsigned int current_view;
2209 #define foreach_displayed_view(view, i) \
2210 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2212 #define displayed_views() (display[1] != NULL ? 2 : 1)
2214 /* Current head and commit ID */
2215 static char ref_blob[SIZEOF_REF] = "";
2216 static char ref_commit[SIZEOF_REF] = "HEAD";
2217 static char ref_head[SIZEOF_REF] = "HEAD";
2218 static char ref_branch[SIZEOF_REF] = "";
2220 enum view_type {
2221 VIEW_MAIN,
2222 VIEW_DIFF,
2223 VIEW_LOG,
2224 VIEW_TREE,
2225 VIEW_BLOB,
2226 VIEW_BLAME,
2227 VIEW_BRANCH,
2228 VIEW_HELP,
2229 VIEW_PAGER,
2230 VIEW_STATUS,
2231 VIEW_STAGE,
2232 };
2234 struct view {
2235 enum view_type type; /* View type */
2236 const char *name; /* View name */
2237 const char *cmd_env; /* Command line set via environment */
2238 const char *id; /* Points to either of ref_{head,commit,blob} */
2240 struct view_ops *ops; /* View operations */
2242 enum keymap keymap; /* What keymap does this view have */
2243 bool git_dir; /* Whether the view requires a git directory. */
2245 char ref[SIZEOF_REF]; /* Hovered commit reference */
2246 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2248 int height, width; /* The width and height of the main window */
2249 WINDOW *win; /* The main window */
2250 WINDOW *title; /* The title window living below the main window */
2252 /* Navigation */
2253 unsigned long offset; /* Offset of the window top */
2254 unsigned long yoffset; /* Offset from the window side. */
2255 unsigned long lineno; /* Current line number */
2256 unsigned long p_offset; /* Previous offset of the window top */
2257 unsigned long p_yoffset;/* Previous offset from the window side */
2258 unsigned long p_lineno; /* Previous current line number */
2259 bool p_restore; /* Should the previous position be restored. */
2261 /* Searching */
2262 char grep[SIZEOF_STR]; /* Search string */
2263 regex_t *regex; /* Pre-compiled regexp */
2265 /* If non-NULL, points to the view that opened this view. If this view
2266 * is closed tig will switch back to the parent view. */
2267 struct view *parent;
2268 struct view *prev;
2270 /* Buffering */
2271 size_t lines; /* Total number of lines */
2272 struct line *line; /* Line index */
2273 unsigned int digits; /* Number of digits in the lines member. */
2275 /* Drawing */
2276 struct line *curline; /* Line currently being drawn. */
2277 enum line_type curtype; /* Attribute currently used for drawing. */
2278 unsigned long col; /* Column when drawing. */
2279 bool has_scrolled; /* View was scrolled. */
2281 /* Loading */
2282 const char **argv; /* Shell command arguments. */
2283 const char *dir; /* Directory from which to execute. */
2284 struct io io;
2285 struct io *pipe;
2286 time_t start_time;
2287 time_t update_secs;
2288 };
2290 struct view_ops {
2291 /* What type of content being displayed. Used in the title bar. */
2292 const char *type;
2293 /* Default command arguments. */
2294 const char **argv;
2295 /* Open and reads in all view content. */
2296 bool (*open)(struct view *view);
2297 /* Read one line; updates view->line. */
2298 bool (*read)(struct view *view, char *data);
2299 /* Draw one line; @lineno must be < view->height. */
2300 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2301 /* Depending on view handle a special requests. */
2302 enum request (*request)(struct view *view, enum request request, struct line *line);
2303 /* Search for regexp in a line. */
2304 bool (*grep)(struct view *view, struct line *line);
2305 /* Select line */
2306 void (*select)(struct view *view, struct line *line);
2307 /* Prepare view for loading */
2308 bool (*prepare)(struct view *view);
2309 };
2311 static struct view_ops blame_ops;
2312 static struct view_ops blob_ops;
2313 static struct view_ops diff_ops;
2314 static struct view_ops help_ops;
2315 static struct view_ops log_ops;
2316 static struct view_ops main_ops;
2317 static struct view_ops pager_ops;
2318 static struct view_ops stage_ops;
2319 static struct view_ops status_ops;
2320 static struct view_ops tree_ops;
2321 static struct view_ops branch_ops;
2323 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2324 { type, name, #env, ref, ops, map, git }
2326 #define VIEW_(id, name, ops, git, ref) \
2327 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2329 static struct view views[] = {
2330 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2331 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2332 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2333 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2334 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2335 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2336 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2337 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2338 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2339 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2340 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2341 };
2343 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2345 #define foreach_view(view, i) \
2346 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2348 #define view_is_displayed(view) \
2349 (view == display[0] || view == display[1])
2351 static enum request
2352 view_request(struct view *view, enum request request)
2353 {
2354 if (!view || !view->lines)
2355 return request;
2356 return view->ops->request(view, request, &view->line[view->lineno]);
2357 }
2360 /*
2361 * View drawing.
2362 */
2364 static inline void
2365 set_view_attr(struct view *view, enum line_type type)
2366 {
2367 if (!view->curline->selected && view->curtype != type) {
2368 (void) wattrset(view->win, get_line_attr(type));
2369 wchgat(view->win, -1, 0, type, NULL);
2370 view->curtype = type;
2371 }
2372 }
2374 static int
2375 draw_chars(struct view *view, enum line_type type, const char *string,
2376 int max_len, bool use_tilde)
2377 {
2378 static char out_buffer[BUFSIZ * 2];
2379 int len = 0;
2380 int col = 0;
2381 int trimmed = FALSE;
2382 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2384 if (max_len <= 0)
2385 return 0;
2387 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2389 set_view_attr(view, type);
2390 if (len > 0) {
2391 if (opt_iconv_out != ICONV_NONE) {
2392 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2393 size_t inlen = len + 1;
2395 char *outbuf = out_buffer;
2396 size_t outlen = sizeof(out_buffer);
2398 size_t ret;
2400 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2401 if (ret != (size_t) -1) {
2402 string = out_buffer;
2403 len = sizeof(out_buffer) - outlen;
2404 }
2405 }
2407 waddnstr(view->win, string, len);
2409 if (trimmed && use_tilde) {
2410 set_view_attr(view, LINE_DELIMITER);
2411 waddch(view->win, '~');
2412 col++;
2413 }
2414 }
2416 return col;
2417 }
2419 static int
2420 draw_space(struct view *view, enum line_type type, int max, int spaces)
2421 {
2422 static char space[] = " ";
2423 int col = 0;
2425 spaces = MIN(max, spaces);
2427 while (spaces > 0) {
2428 int len = MIN(spaces, sizeof(space) - 1);
2430 col += draw_chars(view, type, space, len, FALSE);
2431 spaces -= len;
2432 }
2434 return col;
2435 }
2437 static bool
2438 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2439 {
2440 char text[SIZEOF_STR];
2442 do {
2443 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2445 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2446 string += pos;
2447 } while (*string && view->width + view->yoffset > view->col);
2449 return view->width + view->yoffset <= view->col;
2450 }
2452 static bool
2453 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2454 {
2455 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2456 int max = view->width + view->yoffset - view->col;
2457 int i;
2459 if (max < size)
2460 size = max;
2462 set_view_attr(view, type);
2463 /* Using waddch() instead of waddnstr() ensures that
2464 * they'll be rendered correctly for the cursor line. */
2465 for (i = skip; i < size; i++)
2466 waddch(view->win, graphic[i]);
2468 view->col += size;
2469 if (size < max && skip <= size)
2470 waddch(view->win, ' ');
2471 view->col++;
2473 return view->width + view->yoffset <= view->col;
2474 }
2476 static bool
2477 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2478 {
2479 int max = MIN(view->width + view->yoffset - view->col, len);
2480 int col;
2482 if (text)
2483 col = draw_chars(view, type, text, max - 1, trim);
2484 else
2485 col = draw_space(view, type, max - 1, max - 1);
2487 view->col += col;
2488 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2489 return view->width + view->yoffset <= view->col;
2490 }
2492 static bool
2493 draw_date(struct view *view, struct time *time)
2494 {
2495 const char *date = mkdate(time, opt_date);
2496 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2498 return draw_field(view, LINE_DATE, date, cols, FALSE);
2499 }
2501 static bool
2502 draw_author(struct view *view, const char *author)
2503 {
2504 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2505 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2507 if (abbreviate && author)
2508 author = get_author_initials(author);
2510 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2511 }
2513 static bool
2514 draw_mode(struct view *view, mode_t mode)
2515 {
2516 const char *str;
2518 if (S_ISDIR(mode))
2519 str = "drwxr-xr-x";
2520 else if (S_ISLNK(mode))
2521 str = "lrwxrwxrwx";
2522 else if (S_ISGITLINK(mode))
2523 str = "m---------";
2524 else if (S_ISREG(mode) && mode & S_IXUSR)
2525 str = "-rwxr-xr-x";
2526 else if (S_ISREG(mode))
2527 str = "-rw-r--r--";
2528 else
2529 str = "----------";
2531 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2532 }
2534 static bool
2535 draw_lineno(struct view *view, unsigned int lineno)
2536 {
2537 char number[10];
2538 int digits3 = view->digits < 3 ? 3 : view->digits;
2539 int max = MIN(view->width + view->yoffset - view->col, digits3);
2540 char *text = NULL;
2541 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2543 lineno += view->offset + 1;
2544 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2545 static char fmt[] = "%1ld";
2547 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2548 if (string_format(number, fmt, lineno))
2549 text = number;
2550 }
2551 if (text)
2552 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2553 else
2554 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2555 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2556 }
2558 static bool
2559 draw_view_line(struct view *view, unsigned int lineno)
2560 {
2561 struct line *line;
2562 bool selected = (view->offset + lineno == view->lineno);
2564 assert(view_is_displayed(view));
2566 if (view->offset + lineno >= view->lines)
2567 return FALSE;
2569 line = &view->line[view->offset + lineno];
2571 wmove(view->win, lineno, 0);
2572 if (line->cleareol)
2573 wclrtoeol(view->win);
2574 view->col = 0;
2575 view->curline = line;
2576 view->curtype = LINE_NONE;
2577 line->selected = FALSE;
2578 line->dirty = line->cleareol = 0;
2580 if (selected) {
2581 set_view_attr(view, LINE_CURSOR);
2582 line->selected = TRUE;
2583 view->ops->select(view, line);
2584 }
2586 return view->ops->draw(view, line, lineno);
2587 }
2589 static void
2590 redraw_view_dirty(struct view *view)
2591 {
2592 bool dirty = FALSE;
2593 int lineno;
2595 for (lineno = 0; lineno < view->height; lineno++) {
2596 if (view->offset + lineno >= view->lines)
2597 break;
2598 if (!view->line[view->offset + lineno].dirty)
2599 continue;
2600 dirty = TRUE;
2601 if (!draw_view_line(view, lineno))
2602 break;
2603 }
2605 if (!dirty)
2606 return;
2607 wnoutrefresh(view->win);
2608 }
2610 static void
2611 redraw_view_from(struct view *view, int lineno)
2612 {
2613 assert(0 <= lineno && lineno < view->height);
2615 for (; lineno < view->height; lineno++) {
2616 if (!draw_view_line(view, lineno))
2617 break;
2618 }
2620 wnoutrefresh(view->win);
2621 }
2623 static void
2624 redraw_view(struct view *view)
2625 {
2626 werase(view->win);
2627 redraw_view_from(view, 0);
2628 }
2631 static void
2632 update_view_title(struct view *view)
2633 {
2634 char buf[SIZEOF_STR];
2635 char state[SIZEOF_STR];
2636 size_t bufpos = 0, statelen = 0;
2638 assert(view_is_displayed(view));
2640 if (view->type != VIEW_STATUS && view->lines) {
2641 unsigned int view_lines = view->offset + view->height;
2642 unsigned int lines = view->lines
2643 ? MIN(view_lines, view->lines) * 100 / view->lines
2644 : 0;
2646 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2647 view->ops->type,
2648 view->lineno + 1,
2649 view->lines,
2650 lines);
2652 }
2654 if (view->pipe) {
2655 time_t secs = time(NULL) - view->start_time;
2657 /* Three git seconds are a long time ... */
2658 if (secs > 2)
2659 string_format_from(state, &statelen, " loading %lds", secs);
2660 }
2662 string_format_from(buf, &bufpos, "[%s]", view->name);
2663 if (*view->ref && bufpos < view->width) {
2664 size_t refsize = strlen(view->ref);
2665 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2667 if (minsize < view->width)
2668 refsize = view->width - minsize + 7;
2669 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2670 }
2672 if (statelen && bufpos < view->width) {
2673 string_format_from(buf, &bufpos, "%s", state);
2674 }
2676 if (view == display[current_view])
2677 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2678 else
2679 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2681 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2682 wclrtoeol(view->title);
2683 wnoutrefresh(view->title);
2684 }
2686 static int
2687 apply_step(double step, int value)
2688 {
2689 if (step >= 1)
2690 return (int) step;
2691 value *= step + 0.01;
2692 return value ? value : 1;
2693 }
2695 static void
2696 resize_display(void)
2697 {
2698 int offset, i;
2699 struct view *base = display[0];
2700 struct view *view = display[1] ? display[1] : display[0];
2702 /* Setup window dimensions */
2704 getmaxyx(stdscr, base->height, base->width);
2706 /* Make room for the status window. */
2707 base->height -= 1;
2709 if (view != base) {
2710 /* Horizontal split. */
2711 view->width = base->width;
2712 view->height = apply_step(opt_scale_split_view, base->height);
2713 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2714 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2715 base->height -= view->height;
2717 /* Make room for the title bar. */
2718 view->height -= 1;
2719 }
2721 /* Make room for the title bar. */
2722 base->height -= 1;
2724 offset = 0;
2726 foreach_displayed_view (view, i) {
2727 if (!view->win) {
2728 view->win = newwin(view->height, 0, offset, 0);
2729 if (!view->win)
2730 die("Failed to create %s view", view->name);
2732 scrollok(view->win, FALSE);
2734 view->title = newwin(1, 0, offset + view->height, 0);
2735 if (!view->title)
2736 die("Failed to create title window");
2738 } else {
2739 wresize(view->win, view->height, view->width);
2740 mvwin(view->win, offset, 0);
2741 mvwin(view->title, offset + view->height, 0);
2742 }
2744 offset += view->height + 1;
2745 }
2746 }
2748 static void
2749 redraw_display(bool clear)
2750 {
2751 struct view *view;
2752 int i;
2754 foreach_displayed_view (view, i) {
2755 if (clear)
2756 wclear(view->win);
2757 redraw_view(view);
2758 update_view_title(view);
2759 }
2760 }
2763 /*
2764 * Option management
2765 */
2767 #define TOGGLE_MENU \
2768 TOGGLE_(LINENO, '.', "line numbers", &opt_line_number, NULL) \
2769 TOGGLE_(DATE, 'D', "dates", &opt_date, date_map) \
2770 TOGGLE_(AUTHOR, 'A', "author names", &opt_author, author_map) \
2771 TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \
2772 TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL)
2774 static void
2775 toggle_option(enum request request)
2776 {
2777 const struct {
2778 enum request request;
2779 const struct enum_map *map;
2780 size_t map_size;
2781 } data[] = {
2782 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
2783 TOGGLE_MENU
2784 #undef TOGGLE_
2785 };
2786 const struct menu_item menu[] = {
2787 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2788 TOGGLE_MENU
2789 #undef TOGGLE_
2790 { 0 }
2791 };
2792 int i = 0;
2794 if (request == REQ_OPTIONS) {
2795 if (!prompt_menu("Toggle option", menu, &i))
2796 return;
2797 } else {
2798 while (i < ARRAY_SIZE(data) && data[i].request != request)
2799 i++;
2800 if (i >= ARRAY_SIZE(data))
2801 die("Invalid request (%d)", request);
2802 }
2804 if (data[i].map != NULL) {
2805 unsigned int *opt = menu[i].data;
2807 *opt = (*opt + 1) % data[i].map_size;
2808 redraw_display(FALSE);
2809 report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2811 } else {
2812 bool *option = menu[i].data;
2814 *option = !*option;
2815 redraw_display(FALSE);
2816 report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
2817 }
2818 }
2820 static void
2821 maximize_view(struct view *view)
2822 {
2823 memset(display, 0, sizeof(display));
2824 current_view = 0;
2825 display[current_view] = view;
2826 resize_display();
2827 redraw_display(FALSE);
2828 report("");
2829 }
2832 /*
2833 * Navigation
2834 */
2836 static bool
2837 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2838 {
2839 if (lineno >= view->lines)
2840 lineno = view->lines > 0 ? view->lines - 1 : 0;
2842 if (offset > lineno || offset + view->height <= lineno) {
2843 unsigned long half = view->height / 2;
2845 if (lineno > half)
2846 offset = lineno - half;
2847 else
2848 offset = 0;
2849 }
2851 if (offset != view->offset || lineno != view->lineno) {
2852 view->offset = offset;
2853 view->lineno = lineno;
2854 return TRUE;
2855 }
2857 return FALSE;
2858 }
2860 /* Scrolling backend */
2861 static void
2862 do_scroll_view(struct view *view, int lines)
2863 {
2864 bool redraw_current_line = FALSE;
2866 /* The rendering expects the new offset. */
2867 view->offset += lines;
2869 assert(0 <= view->offset && view->offset < view->lines);
2870 assert(lines);
2872 /* Move current line into the view. */
2873 if (view->lineno < view->offset) {
2874 view->lineno = view->offset;
2875 redraw_current_line = TRUE;
2876 } else if (view->lineno >= view->offset + view->height) {
2877 view->lineno = view->offset + view->height - 1;
2878 redraw_current_line = TRUE;
2879 }
2881 assert(view->offset <= view->lineno && view->lineno < view->lines);
2883 /* Redraw the whole screen if scrolling is pointless. */
2884 if (view->height < ABS(lines)) {
2885 redraw_view(view);
2887 } else {
2888 int line = lines > 0 ? view->height - lines : 0;
2889 int end = line + ABS(lines);
2891 scrollok(view->win, TRUE);
2892 wscrl(view->win, lines);
2893 scrollok(view->win, FALSE);
2895 while (line < end && draw_view_line(view, line))
2896 line++;
2898 if (redraw_current_line)
2899 draw_view_line(view, view->lineno - view->offset);
2900 wnoutrefresh(view->win);
2901 }
2903 view->has_scrolled = TRUE;
2904 report("");
2905 }
2907 /* Scroll frontend */
2908 static void
2909 scroll_view(struct view *view, enum request request)
2910 {
2911 int lines = 1;
2913 assert(view_is_displayed(view));
2915 switch (request) {
2916 case REQ_SCROLL_FIRST_COL:
2917 view->yoffset = 0;
2918 redraw_view_from(view, 0);
2919 report("");
2920 return;
2921 case REQ_SCROLL_LEFT:
2922 if (view->yoffset == 0) {
2923 report("Cannot scroll beyond the first column");
2924 return;
2925 }
2926 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2927 view->yoffset = 0;
2928 else
2929 view->yoffset -= apply_step(opt_hscroll, view->width);
2930 redraw_view_from(view, 0);
2931 report("");
2932 return;
2933 case REQ_SCROLL_RIGHT:
2934 view->yoffset += apply_step(opt_hscroll, view->width);
2935 redraw_view(view);
2936 report("");
2937 return;
2938 case REQ_SCROLL_PAGE_DOWN:
2939 lines = view->height;
2940 case REQ_SCROLL_LINE_DOWN:
2941 if (view->offset + lines > view->lines)
2942 lines = view->lines - view->offset;
2944 if (lines == 0 || view->offset + view->height >= view->lines) {
2945 report("Cannot scroll beyond the last line");
2946 return;
2947 }
2948 break;
2950 case REQ_SCROLL_PAGE_UP:
2951 lines = view->height;
2952 case REQ_SCROLL_LINE_UP:
2953 if (lines > view->offset)
2954 lines = view->offset;
2956 if (lines == 0) {
2957 report("Cannot scroll beyond the first line");
2958 return;
2959 }
2961 lines = -lines;
2962 break;
2964 default:
2965 die("request %d not handled in switch", request);
2966 }
2968 do_scroll_view(view, lines);
2969 }
2971 /* Cursor moving */
2972 static void
2973 move_view(struct view *view, enum request request)
2974 {
2975 int scroll_steps = 0;
2976 int steps;
2978 switch (request) {
2979 case REQ_MOVE_FIRST_LINE:
2980 steps = -view->lineno;
2981 break;
2983 case REQ_MOVE_LAST_LINE:
2984 steps = view->lines - view->lineno - 1;
2985 break;
2987 case REQ_MOVE_PAGE_UP:
2988 steps = view->height > view->lineno
2989 ? -view->lineno : -view->height;
2990 break;
2992 case REQ_MOVE_PAGE_DOWN:
2993 steps = view->lineno + view->height >= view->lines
2994 ? view->lines - view->lineno - 1 : view->height;
2995 break;
2997 case REQ_MOVE_UP:
2998 steps = -1;
2999 break;
3001 case REQ_MOVE_DOWN:
3002 steps = 1;
3003 break;
3005 default:
3006 die("request %d not handled in switch", request);
3007 }
3009 if (steps <= 0 && view->lineno == 0) {
3010 report("Cannot move beyond the first line");
3011 return;
3013 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3014 report("Cannot move beyond the last line");
3015 return;
3016 }
3018 /* Move the current line */
3019 view->lineno += steps;
3020 assert(0 <= view->lineno && view->lineno < view->lines);
3022 /* Check whether the view needs to be scrolled */
3023 if (view->lineno < view->offset ||
3024 view->lineno >= view->offset + view->height) {
3025 scroll_steps = steps;
3026 if (steps < 0 && -steps > view->offset) {
3027 scroll_steps = -view->offset;
3029 } else if (steps > 0) {
3030 if (view->lineno == view->lines - 1 &&
3031 view->lines > view->height) {
3032 scroll_steps = view->lines - view->offset - 1;
3033 if (scroll_steps >= view->height)
3034 scroll_steps -= view->height - 1;
3035 }
3036 }
3037 }
3039 if (!view_is_displayed(view)) {
3040 view->offset += scroll_steps;
3041 assert(0 <= view->offset && view->offset < view->lines);
3042 view->ops->select(view, &view->line[view->lineno]);
3043 return;
3044 }
3046 /* Repaint the old "current" line if we be scrolling */
3047 if (ABS(steps) < view->height)
3048 draw_view_line(view, view->lineno - steps - view->offset);
3050 if (scroll_steps) {
3051 do_scroll_view(view, scroll_steps);
3052 return;
3053 }
3055 /* Draw the current line */
3056 draw_view_line(view, view->lineno - view->offset);
3058 wnoutrefresh(view->win);
3059 report("");
3060 }
3063 /*
3064 * Searching
3065 */
3067 static void search_view(struct view *view, enum request request);
3069 static bool
3070 grep_text(struct view *view, const char *text[])
3071 {
3072 regmatch_t pmatch;
3073 size_t i;
3075 for (i = 0; text[i]; i++)
3076 if (*text[i] &&
3077 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3078 return TRUE;
3079 return FALSE;
3080 }
3082 static void
3083 select_view_line(struct view *view, unsigned long lineno)
3084 {
3085 unsigned long old_lineno = view->lineno;
3086 unsigned long old_offset = view->offset;
3088 if (goto_view_line(view, view->offset, lineno)) {
3089 if (view_is_displayed(view)) {
3090 if (old_offset != view->offset) {
3091 redraw_view(view);
3092 } else {
3093 draw_view_line(view, old_lineno - view->offset);
3094 draw_view_line(view, view->lineno - view->offset);
3095 wnoutrefresh(view->win);
3096 }
3097 } else {
3098 view->ops->select(view, &view->line[view->lineno]);
3099 }
3100 }
3101 }
3103 static void
3104 find_next(struct view *view, enum request request)
3105 {
3106 unsigned long lineno = view->lineno;
3107 int direction;
3109 if (!*view->grep) {
3110 if (!*opt_search)
3111 report("No previous search");
3112 else
3113 search_view(view, request);
3114 return;
3115 }
3117 switch (request) {
3118 case REQ_SEARCH:
3119 case REQ_FIND_NEXT:
3120 direction = 1;
3121 break;
3123 case REQ_SEARCH_BACK:
3124 case REQ_FIND_PREV:
3125 direction = -1;
3126 break;
3128 default:
3129 return;
3130 }
3132 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3133 lineno += direction;
3135 /* Note, lineno is unsigned long so will wrap around in which case it
3136 * will become bigger than view->lines. */
3137 for (; lineno < view->lines; lineno += direction) {
3138 if (view->ops->grep(view, &view->line[lineno])) {
3139 select_view_line(view, lineno);
3140 report("Line %ld matches '%s'", lineno + 1, view->grep);
3141 return;
3142 }
3143 }
3145 report("No match found for '%s'", view->grep);
3146 }
3148 static void
3149 search_view(struct view *view, enum request request)
3150 {
3151 int regex_err;
3153 if (view->regex) {
3154 regfree(view->regex);
3155 *view->grep = 0;
3156 } else {
3157 view->regex = calloc(1, sizeof(*view->regex));
3158 if (!view->regex)
3159 return;
3160 }
3162 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3163 if (regex_err != 0) {
3164 char buf[SIZEOF_STR] = "unknown error";
3166 regerror(regex_err, view->regex, buf, sizeof(buf));
3167 report("Search failed: %s", buf);
3168 return;
3169 }
3171 string_copy(view->grep, opt_search);
3173 find_next(view, request);
3174 }
3176 /*
3177 * Incremental updating
3178 */
3180 static void
3181 reset_view(struct view *view)
3182 {
3183 int i;
3185 for (i = 0; i < view->lines; i++)
3186 free(view->line[i].data);
3187 free(view->line);
3189 view->p_offset = view->offset;
3190 view->p_yoffset = view->yoffset;
3191 view->p_lineno = view->lineno;
3193 view->line = NULL;
3194 view->offset = 0;
3195 view->yoffset = 0;
3196 view->lines = 0;
3197 view->lineno = 0;
3198 view->vid[0] = 0;
3199 view->update_secs = 0;
3200 }
3202 static const char *
3203 format_arg(const char *name)
3204 {
3205 static struct {
3206 const char *name;
3207 size_t namelen;
3208 const char *value;
3209 const char *value_if_empty;
3210 } vars[] = {
3211 #define FORMAT_VAR(name, value, value_if_empty) \
3212 { name, STRING_SIZE(name), value, value_if_empty }
3213 FORMAT_VAR("%(directory)", opt_path, ""),
3214 FORMAT_VAR("%(file)", opt_file, ""),
3215 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3216 FORMAT_VAR("%(head)", ref_head, ""),
3217 FORMAT_VAR("%(commit)", ref_commit, ""),
3218 FORMAT_VAR("%(blob)", ref_blob, ""),
3219 FORMAT_VAR("%(branch)", ref_branch, ""),
3220 };
3221 int i;
3223 for (i = 0; i < ARRAY_SIZE(vars); i++)
3224 if (!strncmp(name, vars[i].name, vars[i].namelen))
3225 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3227 report("Unknown replacement: `%s`", name);
3228 return NULL;
3229 }
3231 static bool
3232 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3233 {
3234 char buf[SIZEOF_STR];
3235 int argc;
3237 argv_free(*dst_argv);
3239 for (argc = 0; src_argv[argc]; argc++) {
3240 const char *arg = src_argv[argc];
3241 size_t bufpos = 0;
3243 if (!strcmp(arg, "%(fileargs)")) {
3244 if (!argv_append_array(dst_argv, opt_file_argv))
3245 break;
3246 continue;
3248 } else if (!strcmp(arg, "%(diffargs)")) {
3249 if (!argv_append_array(dst_argv, opt_diff_argv))
3250 break;
3251 continue;
3253 } else if (!strcmp(arg, "%(revargs)") ||
3254 (first && !strcmp(arg, "%(commit)"))) {
3255 if (!argv_append_array(dst_argv, opt_rev_argv))
3256 break;
3257 continue;
3258 }
3260 while (arg) {
3261 char *next = strstr(arg, "%(");
3262 int len = next - arg;
3263 const char *value;
3265 if (!next || !replace) {
3266 len = strlen(arg);
3267 value = "";
3269 } else {
3270 value = format_arg(next);
3272 if (!value) {
3273 return FALSE;
3274 }
3275 }
3277 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3278 return FALSE;
3280 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3281 }
3283 if (!argv_append(dst_argv, buf))
3284 break;
3285 }
3287 return src_argv[argc] == NULL;
3288 }
3290 static bool
3291 restore_view_position(struct view *view)
3292 {
3293 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3294 return FALSE;
3296 /* Changing the view position cancels the restoring. */
3297 /* FIXME: Changing back to the first line is not detected. */
3298 if (view->offset != 0 || view->lineno != 0) {
3299 view->p_restore = FALSE;
3300 return FALSE;
3301 }
3303 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3304 view_is_displayed(view))
3305 werase(view->win);
3307 view->yoffset = view->p_yoffset;
3308 view->p_restore = FALSE;
3310 return TRUE;
3311 }
3313 static void
3314 end_update(struct view *view, bool force)
3315 {
3316 if (!view->pipe)
3317 return;
3318 while (!view->ops->read(view, NULL))
3319 if (!force)
3320 return;
3321 if (force)
3322 io_kill(view->pipe);
3323 io_done(view->pipe);
3324 view->pipe = NULL;
3325 }
3327 static void
3328 setup_update(struct view *view, const char *vid)
3329 {
3330 reset_view(view);
3331 string_copy_rev(view->vid, vid);
3332 view->pipe = &view->io;
3333 view->start_time = time(NULL);
3334 }
3336 static bool
3337 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3338 {
3339 view->dir = dir;
3340 return format_argv(&view->argv, argv, replace, !view->prev);
3341 }
3343 static bool
3344 prepare_update(struct view *view, const char *argv[], const char *dir)
3345 {
3346 if (view->pipe)
3347 end_update(view, TRUE);
3348 return prepare_io(view, dir, argv, FALSE);
3349 }
3351 static bool
3352 start_update(struct view *view, const char **argv, const char *dir)
3353 {
3354 if (view->pipe)
3355 io_done(view->pipe);
3356 return prepare_io(view, dir, argv, FALSE) &&
3357 io_run(&view->io, IO_RD, dir, view->argv);
3358 }
3360 static bool
3361 prepare_update_file(struct view *view, const char *name)
3362 {
3363 if (view->pipe)
3364 end_update(view, TRUE);
3365 argv_free(view->argv);
3366 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3367 }
3369 static bool
3370 begin_update(struct view *view, bool refresh)
3371 {
3372 if (view->pipe)
3373 end_update(view, TRUE);
3375 if (!refresh) {
3376 if (view->ops->prepare) {
3377 if (!view->ops->prepare(view))
3378 return FALSE;
3379 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3380 return FALSE;
3381 }
3383 /* Put the current ref_* value to the view title ref
3384 * member. This is needed by the blob view. Most other
3385 * views sets it automatically after loading because the
3386 * first line is a commit line. */
3387 string_copy_rev(view->ref, view->id);
3388 }
3390 if (view->argv && view->argv[0] &&
3391 !io_run(&view->io, IO_RD, view->dir, view->argv))
3392 return FALSE;
3394 setup_update(view, view->id);
3396 return TRUE;
3397 }
3399 static bool
3400 update_view(struct view *view)
3401 {
3402 char out_buffer[BUFSIZ * 2];
3403 char *line;
3404 /* Clear the view and redraw everything since the tree sorting
3405 * might have rearranged things. */
3406 bool redraw = view->lines == 0;
3407 bool can_read = TRUE;
3409 if (!view->pipe)
3410 return TRUE;
3412 if (!io_can_read(view->pipe)) {
3413 if (view->lines == 0 && view_is_displayed(view)) {
3414 time_t secs = time(NULL) - view->start_time;
3416 if (secs > 1 && secs > view->update_secs) {
3417 if (view->update_secs == 0)
3418 redraw_view(view);
3419 update_view_title(view);
3420 view->update_secs = secs;
3421 }
3422 }
3423 return TRUE;
3424 }
3426 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3427 if (opt_iconv_in != ICONV_NONE) {
3428 ICONV_CONST char *inbuf = line;
3429 size_t inlen = strlen(line) + 1;
3431 char *outbuf = out_buffer;
3432 size_t outlen = sizeof(out_buffer);
3434 size_t ret;
3436 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3437 if (ret != (size_t) -1)
3438 line = out_buffer;
3439 }
3441 if (!view->ops->read(view, line)) {
3442 report("Allocation failure");
3443 end_update(view, TRUE);
3444 return FALSE;
3445 }
3446 }
3448 {
3449 unsigned long lines = view->lines;
3450 int digits;
3452 for (digits = 0; lines; digits++)
3453 lines /= 10;
3455 /* Keep the displayed view in sync with line number scaling. */
3456 if (digits != view->digits) {
3457 view->digits = digits;
3458 if (opt_line_number || view->type == VIEW_BLAME)
3459 redraw = TRUE;
3460 }
3461 }
3463 if (io_error(view->pipe)) {
3464 report("Failed to read: %s", io_strerror(view->pipe));
3465 end_update(view, TRUE);
3467 } else if (io_eof(view->pipe)) {
3468 if (view_is_displayed(view))
3469 report("");
3470 end_update(view, FALSE);
3471 }
3473 if (restore_view_position(view))
3474 redraw = TRUE;
3476 if (!view_is_displayed(view))
3477 return TRUE;
3479 if (redraw)
3480 redraw_view_from(view, 0);
3481 else
3482 redraw_view_dirty(view);
3484 /* Update the title _after_ the redraw so that if the redraw picks up a
3485 * commit reference in view->ref it'll be available here. */
3486 update_view_title(view);
3487 return TRUE;
3488 }
3490 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3492 static struct line *
3493 add_line_data(struct view *view, void *data, enum line_type type)
3494 {
3495 struct line *line;
3497 if (!realloc_lines(&view->line, view->lines, 1))
3498 return NULL;
3500 line = &view->line[view->lines++];
3501 memset(line, 0, sizeof(*line));
3502 line->type = type;
3503 line->data = data;
3504 line->dirty = 1;
3506 return line;
3507 }
3509 static struct line *
3510 add_line_text(struct view *view, const char *text, enum line_type type)
3511 {
3512 char *data = text ? strdup(text) : NULL;
3514 return data ? add_line_data(view, data, type) : NULL;
3515 }
3517 static struct line *
3518 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3519 {
3520 char buf[SIZEOF_STR];
3521 va_list args;
3523 va_start(args, fmt);
3524 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3525 buf[0] = 0;
3526 va_end(args);
3528 return buf[0] ? add_line_text(view, buf, type) : NULL;
3529 }
3531 /*
3532 * View opening
3533 */
3535 enum open_flags {
3536 OPEN_DEFAULT = 0, /* Use default view switching. */
3537 OPEN_SPLIT = 1, /* Split current view. */
3538 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3539 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3540 OPEN_PREPARED = 32, /* Open already prepared command. */
3541 };
3543 static void
3544 open_view(struct view *prev, enum request request, enum open_flags flags)
3545 {
3546 bool split = !!(flags & OPEN_SPLIT);
3547 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3548 bool nomaximize = !!(flags & OPEN_REFRESH);
3549 struct view *view = VIEW(request);
3550 int nviews = displayed_views();
3551 struct view *base_view = display[0];
3553 if (view == prev && nviews == 1 && !reload) {
3554 report("Already in %s view", view->name);
3555 return;
3556 }
3558 if (view->git_dir && !opt_git_dir[0]) {
3559 report("The %s view is disabled in pager view", view->name);
3560 return;
3561 }
3563 if (split) {
3564 display[1] = view;
3565 current_view = 1;
3566 view->parent = prev;
3567 } else if (!nomaximize) {
3568 /* Maximize the current view. */
3569 memset(display, 0, sizeof(display));
3570 current_view = 0;
3571 display[current_view] = view;
3572 }
3574 /* No prev signals that this is the first loaded view. */
3575 if (prev && view != prev) {
3576 view->prev = prev;
3577 }
3579 /* Resize the view when switching between split- and full-screen,
3580 * or when switching between two different full-screen views. */
3581 if (nviews != displayed_views() ||
3582 (nviews == 1 && base_view != display[0]))
3583 resize_display();
3585 if (view->ops->open) {
3586 if (view->pipe)
3587 end_update(view, TRUE);
3588 if (!view->ops->open(view)) {
3589 report("Failed to load %s view", view->name);
3590 return;
3591 }
3592 restore_view_position(view);
3594 } else if ((reload || strcmp(view->vid, view->id)) &&
3595 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3596 report("Failed to load %s view", view->name);
3597 return;
3598 }
3600 if (split && prev->lineno - prev->offset >= prev->height) {
3601 /* Take the title line into account. */
3602 int lines = prev->lineno - prev->offset - prev->height + 1;
3604 /* Scroll the view that was split if the current line is
3605 * outside the new limited view. */
3606 do_scroll_view(prev, lines);
3607 }
3609 if (prev && view != prev && split && view_is_displayed(prev)) {
3610 /* "Blur" the previous view. */
3611 update_view_title(prev);
3612 }
3614 if (view->pipe && view->lines == 0) {
3615 /* Clear the old view and let the incremental updating refill
3616 * the screen. */
3617 werase(view->win);
3618 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3619 report("");
3620 } else if (view_is_displayed(view)) {
3621 redraw_view(view);
3622 report("");
3623 }
3624 }
3626 static void
3627 open_external_viewer(const char *argv[], const char *dir)
3628 {
3629 def_prog_mode(); /* save current tty modes */
3630 endwin(); /* restore original tty modes */
3631 io_run_fg(argv, dir);
3632 fprintf(stderr, "Press Enter to continue");
3633 getc(opt_tty);
3634 reset_prog_mode();
3635 redraw_display(TRUE);
3636 }
3638 static void
3639 open_mergetool(const char *file)
3640 {
3641 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3643 open_external_viewer(mergetool_argv, opt_cdup);
3644 }
3646 static void
3647 open_editor(const char *file)
3648 {
3649 const char *editor_argv[] = { "vi", file, NULL };
3650 const char *editor;
3652 editor = getenv("GIT_EDITOR");
3653 if (!editor && *opt_editor)
3654 editor = opt_editor;
3655 if (!editor)
3656 editor = getenv("VISUAL");
3657 if (!editor)
3658 editor = getenv("EDITOR");
3659 if (!editor)
3660 editor = "vi";
3662 editor_argv[0] = editor;
3663 open_external_viewer(editor_argv, opt_cdup);
3664 }
3666 static void
3667 open_run_request(enum request request)
3668 {
3669 struct run_request *req = get_run_request(request);
3670 const char **argv = NULL;
3672 if (!req) {
3673 report("Unknown run request");
3674 return;
3675 }
3677 if (format_argv(&argv, req->argv, TRUE, FALSE))
3678 open_external_viewer(argv, NULL);
3679 if (argv)
3680 argv_free(argv);
3681 free(argv);
3682 }
3684 /*
3685 * User request switch noodle
3686 */
3688 static int
3689 view_driver(struct view *view, enum request request)
3690 {
3691 int i;
3693 if (request == REQ_NONE)
3694 return TRUE;
3696 if (request > REQ_NONE) {
3697 open_run_request(request);
3698 view_request(view, REQ_REFRESH);
3699 return TRUE;
3700 }
3702 request = view_request(view, request);
3703 if (request == REQ_NONE)
3704 return TRUE;
3706 switch (request) {
3707 case REQ_MOVE_UP:
3708 case REQ_MOVE_DOWN:
3709 case REQ_MOVE_PAGE_UP:
3710 case REQ_MOVE_PAGE_DOWN:
3711 case REQ_MOVE_FIRST_LINE:
3712 case REQ_MOVE_LAST_LINE:
3713 move_view(view, request);
3714 break;
3716 case REQ_SCROLL_FIRST_COL:
3717 case REQ_SCROLL_LEFT:
3718 case REQ_SCROLL_RIGHT:
3719 case REQ_SCROLL_LINE_DOWN:
3720 case REQ_SCROLL_LINE_UP:
3721 case REQ_SCROLL_PAGE_DOWN:
3722 case REQ_SCROLL_PAGE_UP:
3723 scroll_view(view, request);
3724 break;
3726 case REQ_VIEW_BLAME:
3727 if (!opt_file[0]) {
3728 report("No file chosen, press %s to open tree view",
3729 get_key(view->keymap, REQ_VIEW_TREE));
3730 break;
3731 }
3732 open_view(view, request, OPEN_DEFAULT);
3733 break;
3735 case REQ_VIEW_BLOB:
3736 if (!ref_blob[0]) {
3737 report("No file chosen, press %s to open tree view",
3738 get_key(view->keymap, REQ_VIEW_TREE));
3739 break;
3740 }
3741 open_view(view, request, OPEN_DEFAULT);
3742 break;
3744 case REQ_VIEW_PAGER:
3745 if (view == NULL) {
3746 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3747 die("Failed to open stdin");
3748 open_view(view, request, OPEN_PREPARED);
3749 break;
3750 }
3752 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3753 report("No pager content, press %s to run command from prompt",
3754 get_key(view->keymap, REQ_PROMPT));
3755 break;
3756 }
3757 open_view(view, request, OPEN_DEFAULT);
3758 break;
3760 case REQ_VIEW_STAGE:
3761 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3762 report("No stage content, press %s to open the status view and choose file",
3763 get_key(view->keymap, REQ_VIEW_STATUS));
3764 break;
3765 }
3766 open_view(view, request, OPEN_DEFAULT);
3767 break;
3769 case REQ_VIEW_STATUS:
3770 if (opt_is_inside_work_tree == FALSE) {
3771 report("The status view requires a working tree");
3772 break;
3773 }
3774 open_view(view, request, OPEN_DEFAULT);
3775 break;
3777 case REQ_VIEW_MAIN:
3778 case REQ_VIEW_DIFF:
3779 case REQ_VIEW_LOG:
3780 case REQ_VIEW_TREE:
3781 case REQ_VIEW_HELP:
3782 case REQ_VIEW_BRANCH:
3783 open_view(view, request, OPEN_DEFAULT);
3784 break;
3786 case REQ_NEXT:
3787 case REQ_PREVIOUS:
3788 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3790 if (view->parent) {
3791 int line;
3793 view = view->parent;
3794 line = view->lineno;
3795 move_view(view, request);
3796 if (view_is_displayed(view))
3797 update_view_title(view);
3798 if (line != view->lineno)
3799 view_request(view, REQ_ENTER);
3800 } else {
3801 move_view(view, request);
3802 }
3803 break;
3805 case REQ_VIEW_NEXT:
3806 {
3807 int nviews = displayed_views();
3808 int next_view = (current_view + 1) % nviews;
3810 if (next_view == current_view) {
3811 report("Only one view is displayed");
3812 break;
3813 }
3815 current_view = next_view;
3816 /* Blur out the title of the previous view. */
3817 update_view_title(view);
3818 report("");
3819 break;
3820 }
3821 case REQ_REFRESH:
3822 report("Refreshing is not yet supported for the %s view", view->name);
3823 break;
3825 case REQ_MAXIMIZE:
3826 if (displayed_views() == 2)
3827 maximize_view(view);
3828 break;
3830 case REQ_OPTIONS:
3831 case REQ_TOGGLE_LINENO:
3832 case REQ_TOGGLE_DATE:
3833 case REQ_TOGGLE_AUTHOR:
3834 case REQ_TOGGLE_REV_GRAPH:
3835 case REQ_TOGGLE_REFS:
3836 toggle_option(request);
3837 break;
3839 case REQ_TOGGLE_SORT_FIELD:
3840 case REQ_TOGGLE_SORT_ORDER:
3841 report("Sorting is not yet supported for the %s view", view->name);
3842 break;
3844 case REQ_SEARCH:
3845 case REQ_SEARCH_BACK:
3846 search_view(view, request);
3847 break;
3849 case REQ_FIND_NEXT:
3850 case REQ_FIND_PREV:
3851 find_next(view, request);
3852 break;
3854 case REQ_STOP_LOADING:
3855 foreach_view(view, i) {
3856 if (view->pipe)
3857 report("Stopped loading the %s view", view->name),
3858 end_update(view, TRUE);
3859 }
3860 break;
3862 case REQ_SHOW_VERSION:
3863 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3864 return TRUE;
3866 case REQ_SCREEN_REDRAW:
3867 redraw_display(TRUE);
3868 break;
3870 case REQ_EDIT:
3871 report("Nothing to edit");
3872 break;
3874 case REQ_ENTER:
3875 report("Nothing to enter");
3876 break;
3878 case REQ_VIEW_CLOSE:
3879 /* XXX: Mark closed views by letting view->prev point to the
3880 * view itself. Parents to closed view should never be
3881 * followed. */
3882 if (view->prev && view->prev != view) {
3883 maximize_view(view->prev);
3884 view->prev = view;
3885 break;
3886 }
3887 /* Fall-through */
3888 case REQ_QUIT:
3889 return FALSE;
3891 default:
3892 report("Unknown key, press %s for help",
3893 get_key(view->keymap, REQ_VIEW_HELP));
3894 return TRUE;
3895 }
3897 return TRUE;
3898 }
3901 /*
3902 * View backend utilities
3903 */
3905 enum sort_field {
3906 ORDERBY_NAME,
3907 ORDERBY_DATE,
3908 ORDERBY_AUTHOR,
3909 };
3911 struct sort_state {
3912 const enum sort_field *fields;
3913 size_t size, current;
3914 bool reverse;
3915 };
3917 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3918 #define get_sort_field(state) ((state).fields[(state).current])
3919 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3921 static void
3922 sort_view(struct view *view, enum request request, struct sort_state *state,
3923 int (*compare)(const void *, const void *))
3924 {
3925 switch (request) {
3926 case REQ_TOGGLE_SORT_FIELD:
3927 state->current = (state->current + 1) % state->size;
3928 break;
3930 case REQ_TOGGLE_SORT_ORDER:
3931 state->reverse = !state->reverse;
3932 break;
3933 default:
3934 die("Not a sort request");
3935 }
3937 qsort(view->line, view->lines, sizeof(*view->line), compare);
3938 redraw_view(view);
3939 }
3941 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3943 /* Small author cache to reduce memory consumption. It uses binary
3944 * search to lookup or find place to position new entries. No entries
3945 * are ever freed. */
3946 static const char *
3947 get_author(const char *name)
3948 {
3949 static const char **authors;
3950 static size_t authors_size;
3951 int from = 0, to = authors_size - 1;
3953 while (from <= to) {
3954 size_t pos = (to + from) / 2;
3955 int cmp = strcmp(name, authors[pos]);
3957 if (!cmp)
3958 return authors[pos];
3960 if (cmp < 0)
3961 to = pos - 1;
3962 else
3963 from = pos + 1;
3964 }
3966 if (!realloc_authors(&authors, authors_size, 1))
3967 return NULL;
3968 name = strdup(name);
3969 if (!name)
3970 return NULL;
3972 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3973 authors[from] = name;
3974 authors_size++;
3976 return name;
3977 }
3979 static void
3980 parse_timesec(struct time *time, const char *sec)
3981 {
3982 time->sec = (time_t) atol(sec);
3983 }
3985 static void
3986 parse_timezone(struct time *time, const char *zone)
3987 {
3988 long tz;
3990 tz = ('0' - zone[1]) * 60 * 60 * 10;
3991 tz += ('0' - zone[2]) * 60 * 60;
3992 tz += ('0' - zone[3]) * 60 * 10;
3993 tz += ('0' - zone[4]) * 60;
3995 if (zone[0] == '-')
3996 tz = -tz;
3998 time->tz = tz;
3999 time->sec -= tz;
4000 }
4002 /* Parse author lines where the name may be empty:
4003 * author <email@address.tld> 1138474660 +0100
4004 */
4005 static void
4006 parse_author_line(char *ident, const char **author, struct time *time)
4007 {
4008 char *nameend = strchr(ident, '<');
4009 char *emailend = strchr(ident, '>');
4011 if (nameend && emailend)
4012 *nameend = *emailend = 0;
4013 ident = chomp_string(ident);
4014 if (!*ident) {
4015 if (nameend)
4016 ident = chomp_string(nameend + 1);
4017 if (!*ident)
4018 ident = "Unknown";
4019 }
4021 *author = get_author(ident);
4023 /* Parse epoch and timezone */
4024 if (emailend && emailend[1] == ' ') {
4025 char *secs = emailend + 2;
4026 char *zone = strchr(secs, ' ');
4028 parse_timesec(time, secs);
4030 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4031 parse_timezone(time, zone + 1);
4032 }
4033 }
4035 /*
4036 * Pager backend
4037 */
4039 static bool
4040 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4041 {
4042 if (opt_line_number && draw_lineno(view, lineno))
4043 return TRUE;
4045 draw_text(view, line->type, line->data, TRUE);
4046 return TRUE;
4047 }
4049 static bool
4050 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4051 {
4052 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4053 char ref[SIZEOF_STR];
4055 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4056 return TRUE;
4058 /* This is the only fatal call, since it can "corrupt" the buffer. */
4059 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4060 return FALSE;
4062 return TRUE;
4063 }
4065 static void
4066 add_pager_refs(struct view *view, struct line *line)
4067 {
4068 char buf[SIZEOF_STR];
4069 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4070 struct ref_list *list;
4071 size_t bufpos = 0, i;
4072 const char *sep = "Refs: ";
4073 bool is_tag = FALSE;
4075 assert(line->type == LINE_COMMIT);
4077 list = get_ref_list(commit_id);
4078 if (!list) {
4079 if (view->type == VIEW_DIFF)
4080 goto try_add_describe_ref;
4081 return;
4082 }
4084 for (i = 0; i < list->size; i++) {
4085 struct ref *ref = list->refs[i];
4086 const char *fmt = ref->tag ? "%s[%s]" :
4087 ref->remote ? "%s<%s>" : "%s%s";
4089 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4090 return;
4091 sep = ", ";
4092 if (ref->tag)
4093 is_tag = TRUE;
4094 }
4096 if (!is_tag && view->type == VIEW_DIFF) {
4097 try_add_describe_ref:
4098 /* Add <tag>-g<commit_id> "fake" reference. */
4099 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4100 return;
4101 }
4103 if (bufpos == 0)
4104 return;
4106 add_line_text(view, buf, LINE_PP_REFS);
4107 }
4109 static bool
4110 pager_read(struct view *view, char *data)
4111 {
4112 struct line *line;
4114 if (!data)
4115 return TRUE;
4117 line = add_line_text(view, data, get_line_type(data));
4118 if (!line)
4119 return FALSE;
4121 if (line->type == LINE_COMMIT &&
4122 (view->type == VIEW_DIFF ||
4123 view->type == VIEW_LOG))
4124 add_pager_refs(view, line);
4126 return TRUE;
4127 }
4129 static enum request
4130 pager_request(struct view *view, enum request request, struct line *line)
4131 {
4132 int split = 0;
4134 if (request != REQ_ENTER)
4135 return request;
4137 if (line->type == LINE_COMMIT &&
4138 (view->type == VIEW_LOG ||
4139 view->type == VIEW_PAGER)) {
4140 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4141 split = 1;
4142 }
4144 /* Always scroll the view even if it was split. That way
4145 * you can use Enter to scroll through the log view and
4146 * split open each commit diff. */
4147 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4149 /* FIXME: A minor workaround. Scrolling the view will call report("")
4150 * but if we are scrolling a non-current view this won't properly
4151 * update the view title. */
4152 if (split)
4153 update_view_title(view);
4155 return REQ_NONE;
4156 }
4158 static bool
4159 pager_grep(struct view *view, struct line *line)
4160 {
4161 const char *text[] = { line->data, NULL };
4163 return grep_text(view, text);
4164 }
4166 static void
4167 pager_select(struct view *view, struct line *line)
4168 {
4169 if (line->type == LINE_COMMIT) {
4170 char *text = (char *)line->data + STRING_SIZE("commit ");
4172 if (view->type != VIEW_PAGER)
4173 string_copy_rev(view->ref, text);
4174 string_copy_rev(ref_commit, text);
4175 }
4176 }
4178 static struct view_ops pager_ops = {
4179 "line",
4180 NULL,
4181 NULL,
4182 pager_read,
4183 pager_draw,
4184 pager_request,
4185 pager_grep,
4186 pager_select,
4187 };
4189 static const char *log_argv[SIZEOF_ARG] = {
4190 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4191 };
4193 static enum request
4194 log_request(struct view *view, enum request request, struct line *line)
4195 {
4196 switch (request) {
4197 case REQ_REFRESH:
4198 load_refs();
4199 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4200 return REQ_NONE;
4201 default:
4202 return pager_request(view, request, line);
4203 }
4204 }
4206 static struct view_ops log_ops = {
4207 "line",
4208 log_argv,
4209 NULL,
4210 pager_read,
4211 pager_draw,
4212 log_request,
4213 pager_grep,
4214 pager_select,
4215 };
4217 static const char *diff_argv[SIZEOF_ARG] = {
4218 "git", "show", "--pretty=fuller", "--no-color", "--root",
4219 "--patch-with-stat", "--find-copies-harder", "-C",
4220 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4221 };
4223 static bool
4224 diff_read(struct view *view, char *data)
4225 {
4226 if (!data) {
4227 /* Fall back to retry if no diff will be shown. */
4228 if (view->lines == 0 && opt_file_argv) {
4229 int pos = argv_size(view->argv)
4230 - argv_size(opt_file_argv) - 1;
4232 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4233 for (; view->argv[pos]; pos++) {
4234 free((void *) view->argv[pos]);
4235 view->argv[pos] = NULL;
4236 }
4238 if (view->pipe)
4239 io_done(view->pipe);
4240 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4241 return FALSE;
4242 }
4243 }
4244 return TRUE;
4245 }
4247 return pager_read(view, data);
4248 }
4250 static struct view_ops diff_ops = {
4251 "line",
4252 diff_argv,
4253 NULL,
4254 diff_read,
4255 pager_draw,
4256 pager_request,
4257 pager_grep,
4258 pager_select,
4259 };
4261 /*
4262 * Help backend
4263 */
4265 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4267 static bool
4268 help_open_keymap_title(struct view *view, enum keymap keymap)
4269 {
4270 struct line *line;
4272 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4273 help_keymap_hidden[keymap] ? '+' : '-',
4274 enum_name(keymap_table[keymap]));
4275 if (line)
4276 line->other = keymap;
4278 return help_keymap_hidden[keymap];
4279 }
4281 static void
4282 help_open_keymap(struct view *view, enum keymap keymap)
4283 {
4284 const char *group = NULL;
4285 char buf[SIZEOF_STR];
4286 size_t bufpos;
4287 bool add_title = TRUE;
4288 int i;
4290 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4291 const char *key = NULL;
4293 if (req_info[i].request == REQ_NONE)
4294 continue;
4296 if (!req_info[i].request) {
4297 group = req_info[i].help;
4298 continue;
4299 }
4301 key = get_keys(keymap, req_info[i].request, TRUE);
4302 if (!key || !*key)
4303 continue;
4305 if (add_title && help_open_keymap_title(view, keymap))
4306 return;
4307 add_title = FALSE;
4309 if (group) {
4310 add_line_text(view, group, LINE_HELP_GROUP);
4311 group = NULL;
4312 }
4314 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4315 enum_name(req_info[i]), req_info[i].help);
4316 }
4318 group = "External commands:";
4320 for (i = 0; i < run_requests; i++) {
4321 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4322 const char *key;
4323 int argc;
4325 if (!req || req->keymap != keymap)
4326 continue;
4328 key = get_key_name(req->key);
4329 if (!*key)
4330 key = "(no key defined)";
4332 if (add_title && help_open_keymap_title(view, keymap))
4333 return;
4334 if (group) {
4335 add_line_text(view, group, LINE_HELP_GROUP);
4336 group = NULL;
4337 }
4339 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4340 if (!string_format_from(buf, &bufpos, "%s%s",
4341 argc ? " " : "", req->argv[argc]))
4342 return;
4344 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4345 }
4346 }
4348 static bool
4349 help_open(struct view *view)
4350 {
4351 enum keymap keymap;
4353 reset_view(view);
4354 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4355 add_line_text(view, "", LINE_DEFAULT);
4357 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4358 help_open_keymap(view, keymap);
4360 return TRUE;
4361 }
4363 static enum request
4364 help_request(struct view *view, enum request request, struct line *line)
4365 {
4366 switch (request) {
4367 case REQ_ENTER:
4368 if (line->type == LINE_HELP_KEYMAP) {
4369 help_keymap_hidden[line->other] =
4370 !help_keymap_hidden[line->other];
4371 view->p_restore = TRUE;
4372 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4373 }
4375 return REQ_NONE;
4376 default:
4377 return pager_request(view, request, line);
4378 }
4379 }
4381 static struct view_ops help_ops = {
4382 "line",
4383 NULL,
4384 help_open,
4385 NULL,
4386 pager_draw,
4387 help_request,
4388 pager_grep,
4389 pager_select,
4390 };
4393 /*
4394 * Tree backend
4395 */
4397 struct tree_stack_entry {
4398 struct tree_stack_entry *prev; /* Entry below this in the stack */
4399 unsigned long lineno; /* Line number to restore */
4400 char *name; /* Position of name in opt_path */
4401 };
4403 /* The top of the path stack. */
4404 static struct tree_stack_entry *tree_stack = NULL;
4405 unsigned long tree_lineno = 0;
4407 static void
4408 pop_tree_stack_entry(void)
4409 {
4410 struct tree_stack_entry *entry = tree_stack;
4412 tree_lineno = entry->lineno;
4413 entry->name[0] = 0;
4414 tree_stack = entry->prev;
4415 free(entry);
4416 }
4418 static void
4419 push_tree_stack_entry(const char *name, unsigned long lineno)
4420 {
4421 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4422 size_t pathlen = strlen(opt_path);
4424 if (!entry)
4425 return;
4427 entry->prev = tree_stack;
4428 entry->name = opt_path + pathlen;
4429 tree_stack = entry;
4431 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4432 pop_tree_stack_entry();
4433 return;
4434 }
4436 /* Move the current line to the first tree entry. */
4437 tree_lineno = 1;
4438 entry->lineno = lineno;
4439 }
4441 /* Parse output from git-ls-tree(1):
4442 *
4443 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4444 */
4446 #define SIZEOF_TREE_ATTR \
4447 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4449 #define SIZEOF_TREE_MODE \
4450 STRING_SIZE("100644 ")
4452 #define TREE_ID_OFFSET \
4453 STRING_SIZE("100644 blob ")
4455 struct tree_entry {
4456 char id[SIZEOF_REV];
4457 mode_t mode;
4458 struct time time; /* Date from the author ident. */
4459 const char *author; /* Author of the commit. */
4460 char name[1];
4461 };
4463 static const char *
4464 tree_path(const struct line *line)
4465 {
4466 return ((struct tree_entry *) line->data)->name;
4467 }
4469 static int
4470 tree_compare_entry(const struct line *line1, const struct line *line2)
4471 {
4472 if (line1->type != line2->type)
4473 return line1->type == LINE_TREE_DIR ? -1 : 1;
4474 return strcmp(tree_path(line1), tree_path(line2));
4475 }
4477 static const enum sort_field tree_sort_fields[] = {
4478 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4479 };
4480 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4482 static int
4483 tree_compare(const void *l1, const void *l2)
4484 {
4485 const struct line *line1 = (const struct line *) l1;
4486 const struct line *line2 = (const struct line *) l2;
4487 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4488 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4490 if (line1->type == LINE_TREE_HEAD)
4491 return -1;
4492 if (line2->type == LINE_TREE_HEAD)
4493 return 1;
4495 switch (get_sort_field(tree_sort_state)) {
4496 case ORDERBY_DATE:
4497 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4499 case ORDERBY_AUTHOR:
4500 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4502 case ORDERBY_NAME:
4503 default:
4504 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4505 }
4506 }
4509 static struct line *
4510 tree_entry(struct view *view, enum line_type type, const char *path,
4511 const char *mode, const char *id)
4512 {
4513 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4514 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4516 if (!entry || !line) {
4517 free(entry);
4518 return NULL;
4519 }
4521 strncpy(entry->name, path, strlen(path));
4522 if (mode)
4523 entry->mode = strtoul(mode, NULL, 8);
4524 if (id)
4525 string_copy_rev(entry->id, id);
4527 return line;
4528 }
4530 static bool
4531 tree_read_date(struct view *view, char *text, bool *read_date)
4532 {
4533 static const char *author_name;
4534 static struct time author_time;
4536 if (!text && *read_date) {
4537 *read_date = FALSE;
4538 return TRUE;
4540 } else if (!text) {
4541 char *path = *opt_path ? opt_path : ".";
4542 /* Find next entry to process */
4543 const char *log_file[] = {
4544 "git", "log", "--no-color", "--pretty=raw",
4545 "--cc", "--raw", view->id, "--", path, NULL
4546 };
4548 if (!view->lines) {
4549 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4550 report("Tree is empty");
4551 return TRUE;
4552 }
4554 if (!start_update(view, log_file, opt_cdup)) {
4555 report("Failed to load tree data");
4556 return TRUE;
4557 }
4559 *read_date = TRUE;
4560 return FALSE;
4562 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4563 parse_author_line(text + STRING_SIZE("author "),
4564 &author_name, &author_time);
4566 } else if (*text == ':') {
4567 char *pos;
4568 size_t annotated = 1;
4569 size_t i;
4571 pos = strchr(text, '\t');
4572 if (!pos)
4573 return TRUE;
4574 text = pos + 1;
4575 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4576 text += strlen(opt_path);
4577 pos = strchr(text, '/');
4578 if (pos)
4579 *pos = 0;
4581 for (i = 1; i < view->lines; i++) {
4582 struct line *line = &view->line[i];
4583 struct tree_entry *entry = line->data;
4585 annotated += !!entry->author;
4586 if (entry->author || strcmp(entry->name, text))
4587 continue;
4589 entry->author = author_name;
4590 entry->time = author_time;
4591 line->dirty = 1;
4592 break;
4593 }
4595 if (annotated == view->lines)
4596 io_kill(view->pipe);
4597 }
4598 return TRUE;
4599 }
4601 static bool
4602 tree_read(struct view *view, char *text)
4603 {
4604 static bool read_date = FALSE;
4605 struct tree_entry *data;
4606 struct line *entry, *line;
4607 enum line_type type;
4608 size_t textlen = text ? strlen(text) : 0;
4609 char *path = text + SIZEOF_TREE_ATTR;
4611 if (read_date || !text)
4612 return tree_read_date(view, text, &read_date);
4614 if (textlen <= SIZEOF_TREE_ATTR)
4615 return FALSE;
4616 if (view->lines == 0 &&
4617 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4618 return FALSE;
4620 /* Strip the path part ... */
4621 if (*opt_path) {
4622 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4623 size_t striplen = strlen(opt_path);
4625 if (pathlen > striplen)
4626 memmove(path, path + striplen,
4627 pathlen - striplen + 1);
4629 /* Insert "link" to parent directory. */
4630 if (view->lines == 1 &&
4631 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4632 return FALSE;
4633 }
4635 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4636 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4637 if (!entry)
4638 return FALSE;
4639 data = entry->data;
4641 /* Skip "Directory ..." and ".." line. */
4642 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4643 if (tree_compare_entry(line, entry) <= 0)
4644 continue;
4646 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4648 line->data = data;
4649 line->type = type;
4650 for (; line <= entry; line++)
4651 line->dirty = line->cleareol = 1;
4652 return TRUE;
4653 }
4655 if (tree_lineno > view->lineno) {
4656 view->lineno = tree_lineno;
4657 tree_lineno = 0;
4658 }
4660 return TRUE;
4661 }
4663 static bool
4664 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4665 {
4666 struct tree_entry *entry = line->data;
4668 if (line->type == LINE_TREE_HEAD) {
4669 if (draw_text(view, line->type, "Directory path /", TRUE))
4670 return TRUE;
4671 } else {
4672 if (draw_mode(view, entry->mode))
4673 return TRUE;
4675 if (opt_author && draw_author(view, entry->author))
4676 return TRUE;
4678 if (opt_date && draw_date(view, &entry->time))
4679 return TRUE;
4680 }
4681 if (draw_text(view, line->type, entry->name, TRUE))
4682 return TRUE;
4683 return TRUE;
4684 }
4686 static void
4687 open_blob_editor(const char *id)
4688 {
4689 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4690 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4691 int fd = mkstemp(file);
4693 if (fd == -1)
4694 report("Failed to create temporary file");
4695 else if (!io_run_append(blob_argv, fd))
4696 report("Failed to save blob data to file");
4697 else
4698 open_editor(file);
4699 if (fd != -1)
4700 unlink(file);
4701 }
4703 static enum request
4704 tree_request(struct view *view, enum request request, struct line *line)
4705 {
4706 enum open_flags flags;
4707 struct tree_entry *entry = line->data;
4709 switch (request) {
4710 case REQ_VIEW_BLAME:
4711 if (line->type != LINE_TREE_FILE) {
4712 report("Blame only supported for files");
4713 return REQ_NONE;
4714 }
4716 string_copy(opt_ref, view->vid);
4717 return request;
4719 case REQ_EDIT:
4720 if (line->type != LINE_TREE_FILE) {
4721 report("Edit only supported for files");
4722 } else if (!is_head_commit(view->vid)) {
4723 open_blob_editor(entry->id);
4724 } else {
4725 open_editor(opt_file);
4726 }
4727 return REQ_NONE;
4729 case REQ_TOGGLE_SORT_FIELD:
4730 case REQ_TOGGLE_SORT_ORDER:
4731 sort_view(view, request, &tree_sort_state, tree_compare);
4732 return REQ_NONE;
4734 case REQ_PARENT:
4735 if (!*opt_path) {
4736 /* quit view if at top of tree */
4737 return REQ_VIEW_CLOSE;
4738 }
4739 /* fake 'cd ..' */
4740 line = &view->line[1];
4741 break;
4743 case REQ_ENTER:
4744 break;
4746 default:
4747 return request;
4748 }
4750 /* Cleanup the stack if the tree view is at a different tree. */
4751 while (!*opt_path && tree_stack)
4752 pop_tree_stack_entry();
4754 switch (line->type) {
4755 case LINE_TREE_DIR:
4756 /* Depending on whether it is a subdirectory or parent link
4757 * mangle the path buffer. */
4758 if (line == &view->line[1] && *opt_path) {
4759 pop_tree_stack_entry();
4761 } else {
4762 const char *basename = tree_path(line);
4764 push_tree_stack_entry(basename, view->lineno);
4765 }
4767 /* Trees and subtrees share the same ID, so they are not not
4768 * unique like blobs. */
4769 flags = OPEN_RELOAD;
4770 request = REQ_VIEW_TREE;
4771 break;
4773 case LINE_TREE_FILE:
4774 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4775 request = REQ_VIEW_BLOB;
4776 break;
4778 default:
4779 return REQ_NONE;
4780 }
4782 open_view(view, request, flags);
4783 if (request == REQ_VIEW_TREE)
4784 view->lineno = tree_lineno;
4786 return REQ_NONE;
4787 }
4789 static bool
4790 tree_grep(struct view *view, struct line *line)
4791 {
4792 struct tree_entry *entry = line->data;
4793 const char *text[] = {
4794 entry->name,
4795 opt_author ? entry->author : "",
4796 mkdate(&entry->time, opt_date),
4797 NULL
4798 };
4800 return grep_text(view, text);
4801 }
4803 static void
4804 tree_select(struct view *view, struct line *line)
4805 {
4806 struct tree_entry *entry = line->data;
4808 if (line->type == LINE_TREE_FILE) {
4809 string_copy_rev(ref_blob, entry->id);
4810 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4812 } else if (line->type != LINE_TREE_DIR) {
4813 return;
4814 }
4816 string_copy_rev(view->ref, entry->id);
4817 }
4819 static bool
4820 tree_prepare(struct view *view)
4821 {
4822 if (view->lines == 0 && opt_prefix[0]) {
4823 char *pos = opt_prefix;
4825 while (pos && *pos) {
4826 char *end = strchr(pos, '/');
4828 if (end)
4829 *end = 0;
4830 push_tree_stack_entry(pos, 0);
4831 pos = end;
4832 if (end) {
4833 *end = '/';
4834 pos++;
4835 }
4836 }
4838 } else if (strcmp(view->vid, view->id)) {
4839 opt_path[0] = 0;
4840 }
4842 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4843 }
4845 static const char *tree_argv[SIZEOF_ARG] = {
4846 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4847 };
4849 static struct view_ops tree_ops = {
4850 "file",
4851 tree_argv,
4852 NULL,
4853 tree_read,
4854 tree_draw,
4855 tree_request,
4856 tree_grep,
4857 tree_select,
4858 tree_prepare,
4859 };
4861 static bool
4862 blob_read(struct view *view, char *line)
4863 {
4864 if (!line)
4865 return TRUE;
4866 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4867 }
4869 static enum request
4870 blob_request(struct view *view, enum request request, struct line *line)
4871 {
4872 switch (request) {
4873 case REQ_EDIT:
4874 open_blob_editor(view->vid);
4875 return REQ_NONE;
4876 default:
4877 return pager_request(view, request, line);
4878 }
4879 }
4881 static const char *blob_argv[SIZEOF_ARG] = {
4882 "git", "cat-file", "blob", "%(blob)", NULL
4883 };
4885 static struct view_ops blob_ops = {
4886 "line",
4887 blob_argv,
4888 NULL,
4889 blob_read,
4890 pager_draw,
4891 blob_request,
4892 pager_grep,
4893 pager_select,
4894 };
4896 /*
4897 * Blame backend
4898 *
4899 * Loading the blame view is a two phase job:
4900 *
4901 * 1. File content is read either using opt_file from the
4902 * filesystem or using git-cat-file.
4903 * 2. Then blame information is incrementally added by
4904 * reading output from git-blame.
4905 */
4907 struct blame_commit {
4908 char id[SIZEOF_REV]; /* SHA1 ID. */
4909 char title[128]; /* First line of the commit message. */
4910 const char *author; /* Author of the commit. */
4911 struct time time; /* Date from the author ident. */
4912 char filename[128]; /* Name of file. */
4913 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4914 char parent_filename[128]; /* Parent/previous name of file. */
4915 };
4917 struct blame {
4918 struct blame_commit *commit;
4919 unsigned long lineno;
4920 char text[1];
4921 };
4923 static bool
4924 blame_open(struct view *view)
4925 {
4926 char path[SIZEOF_STR];
4927 size_t i;
4929 if (!view->prev && *opt_prefix) {
4930 string_copy(path, opt_file);
4931 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4932 return FALSE;
4933 }
4935 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4936 const char *blame_cat_file_argv[] = {
4937 "git", "cat-file", "blob", path, NULL
4938 };
4940 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4941 !start_update(view, blame_cat_file_argv, opt_cdup))
4942 return FALSE;
4943 }
4945 /* First pass: remove multiple references to the same commit. */
4946 for (i = 0; i < view->lines; i++) {
4947 struct blame *blame = view->line[i].data;
4949 if (blame->commit && blame->commit->id[0])
4950 blame->commit->id[0] = 0;
4951 else
4952 blame->commit = NULL;
4953 }
4955 /* Second pass: free existing references. */
4956 for (i = 0; i < view->lines; i++) {
4957 struct blame *blame = view->line[i].data;
4959 if (blame->commit)
4960 free(blame->commit);
4961 }
4963 setup_update(view, opt_file);
4964 string_format(view->ref, "%s ...", opt_file);
4966 return TRUE;
4967 }
4969 static struct blame_commit *
4970 get_blame_commit(struct view *view, const char *id)
4971 {
4972 size_t i;
4974 for (i = 0; i < view->lines; i++) {
4975 struct blame *blame = view->line[i].data;
4977 if (!blame->commit)
4978 continue;
4980 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4981 return blame->commit;
4982 }
4984 {
4985 struct blame_commit *commit = calloc(1, sizeof(*commit));
4987 if (commit)
4988 string_ncopy(commit->id, id, SIZEOF_REV);
4989 return commit;
4990 }
4991 }
4993 static bool
4994 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4995 {
4996 const char *pos = *posref;
4998 *posref = NULL;
4999 pos = strchr(pos + 1, ' ');
5000 if (!pos || !isdigit(pos[1]))
5001 return FALSE;
5002 *number = atoi(pos + 1);
5003 if (*number < min || *number > max)
5004 return FALSE;
5006 *posref = pos;
5007 return TRUE;
5008 }
5010 static struct blame_commit *
5011 parse_blame_commit(struct view *view, const char *text, int *blamed)
5012 {
5013 struct blame_commit *commit;
5014 struct blame *blame;
5015 const char *pos = text + SIZEOF_REV - 2;
5016 size_t orig_lineno = 0;
5017 size_t lineno;
5018 size_t group;
5020 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5021 return NULL;
5023 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5024 !parse_number(&pos, &lineno, 1, view->lines) ||
5025 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5026 return NULL;
5028 commit = get_blame_commit(view, text);
5029 if (!commit)
5030 return NULL;
5032 *blamed += group;
5033 while (group--) {
5034 struct line *line = &view->line[lineno + group - 1];
5036 blame = line->data;
5037 blame->commit = commit;
5038 blame->lineno = orig_lineno + group - 1;
5039 line->dirty = 1;
5040 }
5042 return commit;
5043 }
5045 static bool
5046 blame_read_file(struct view *view, const char *line, bool *read_file)
5047 {
5048 if (!line) {
5049 const char *blame_argv[] = {
5050 "git", "blame", "--incremental",
5051 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5052 };
5054 if (view->lines == 0 && !view->prev)
5055 die("No blame exist for %s", view->vid);
5057 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5058 report("Failed to load blame data");
5059 return TRUE;
5060 }
5062 *read_file = FALSE;
5063 return FALSE;
5065 } else {
5066 size_t linelen = strlen(line);
5067 struct blame *blame = malloc(sizeof(*blame) + linelen);
5069 if (!blame)
5070 return FALSE;
5072 blame->commit = NULL;
5073 strncpy(blame->text, line, linelen);
5074 blame->text[linelen] = 0;
5075 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5076 }
5077 }
5079 static bool
5080 match_blame_header(const char *name, char **line)
5081 {
5082 size_t namelen = strlen(name);
5083 bool matched = !strncmp(name, *line, namelen);
5085 if (matched)
5086 *line += namelen;
5088 return matched;
5089 }
5091 static bool
5092 blame_read(struct view *view, char *line)
5093 {
5094 static struct blame_commit *commit = NULL;
5095 static int blamed = 0;
5096 static bool read_file = TRUE;
5098 if (read_file)
5099 return blame_read_file(view, line, &read_file);
5101 if (!line) {
5102 /* Reset all! */
5103 commit = NULL;
5104 blamed = 0;
5105 read_file = TRUE;
5106 string_format(view->ref, "%s", view->vid);
5107 if (view_is_displayed(view)) {
5108 update_view_title(view);
5109 redraw_view_from(view, 0);
5110 }
5111 return TRUE;
5112 }
5114 if (!commit) {
5115 commit = parse_blame_commit(view, line, &blamed);
5116 string_format(view->ref, "%s %2d%%", view->vid,
5117 view->lines ? blamed * 100 / view->lines : 0);
5119 } else if (match_blame_header("author ", &line)) {
5120 commit->author = get_author(line);
5122 } else if (match_blame_header("author-time ", &line)) {
5123 parse_timesec(&commit->time, line);
5125 } else if (match_blame_header("author-tz ", &line)) {
5126 parse_timezone(&commit->time, line);
5128 } else if (match_blame_header("summary ", &line)) {
5129 string_ncopy(commit->title, line, strlen(line));
5131 } else if (match_blame_header("previous ", &line)) {
5132 if (strlen(line) <= SIZEOF_REV)
5133 return FALSE;
5134 string_copy_rev(commit->parent_id, line);
5135 line += SIZEOF_REV;
5136 string_ncopy(commit->parent_filename, line, strlen(line));
5138 } else if (match_blame_header("filename ", &line)) {
5139 string_ncopy(commit->filename, line, strlen(line));
5140 commit = NULL;
5141 }
5143 return TRUE;
5144 }
5146 static bool
5147 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5148 {
5149 struct blame *blame = line->data;
5150 struct time *time = NULL;
5151 const char *id = NULL, *author = NULL;
5153 if (blame->commit && *blame->commit->filename) {
5154 id = blame->commit->id;
5155 author = blame->commit->author;
5156 time = &blame->commit->time;
5157 }
5159 if (opt_date && draw_date(view, time))
5160 return TRUE;
5162 if (opt_author && draw_author(view, author))
5163 return TRUE;
5165 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5166 return TRUE;
5168 if (draw_lineno(view, lineno))
5169 return TRUE;
5171 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5172 return TRUE;
5173 }
5175 static bool
5176 check_blame_commit(struct blame *blame, bool check_null_id)
5177 {
5178 if (!blame->commit)
5179 report("Commit data not loaded yet");
5180 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5181 report("No commit exist for the selected line");
5182 else
5183 return TRUE;
5184 return FALSE;
5185 }
5187 static void
5188 setup_blame_parent_line(struct view *view, struct blame *blame)
5189 {
5190 char from[SIZEOF_REF + SIZEOF_STR];
5191 char to[SIZEOF_REF + SIZEOF_STR];
5192 const char *diff_tree_argv[] = {
5193 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5194 "-U0", from, to, "--", NULL
5195 };
5196 struct io io;
5197 int parent_lineno = -1;
5198 int blamed_lineno = -1;
5199 char *line;
5201 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5202 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5203 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5204 return;
5206 while ((line = io_get(&io, '\n', TRUE))) {
5207 if (*line == '@') {
5208 char *pos = strchr(line, '+');
5210 parent_lineno = atoi(line + 4);
5211 if (pos)
5212 blamed_lineno = atoi(pos + 1);
5214 } else if (*line == '+' && parent_lineno != -1) {
5215 if (blame->lineno == blamed_lineno - 1 &&
5216 !strcmp(blame->text, line + 1)) {
5217 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5218 break;
5219 }
5220 blamed_lineno++;
5221 }
5222 }
5224 io_done(&io);
5225 }
5227 static enum request
5228 blame_request(struct view *view, enum request request, struct line *line)
5229 {
5230 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5231 struct blame *blame = line->data;
5233 switch (request) {
5234 case REQ_VIEW_BLAME:
5235 if (check_blame_commit(blame, TRUE)) {
5236 string_copy(opt_ref, blame->commit->id);
5237 string_copy(opt_file, blame->commit->filename);
5238 if (blame->lineno)
5239 view->lineno = blame->lineno;
5240 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5241 }
5242 break;
5244 case REQ_PARENT:
5245 if (!check_blame_commit(blame, TRUE))
5246 break;
5247 if (!*blame->commit->parent_id) {
5248 report("The selected commit has no parents");
5249 } else {
5250 string_copy_rev(opt_ref, blame->commit->parent_id);
5251 string_copy(opt_file, blame->commit->parent_filename);
5252 setup_blame_parent_line(view, blame);
5253 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5254 }
5255 break;
5257 case REQ_ENTER:
5258 if (!check_blame_commit(blame, FALSE))
5259 break;
5261 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5262 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5263 break;
5265 if (!strcmp(blame->commit->id, NULL_ID)) {
5266 struct view *diff = VIEW(REQ_VIEW_DIFF);
5267 const char *diff_index_argv[] = {
5268 "git", "diff-index", "--root", "--patch-with-stat",
5269 "-C", "-M", "HEAD", "--", view->vid, NULL
5270 };
5272 if (!*blame->commit->parent_id) {
5273 diff_index_argv[1] = "diff";
5274 diff_index_argv[2] = "--no-color";
5275 diff_index_argv[6] = "--";
5276 diff_index_argv[7] = "/dev/null";
5277 }
5279 if (!prepare_update(diff, diff_index_argv, NULL)) {
5280 report("Failed to allocate diff command");
5281 break;
5282 }
5283 flags |= OPEN_PREPARED;
5284 }
5286 open_view(view, REQ_VIEW_DIFF, flags);
5287 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5288 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5289 break;
5291 default:
5292 return request;
5293 }
5295 return REQ_NONE;
5296 }
5298 static bool
5299 blame_grep(struct view *view, struct line *line)
5300 {
5301 struct blame *blame = line->data;
5302 struct blame_commit *commit = blame->commit;
5303 const char *text[] = {
5304 blame->text,
5305 commit ? commit->title : "",
5306 commit ? commit->id : "",
5307 commit && opt_author ? commit->author : "",
5308 commit ? mkdate(&commit->time, opt_date) : "",
5309 NULL
5310 };
5312 return grep_text(view, text);
5313 }
5315 static void
5316 blame_select(struct view *view, struct line *line)
5317 {
5318 struct blame *blame = line->data;
5319 struct blame_commit *commit = blame->commit;
5321 if (!commit)
5322 return;
5324 if (!strcmp(commit->id, NULL_ID))
5325 string_ncopy(ref_commit, "HEAD", 4);
5326 else
5327 string_copy_rev(ref_commit, commit->id);
5328 }
5330 static struct view_ops blame_ops = {
5331 "line",
5332 NULL,
5333 blame_open,
5334 blame_read,
5335 blame_draw,
5336 blame_request,
5337 blame_grep,
5338 blame_select,
5339 };
5341 /*
5342 * Branch backend
5343 */
5345 struct branch {
5346 const char *author; /* Author of the last commit. */
5347 struct time time; /* Date of the last activity. */
5348 const struct ref *ref; /* Name and commit ID information. */
5349 };
5351 static const struct ref branch_all;
5353 static const enum sort_field branch_sort_fields[] = {
5354 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5355 };
5356 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5358 static int
5359 branch_compare(const void *l1, const void *l2)
5360 {
5361 const struct branch *branch1 = ((const struct line *) l1)->data;
5362 const struct branch *branch2 = ((const struct line *) l2)->data;
5364 switch (get_sort_field(branch_sort_state)) {
5365 case ORDERBY_DATE:
5366 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5368 case ORDERBY_AUTHOR:
5369 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5371 case ORDERBY_NAME:
5372 default:
5373 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5374 }
5375 }
5377 static bool
5378 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5379 {
5380 struct branch *branch = line->data;
5381 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5383 if (opt_date && draw_date(view, &branch->time))
5384 return TRUE;
5386 if (opt_author && draw_author(view, branch->author))
5387 return TRUE;
5389 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5390 return TRUE;
5391 }
5393 static enum request
5394 branch_request(struct view *view, enum request request, struct line *line)
5395 {
5396 struct branch *branch = line->data;
5398 switch (request) {
5399 case REQ_REFRESH:
5400 load_refs();
5401 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5402 return REQ_NONE;
5404 case REQ_TOGGLE_SORT_FIELD:
5405 case REQ_TOGGLE_SORT_ORDER:
5406 sort_view(view, request, &branch_sort_state, branch_compare);
5407 return REQ_NONE;
5409 case REQ_ENTER:
5410 {
5411 const struct ref *ref = branch->ref;
5412 const char *all_branches_argv[] = {
5413 "git", "log", "--no-color", "--pretty=raw", "--parents",
5414 "--topo-order",
5415 ref == &branch_all ? "--all" : ref->name, NULL
5416 };
5417 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5419 if (!prepare_update(main_view, all_branches_argv, NULL))
5420 report("Failed to load view of all branches");
5421 else
5422 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5423 return REQ_NONE;
5424 }
5425 default:
5426 return request;
5427 }
5428 }
5430 static bool
5431 branch_read(struct view *view, char *line)
5432 {
5433 static char id[SIZEOF_REV];
5434 struct branch *reference;
5435 size_t i;
5437 if (!line)
5438 return TRUE;
5440 switch (get_line_type(line)) {
5441 case LINE_COMMIT:
5442 string_copy_rev(id, line + STRING_SIZE("commit "));
5443 return TRUE;
5445 case LINE_AUTHOR:
5446 for (i = 0, reference = NULL; i < view->lines; i++) {
5447 struct branch *branch = view->line[i].data;
5449 if (strcmp(branch->ref->id, id))
5450 continue;
5452 view->line[i].dirty = TRUE;
5453 if (reference) {
5454 branch->author = reference->author;
5455 branch->time = reference->time;
5456 continue;
5457 }
5459 parse_author_line(line + STRING_SIZE("author "),
5460 &branch->author, &branch->time);
5461 reference = branch;
5462 }
5463 return TRUE;
5465 default:
5466 return TRUE;
5467 }
5469 }
5471 static bool
5472 branch_open_visitor(void *data, const struct ref *ref)
5473 {
5474 struct view *view = data;
5475 struct branch *branch;
5477 if (ref->tag || ref->ltag || ref->remote)
5478 return TRUE;
5480 branch = calloc(1, sizeof(*branch));
5481 if (!branch)
5482 return FALSE;
5484 branch->ref = ref;
5485 return !!add_line_data(view, branch, LINE_DEFAULT);
5486 }
5488 static bool
5489 branch_open(struct view *view)
5490 {
5491 const char *branch_log[] = {
5492 "git", "log", "--no-color", "--pretty=raw",
5493 "--simplify-by-decoration", "--all", NULL
5494 };
5496 if (!start_update(view, branch_log, NULL)) {
5497 report("Failed to load branch data");
5498 return TRUE;
5499 }
5501 setup_update(view, view->id);
5502 branch_open_visitor(view, &branch_all);
5503 foreach_ref(branch_open_visitor, view);
5504 view->p_restore = TRUE;
5506 return TRUE;
5507 }
5509 static bool
5510 branch_grep(struct view *view, struct line *line)
5511 {
5512 struct branch *branch = line->data;
5513 const char *text[] = {
5514 branch->ref->name,
5515 branch->author,
5516 NULL
5517 };
5519 return grep_text(view, text);
5520 }
5522 static void
5523 branch_select(struct view *view, struct line *line)
5524 {
5525 struct branch *branch = line->data;
5527 string_copy_rev(view->ref, branch->ref->id);
5528 string_copy_rev(ref_commit, branch->ref->id);
5529 string_copy_rev(ref_head, branch->ref->id);
5530 string_copy_rev(ref_branch, branch->ref->name);
5531 }
5533 static struct view_ops branch_ops = {
5534 "branch",
5535 NULL,
5536 branch_open,
5537 branch_read,
5538 branch_draw,
5539 branch_request,
5540 branch_grep,
5541 branch_select,
5542 };
5544 /*
5545 * Status backend
5546 */
5548 struct status {
5549 char status;
5550 struct {
5551 mode_t mode;
5552 char rev[SIZEOF_REV];
5553 char name[SIZEOF_STR];
5554 } old;
5555 struct {
5556 mode_t mode;
5557 char rev[SIZEOF_REV];
5558 char name[SIZEOF_STR];
5559 } new;
5560 };
5562 static char status_onbranch[SIZEOF_STR];
5563 static struct status stage_status;
5564 static enum line_type stage_line_type;
5565 static size_t stage_chunks;
5566 static int *stage_chunk;
5568 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5570 /* This should work even for the "On branch" line. */
5571 static inline bool
5572 status_has_none(struct view *view, struct line *line)
5573 {
5574 return line < view->line + view->lines && !line[1].data;
5575 }
5577 /* Get fields from the diff line:
5578 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5579 */
5580 static inline bool
5581 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5582 {
5583 const char *old_mode = buf + 1;
5584 const char *new_mode = buf + 8;
5585 const char *old_rev = buf + 15;
5586 const char *new_rev = buf + 56;
5587 const char *status = buf + 97;
5589 if (bufsize < 98 ||
5590 old_mode[-1] != ':' ||
5591 new_mode[-1] != ' ' ||
5592 old_rev[-1] != ' ' ||
5593 new_rev[-1] != ' ' ||
5594 status[-1] != ' ')
5595 return FALSE;
5597 file->status = *status;
5599 string_copy_rev(file->old.rev, old_rev);
5600 string_copy_rev(file->new.rev, new_rev);
5602 file->old.mode = strtoul(old_mode, NULL, 8);
5603 file->new.mode = strtoul(new_mode, NULL, 8);
5605 file->old.name[0] = file->new.name[0] = 0;
5607 return TRUE;
5608 }
5610 static bool
5611 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5612 {
5613 struct status *unmerged = NULL;
5614 char *buf;
5615 struct io io;
5617 if (!io_run(&io, IO_RD, opt_cdup, argv))
5618 return FALSE;
5620 add_line_data(view, NULL, type);
5622 while ((buf = io_get(&io, 0, TRUE))) {
5623 struct status *file = unmerged;
5625 if (!file) {
5626 file = calloc(1, sizeof(*file));
5627 if (!file || !add_line_data(view, file, type))
5628 goto error_out;
5629 }
5631 /* Parse diff info part. */
5632 if (status) {
5633 file->status = status;
5634 if (status == 'A')
5635 string_copy(file->old.rev, NULL_ID);
5637 } else if (!file->status || file == unmerged) {
5638 if (!status_get_diff(file, buf, strlen(buf)))
5639 goto error_out;
5641 buf = io_get(&io, 0, TRUE);
5642 if (!buf)
5643 break;
5645 /* Collapse all modified entries that follow an
5646 * associated unmerged entry. */
5647 if (unmerged == file) {
5648 unmerged->status = 'U';
5649 unmerged = NULL;
5650 } else if (file->status == 'U') {
5651 unmerged = file;
5652 }
5653 }
5655 /* Grab the old name for rename/copy. */
5656 if (!*file->old.name &&
5657 (file->status == 'R' || file->status == 'C')) {
5658 string_ncopy(file->old.name, buf, strlen(buf));
5660 buf = io_get(&io, 0, TRUE);
5661 if (!buf)
5662 break;
5663 }
5665 /* git-ls-files just delivers a NUL separated list of
5666 * file names similar to the second half of the
5667 * git-diff-* output. */
5668 string_ncopy(file->new.name, buf, strlen(buf));
5669 if (!*file->old.name)
5670 string_copy(file->old.name, file->new.name);
5671 file = NULL;
5672 }
5674 if (io_error(&io)) {
5675 error_out:
5676 io_done(&io);
5677 return FALSE;
5678 }
5680 if (!view->line[view->lines - 1].data)
5681 add_line_data(view, NULL, LINE_STAT_NONE);
5683 io_done(&io);
5684 return TRUE;
5685 }
5687 /* Don't show unmerged entries in the staged section. */
5688 static const char *status_diff_index_argv[] = {
5689 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5690 "--cached", "-M", "HEAD", NULL
5691 };
5693 static const char *status_diff_files_argv[] = {
5694 "git", "diff-files", "-z", NULL
5695 };
5697 static const char *status_list_other_argv[] = {
5698 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5699 };
5701 static const char *status_list_no_head_argv[] = {
5702 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5703 };
5705 static const char *update_index_argv[] = {
5706 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5707 };
5709 /* Restore the previous line number to stay in the context or select a
5710 * line with something that can be updated. */
5711 static void
5712 status_restore(struct view *view)
5713 {
5714 if (view->p_lineno >= view->lines)
5715 view->p_lineno = view->lines - 1;
5716 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5717 view->p_lineno++;
5718 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5719 view->p_lineno--;
5721 /* If the above fails, always skip the "On branch" line. */
5722 if (view->p_lineno < view->lines)
5723 view->lineno = view->p_lineno;
5724 else
5725 view->lineno = 1;
5727 if (view->lineno < view->offset)
5728 view->offset = view->lineno;
5729 else if (view->offset + view->height <= view->lineno)
5730 view->offset = view->lineno - view->height + 1;
5732 view->p_restore = FALSE;
5733 }
5735 static void
5736 status_update_onbranch(void)
5737 {
5738 static const char *paths[][2] = {
5739 { "rebase-apply/rebasing", "Rebasing" },
5740 { "rebase-apply/applying", "Applying mailbox" },
5741 { "rebase-apply/", "Rebasing mailbox" },
5742 { "rebase-merge/interactive", "Interactive rebase" },
5743 { "rebase-merge/", "Rebase merge" },
5744 { "MERGE_HEAD", "Merging" },
5745 { "BISECT_LOG", "Bisecting" },
5746 { "HEAD", "On branch" },
5747 };
5748 char buf[SIZEOF_STR];
5749 struct stat stat;
5750 int i;
5752 if (is_initial_commit()) {
5753 string_copy(status_onbranch, "Initial commit");
5754 return;
5755 }
5757 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5758 char *head = opt_head;
5760 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5761 lstat(buf, &stat) < 0)
5762 continue;
5764 if (!*opt_head) {
5765 struct io io;
5767 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5768 io_read_buf(&io, buf, sizeof(buf))) {
5769 head = buf;
5770 if (!prefixcmp(head, "refs/heads/"))
5771 head += STRING_SIZE("refs/heads/");
5772 }
5773 }
5775 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5776 string_copy(status_onbranch, opt_head);
5777 return;
5778 }
5780 string_copy(status_onbranch, "Not currently on any branch");
5781 }
5783 /* First parse staged info using git-diff-index(1), then parse unstaged
5784 * info using git-diff-files(1), and finally untracked files using
5785 * git-ls-files(1). */
5786 static bool
5787 status_open(struct view *view)
5788 {
5789 reset_view(view);
5791 add_line_data(view, NULL, LINE_STAT_HEAD);
5792 status_update_onbranch();
5794 io_run_bg(update_index_argv);
5796 if (is_initial_commit()) {
5797 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5798 return FALSE;
5799 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5800 return FALSE;
5801 }
5803 if (!opt_untracked_dirs_content)
5804 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5806 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5807 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5808 return FALSE;
5810 /* Restore the exact position or use the specialized restore
5811 * mode? */
5812 if (!view->p_restore)
5813 status_restore(view);
5814 return TRUE;
5815 }
5817 static bool
5818 status_draw(struct view *view, struct line *line, unsigned int lineno)
5819 {
5820 struct status *status = line->data;
5821 enum line_type type;
5822 const char *text;
5824 if (!status) {
5825 switch (line->type) {
5826 case LINE_STAT_STAGED:
5827 type = LINE_STAT_SECTION;
5828 text = "Changes to be committed:";
5829 break;
5831 case LINE_STAT_UNSTAGED:
5832 type = LINE_STAT_SECTION;
5833 text = "Changed but not updated:";
5834 break;
5836 case LINE_STAT_UNTRACKED:
5837 type = LINE_STAT_SECTION;
5838 text = "Untracked files:";
5839 break;
5841 case LINE_STAT_NONE:
5842 type = LINE_DEFAULT;
5843 text = " (no files)";
5844 break;
5846 case LINE_STAT_HEAD:
5847 type = LINE_STAT_HEAD;
5848 text = status_onbranch;
5849 break;
5851 default:
5852 return FALSE;
5853 }
5854 } else {
5855 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5857 buf[0] = status->status;
5858 if (draw_text(view, line->type, buf, TRUE))
5859 return TRUE;
5860 type = LINE_DEFAULT;
5861 text = status->new.name;
5862 }
5864 draw_text(view, type, text, TRUE);
5865 return TRUE;
5866 }
5868 static enum request
5869 status_load_error(struct view *view, struct view *stage, const char *path)
5870 {
5871 if (displayed_views() == 2 || display[current_view] != view)
5872 maximize_view(view);
5873 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5874 return REQ_NONE;
5875 }
5877 static enum request
5878 status_enter(struct view *view, struct line *line)
5879 {
5880 struct status *status = line->data;
5881 const char *oldpath = status ? status->old.name : NULL;
5882 /* Diffs for unmerged entries are empty when passing the new
5883 * path, so leave it empty. */
5884 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5885 const char *info;
5886 enum open_flags split;
5887 struct view *stage = VIEW(REQ_VIEW_STAGE);
5889 if (line->type == LINE_STAT_NONE ||
5890 (!status && line[1].type == LINE_STAT_NONE)) {
5891 report("No file to diff");
5892 return REQ_NONE;
5893 }
5895 switch (line->type) {
5896 case LINE_STAT_STAGED:
5897 if (is_initial_commit()) {
5898 const char *no_head_diff_argv[] = {
5899 "git", "diff", "--no-color", "--patch-with-stat",
5900 "--", "/dev/null", newpath, NULL
5901 };
5903 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5904 return status_load_error(view, stage, newpath);
5905 } else {
5906 const char *index_show_argv[] = {
5907 "git", "diff-index", "--root", "--patch-with-stat",
5908 "-C", "-M", "--cached", "HEAD", "--",
5909 oldpath, newpath, NULL
5910 };
5912 if (!prepare_update(stage, index_show_argv, opt_cdup))
5913 return status_load_error(view, stage, newpath);
5914 }
5916 if (status)
5917 info = "Staged changes to %s";
5918 else
5919 info = "Staged changes";
5920 break;
5922 case LINE_STAT_UNSTAGED:
5923 {
5924 const char *files_show_argv[] = {
5925 "git", "diff-files", "--root", "--patch-with-stat",
5926 "-C", "-M", "--", oldpath, newpath, NULL
5927 };
5929 if (!prepare_update(stage, files_show_argv, opt_cdup))
5930 return status_load_error(view, stage, newpath);
5931 if (status)
5932 info = "Unstaged changes to %s";
5933 else
5934 info = "Unstaged changes";
5935 break;
5936 }
5937 case LINE_STAT_UNTRACKED:
5938 if (!newpath) {
5939 report("No file to show");
5940 return REQ_NONE;
5941 }
5943 if (!suffixcmp(status->new.name, -1, "/")) {
5944 report("Cannot display a directory");
5945 return REQ_NONE;
5946 }
5948 if (!prepare_update_file(stage, newpath))
5949 return status_load_error(view, stage, newpath);
5950 info = "Untracked file %s";
5951 break;
5953 case LINE_STAT_HEAD:
5954 return REQ_NONE;
5956 default:
5957 die("line type %d not handled in switch", line->type);
5958 }
5960 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5961 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5962 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5963 if (status) {
5964 stage_status = *status;
5965 } else {
5966 memset(&stage_status, 0, sizeof(stage_status));
5967 }
5969 stage_line_type = line->type;
5970 stage_chunks = 0;
5971 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5972 }
5974 return REQ_NONE;
5975 }
5977 static bool
5978 status_exists(struct status *status, enum line_type type)
5979 {
5980 struct view *view = VIEW(REQ_VIEW_STATUS);
5981 unsigned long lineno;
5983 for (lineno = 0; lineno < view->lines; lineno++) {
5984 struct line *line = &view->line[lineno];
5985 struct status *pos = line->data;
5987 if (line->type != type)
5988 continue;
5989 if (!pos && (!status || !status->status) && line[1].data) {
5990 select_view_line(view, lineno);
5991 return TRUE;
5992 }
5993 if (pos && !strcmp(status->new.name, pos->new.name)) {
5994 select_view_line(view, lineno);
5995 return TRUE;
5996 }
5997 }
5999 return FALSE;
6000 }
6003 static bool
6004 status_update_prepare(struct io *io, enum line_type type)
6005 {
6006 const char *staged_argv[] = {
6007 "git", "update-index", "-z", "--index-info", NULL
6008 };
6009 const char *others_argv[] = {
6010 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6011 };
6013 switch (type) {
6014 case LINE_STAT_STAGED:
6015 return io_run(io, IO_WR, opt_cdup, staged_argv);
6017 case LINE_STAT_UNSTAGED:
6018 case LINE_STAT_UNTRACKED:
6019 return io_run(io, IO_WR, opt_cdup, others_argv);
6021 default:
6022 die("line type %d not handled in switch", type);
6023 return FALSE;
6024 }
6025 }
6027 static bool
6028 status_update_write(struct io *io, struct status *status, enum line_type type)
6029 {
6030 char buf[SIZEOF_STR];
6031 size_t bufsize = 0;
6033 switch (type) {
6034 case LINE_STAT_STAGED:
6035 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6036 status->old.mode,
6037 status->old.rev,
6038 status->old.name, 0))
6039 return FALSE;
6040 break;
6042 case LINE_STAT_UNSTAGED:
6043 case LINE_STAT_UNTRACKED:
6044 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6045 return FALSE;
6046 break;
6048 default:
6049 die("line type %d not handled in switch", type);
6050 }
6052 return io_write(io, buf, bufsize);
6053 }
6055 static bool
6056 status_update_file(struct status *status, enum line_type type)
6057 {
6058 struct io io;
6059 bool result;
6061 if (!status_update_prepare(&io, type))
6062 return FALSE;
6064 result = status_update_write(&io, status, type);
6065 return io_done(&io) && result;
6066 }
6068 static bool
6069 status_update_files(struct view *view, struct line *line)
6070 {
6071 char buf[sizeof(view->ref)];
6072 struct io io;
6073 bool result = TRUE;
6074 struct line *pos = view->line + view->lines;
6075 int files = 0;
6076 int file, done;
6077 int cursor_y = -1, cursor_x = -1;
6079 if (!status_update_prepare(&io, line->type))
6080 return FALSE;
6082 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6083 files++;
6085 string_copy(buf, view->ref);
6086 getsyx(cursor_y, cursor_x);
6087 for (file = 0, done = 5; result && file < files; line++, file++) {
6088 int almost_done = file * 100 / files;
6090 if (almost_done > done) {
6091 done = almost_done;
6092 string_format(view->ref, "updating file %u of %u (%d%% done)",
6093 file, files, done);
6094 update_view_title(view);
6095 setsyx(cursor_y, cursor_x);
6096 doupdate();
6097 }
6098 result = status_update_write(&io, line->data, line->type);
6099 }
6100 string_copy(view->ref, buf);
6102 return io_done(&io) && result;
6103 }
6105 static bool
6106 status_update(struct view *view)
6107 {
6108 struct line *line = &view->line[view->lineno];
6110 assert(view->lines);
6112 if (!line->data) {
6113 /* This should work even for the "On branch" line. */
6114 if (line < view->line + view->lines && !line[1].data) {
6115 report("Nothing to update");
6116 return FALSE;
6117 }
6119 if (!status_update_files(view, line + 1)) {
6120 report("Failed to update file status");
6121 return FALSE;
6122 }
6124 } else if (!status_update_file(line->data, line->type)) {
6125 report("Failed to update file status");
6126 return FALSE;
6127 }
6129 return TRUE;
6130 }
6132 static bool
6133 status_revert(struct status *status, enum line_type type, bool has_none)
6134 {
6135 if (!status || type != LINE_STAT_UNSTAGED) {
6136 if (type == LINE_STAT_STAGED) {
6137 report("Cannot revert changes to staged files");
6138 } else if (type == LINE_STAT_UNTRACKED) {
6139 report("Cannot revert changes to untracked files");
6140 } else if (has_none) {
6141 report("Nothing to revert");
6142 } else {
6143 report("Cannot revert changes to multiple files");
6144 }
6146 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6147 char mode[10] = "100644";
6148 const char *reset_argv[] = {
6149 "git", "update-index", "--cacheinfo", mode,
6150 status->old.rev, status->old.name, NULL
6151 };
6152 const char *checkout_argv[] = {
6153 "git", "checkout", "--", status->old.name, NULL
6154 };
6156 if (status->status == 'U') {
6157 string_format(mode, "%5o", status->old.mode);
6159 if (status->old.mode == 0 && status->new.mode == 0) {
6160 reset_argv[2] = "--force-remove";
6161 reset_argv[3] = status->old.name;
6162 reset_argv[4] = NULL;
6163 }
6165 if (!io_run_fg(reset_argv, opt_cdup))
6166 return FALSE;
6167 if (status->old.mode == 0 && status->new.mode == 0)
6168 return TRUE;
6169 }
6171 return io_run_fg(checkout_argv, opt_cdup);
6172 }
6174 return FALSE;
6175 }
6177 static enum request
6178 status_request(struct view *view, enum request request, struct line *line)
6179 {
6180 struct status *status = line->data;
6182 switch (request) {
6183 case REQ_STATUS_UPDATE:
6184 if (!status_update(view))
6185 return REQ_NONE;
6186 break;
6188 case REQ_STATUS_REVERT:
6189 if (!status_revert(status, line->type, status_has_none(view, line)))
6190 return REQ_NONE;
6191 break;
6193 case REQ_STATUS_MERGE:
6194 if (!status || status->status != 'U') {
6195 report("Merging only possible for files with unmerged status ('U').");
6196 return REQ_NONE;
6197 }
6198 open_mergetool(status->new.name);
6199 break;
6201 case REQ_EDIT:
6202 if (!status)
6203 return request;
6204 if (status->status == 'D') {
6205 report("File has been deleted.");
6206 return REQ_NONE;
6207 }
6209 open_editor(status->new.name);
6210 break;
6212 case REQ_VIEW_BLAME:
6213 if (status)
6214 opt_ref[0] = 0;
6215 return request;
6217 case REQ_ENTER:
6218 /* After returning the status view has been split to
6219 * show the stage view. No further reloading is
6220 * necessary. */
6221 return status_enter(view, line);
6223 case REQ_REFRESH:
6224 /* Simply reload the view. */
6225 break;
6227 default:
6228 return request;
6229 }
6231 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6233 return REQ_NONE;
6234 }
6236 static void
6237 status_select(struct view *view, struct line *line)
6238 {
6239 struct status *status = line->data;
6240 char file[SIZEOF_STR] = "all files";
6241 const char *text;
6242 const char *key;
6244 if (status && !string_format(file, "'%s'", status->new.name))
6245 return;
6247 if (!status && line[1].type == LINE_STAT_NONE)
6248 line++;
6250 switch (line->type) {
6251 case LINE_STAT_STAGED:
6252 text = "Press %s to unstage %s for commit";
6253 break;
6255 case LINE_STAT_UNSTAGED:
6256 text = "Press %s to stage %s for commit";
6257 break;
6259 case LINE_STAT_UNTRACKED:
6260 text = "Press %s to stage %s for addition";
6261 break;
6263 case LINE_STAT_HEAD:
6264 case LINE_STAT_NONE:
6265 text = "Nothing to update";
6266 break;
6268 default:
6269 die("line type %d not handled in switch", line->type);
6270 }
6272 if (status && status->status == 'U') {
6273 text = "Press %s to resolve conflict in %s";
6274 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6276 } else {
6277 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6278 }
6280 string_format(view->ref, text, key, file);
6281 if (status)
6282 string_copy(opt_file, status->new.name);
6283 }
6285 static bool
6286 status_grep(struct view *view, struct line *line)
6287 {
6288 struct status *status = line->data;
6290 if (status) {
6291 const char buf[2] = { status->status, 0 };
6292 const char *text[] = { status->new.name, buf, NULL };
6294 return grep_text(view, text);
6295 }
6297 return FALSE;
6298 }
6300 static struct view_ops status_ops = {
6301 "file",
6302 NULL,
6303 status_open,
6304 NULL,
6305 status_draw,
6306 status_request,
6307 status_grep,
6308 status_select,
6309 };
6312 static bool
6313 stage_diff_write(struct io *io, struct line *line, struct line *end)
6314 {
6315 while (line < end) {
6316 if (!io_write(io, line->data, strlen(line->data)) ||
6317 !io_write(io, "\n", 1))
6318 return FALSE;
6319 line++;
6320 if (line->type == LINE_DIFF_CHUNK ||
6321 line->type == LINE_DIFF_HEADER)
6322 break;
6323 }
6325 return TRUE;
6326 }
6328 static struct line *
6329 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6330 {
6331 for (; view->line < line; line--)
6332 if (line->type == type)
6333 return line;
6335 return NULL;
6336 }
6338 static bool
6339 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6340 {
6341 const char *apply_argv[SIZEOF_ARG] = {
6342 "git", "apply", "--whitespace=nowarn", NULL
6343 };
6344 struct line *diff_hdr;
6345 struct io io;
6346 int argc = 3;
6348 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6349 if (!diff_hdr)
6350 return FALSE;
6352 if (!revert)
6353 apply_argv[argc++] = "--cached";
6354 if (revert || stage_line_type == LINE_STAT_STAGED)
6355 apply_argv[argc++] = "-R";
6356 apply_argv[argc++] = "-";
6357 apply_argv[argc++] = NULL;
6358 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6359 return FALSE;
6361 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6362 !stage_diff_write(&io, chunk, view->line + view->lines))
6363 chunk = NULL;
6365 io_done(&io);
6366 io_run_bg(update_index_argv);
6368 return chunk ? TRUE : FALSE;
6369 }
6371 static bool
6372 stage_update(struct view *view, struct line *line)
6373 {
6374 struct line *chunk = NULL;
6376 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6377 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6379 if (chunk) {
6380 if (!stage_apply_chunk(view, chunk, FALSE)) {
6381 report("Failed to apply chunk");
6382 return FALSE;
6383 }
6385 } else if (!stage_status.status) {
6386 view = VIEW(REQ_VIEW_STATUS);
6388 for (line = view->line; line < view->line + view->lines; line++)
6389 if (line->type == stage_line_type)
6390 break;
6392 if (!status_update_files(view, line + 1)) {
6393 report("Failed to update files");
6394 return FALSE;
6395 }
6397 } else if (!status_update_file(&stage_status, stage_line_type)) {
6398 report("Failed to update file");
6399 return FALSE;
6400 }
6402 return TRUE;
6403 }
6405 static bool
6406 stage_revert(struct view *view, struct line *line)
6407 {
6408 struct line *chunk = NULL;
6410 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6411 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6413 if (chunk) {
6414 if (!prompt_yesno("Are you sure you want to revert changes?"))
6415 return FALSE;
6417 if (!stage_apply_chunk(view, chunk, TRUE)) {
6418 report("Failed to revert chunk");
6419 return FALSE;
6420 }
6421 return TRUE;
6423 } else {
6424 return status_revert(stage_status.status ? &stage_status : NULL,
6425 stage_line_type, FALSE);
6426 }
6427 }
6430 static void
6431 stage_next(struct view *view, struct line *line)
6432 {
6433 int i;
6435 if (!stage_chunks) {
6436 for (line = view->line; line < view->line + view->lines; line++) {
6437 if (line->type != LINE_DIFF_CHUNK)
6438 continue;
6440 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6441 report("Allocation failure");
6442 return;
6443 }
6445 stage_chunk[stage_chunks++] = line - view->line;
6446 }
6447 }
6449 for (i = 0; i < stage_chunks; i++) {
6450 if (stage_chunk[i] > view->lineno) {
6451 do_scroll_view(view, stage_chunk[i] - view->lineno);
6452 report("Chunk %d of %d", i + 1, stage_chunks);
6453 return;
6454 }
6455 }
6457 report("No next chunk found");
6458 }
6460 static enum request
6461 stage_request(struct view *view, enum request request, struct line *line)
6462 {
6463 switch (request) {
6464 case REQ_STATUS_UPDATE:
6465 if (!stage_update(view, line))
6466 return REQ_NONE;
6467 break;
6469 case REQ_STATUS_REVERT:
6470 if (!stage_revert(view, line))
6471 return REQ_NONE;
6472 break;
6474 case REQ_STAGE_NEXT:
6475 if (stage_line_type == LINE_STAT_UNTRACKED) {
6476 report("File is untracked; press %s to add",
6477 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6478 return REQ_NONE;
6479 }
6480 stage_next(view, line);
6481 return REQ_NONE;
6483 case REQ_EDIT:
6484 if (!stage_status.new.name[0])
6485 return request;
6486 if (stage_status.status == 'D') {
6487 report("File has been deleted.");
6488 return REQ_NONE;
6489 }
6491 open_editor(stage_status.new.name);
6492 break;
6494 case REQ_REFRESH:
6495 /* Reload everything ... */
6496 break;
6498 case REQ_VIEW_BLAME:
6499 if (stage_status.new.name[0]) {
6500 string_copy(opt_file, stage_status.new.name);
6501 opt_ref[0] = 0;
6502 }
6503 return request;
6505 case REQ_ENTER:
6506 return pager_request(view, request, line);
6508 default:
6509 return request;
6510 }
6512 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6513 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6515 /* Check whether the staged entry still exists, and close the
6516 * stage view if it doesn't. */
6517 if (!status_exists(&stage_status, stage_line_type)) {
6518 status_restore(VIEW(REQ_VIEW_STATUS));
6519 return REQ_VIEW_CLOSE;
6520 }
6522 if (stage_line_type == LINE_STAT_UNTRACKED) {
6523 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6524 report("Cannot display a directory");
6525 return REQ_NONE;
6526 }
6528 if (!prepare_update_file(view, stage_status.new.name)) {
6529 report("Failed to open file: %s", strerror(errno));
6530 return REQ_NONE;
6531 }
6532 }
6533 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6535 return REQ_NONE;
6536 }
6538 static struct view_ops stage_ops = {
6539 "line",
6540 NULL,
6541 NULL,
6542 pager_read,
6543 pager_draw,
6544 stage_request,
6545 pager_grep,
6546 pager_select,
6547 };
6550 /*
6551 * Revision graph
6552 */
6554 struct commit {
6555 char id[SIZEOF_REV]; /* SHA1 ID. */
6556 char title[128]; /* First line of the commit message. */
6557 const char *author; /* Author of the commit. */
6558 struct time time; /* Date from the author ident. */
6559 struct ref_list *refs; /* Repository references. */
6560 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6561 size_t graph_size; /* The width of the graph array. */
6562 bool has_parents; /* Rewritten --parents seen. */
6563 };
6565 /* Size of rev graph with no "padding" columns */
6566 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6568 struct rev_graph {
6569 struct rev_graph *prev, *next, *parents;
6570 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6571 size_t size;
6572 struct commit *commit;
6573 size_t pos;
6574 unsigned int boundary:1;
6575 };
6577 /* Parents of the commit being visualized. */
6578 static struct rev_graph graph_parents[4];
6580 /* The current stack of revisions on the graph. */
6581 static struct rev_graph graph_stacks[4] = {
6582 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6583 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6584 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6585 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6586 };
6588 static inline bool
6589 graph_parent_is_merge(struct rev_graph *graph)
6590 {
6591 return graph->parents->size > 1;
6592 }
6594 static inline void
6595 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6596 {
6597 struct commit *commit = graph->commit;
6599 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6600 commit->graph[commit->graph_size++] = symbol;
6601 }
6603 static void
6604 clear_rev_graph(struct rev_graph *graph)
6605 {
6606 graph->boundary = 0;
6607 graph->size = graph->pos = 0;
6608 graph->commit = NULL;
6609 memset(graph->parents, 0, sizeof(*graph->parents));
6610 }
6612 static void
6613 done_rev_graph(struct rev_graph *graph)
6614 {
6615 if (graph_parent_is_merge(graph) &&
6616 graph->pos < graph->size - 1 &&
6617 graph->next->size == graph->size + graph->parents->size - 1) {
6618 size_t i = graph->pos + graph->parents->size - 1;
6620 graph->commit->graph_size = i * 2;
6621 while (i < graph->next->size - 1) {
6622 append_to_rev_graph(graph, ' ');
6623 append_to_rev_graph(graph, '\\');
6624 i++;
6625 }
6626 }
6628 clear_rev_graph(graph);
6629 }
6631 static void
6632 push_rev_graph(struct rev_graph *graph, const char *parent)
6633 {
6634 int i;
6636 /* "Collapse" duplicate parents lines.
6637 *
6638 * FIXME: This needs to also update update the drawn graph but
6639 * for now it just serves as a method for pruning graph lines. */
6640 for (i = 0; i < graph->size; i++)
6641 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6642 return;
6644 if (graph->size < SIZEOF_REVITEMS) {
6645 string_copy_rev(graph->rev[graph->size++], parent);
6646 }
6647 }
6649 static chtype
6650 get_rev_graph_symbol(struct rev_graph *graph)
6651 {
6652 chtype symbol;
6654 if (graph->boundary)
6655 symbol = REVGRAPH_BOUND;
6656 else if (graph->parents->size == 0)
6657 symbol = REVGRAPH_INIT;
6658 else if (graph_parent_is_merge(graph))
6659 symbol = REVGRAPH_MERGE;
6660 else if (graph->pos >= graph->size)
6661 symbol = REVGRAPH_BRANCH;
6662 else
6663 symbol = REVGRAPH_COMMIT;
6665 return symbol;
6666 }
6668 static void
6669 draw_rev_graph(struct rev_graph *graph)
6670 {
6671 struct rev_filler {
6672 chtype separator, line;
6673 };
6674 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6675 static struct rev_filler fillers[] = {
6676 { ' ', '|' },
6677 { '`', '.' },
6678 { '\'', ' ' },
6679 { '/', ' ' },
6680 };
6681 chtype symbol = get_rev_graph_symbol(graph);
6682 struct rev_filler *filler;
6683 size_t i;
6685 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6686 filler = &fillers[DEFAULT];
6688 for (i = 0; i < graph->pos; i++) {
6689 append_to_rev_graph(graph, filler->line);
6690 if (graph_parent_is_merge(graph->prev) &&
6691 graph->prev->pos == i)
6692 filler = &fillers[RSHARP];
6694 append_to_rev_graph(graph, filler->separator);
6695 }
6697 /* Place the symbol for this revision. */
6698 append_to_rev_graph(graph, symbol);
6700 if (graph->prev->size > graph->size)
6701 filler = &fillers[RDIAG];
6702 else
6703 filler = &fillers[DEFAULT];
6705 i++;
6707 for (; i < graph->size; i++) {
6708 append_to_rev_graph(graph, filler->separator);
6709 append_to_rev_graph(graph, filler->line);
6710 if (graph_parent_is_merge(graph->prev) &&
6711 i < graph->prev->pos + graph->parents->size)
6712 filler = &fillers[RSHARP];
6713 if (graph->prev->size > graph->size)
6714 filler = &fillers[LDIAG];
6715 }
6717 if (graph->prev->size > graph->size) {
6718 append_to_rev_graph(graph, filler->separator);
6719 if (filler->line != ' ')
6720 append_to_rev_graph(graph, filler->line);
6721 }
6722 }
6724 /* Prepare the next rev graph */
6725 static void
6726 prepare_rev_graph(struct rev_graph *graph)
6727 {
6728 size_t i;
6730 /* First, traverse all lines of revisions up to the active one. */
6731 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6732 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6733 break;
6735 push_rev_graph(graph->next, graph->rev[graph->pos]);
6736 }
6738 /* Interleave the new revision parent(s). */
6739 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6740 push_rev_graph(graph->next, graph->parents->rev[i]);
6742 /* Lastly, put any remaining revisions. */
6743 for (i = graph->pos + 1; i < graph->size; i++)
6744 push_rev_graph(graph->next, graph->rev[i]);
6745 }
6747 static void
6748 update_rev_graph(struct view *view, struct rev_graph *graph)
6749 {
6750 /* If this is the finalizing update ... */
6751 if (graph->commit)
6752 prepare_rev_graph(graph);
6754 /* Graph visualization needs a one rev look-ahead,
6755 * so the first update doesn't visualize anything. */
6756 if (!graph->prev->commit)
6757 return;
6759 if (view->lines > 2)
6760 view->line[view->lines - 3].dirty = 1;
6761 if (view->lines > 1)
6762 view->line[view->lines - 2].dirty = 1;
6763 draw_rev_graph(graph->prev);
6764 done_rev_graph(graph->prev->prev);
6765 }
6768 /*
6769 * Main view backend
6770 */
6772 static const char *main_argv[SIZEOF_ARG] = {
6773 "git", "log", "--no-color", "--pretty=raw", "--parents",
6774 "--topo-order", "%(diffargs)", "%(revargs)",
6775 "--", "%(fileargs)", NULL
6776 };
6778 static bool
6779 main_draw(struct view *view, struct line *line, unsigned int lineno)
6780 {
6781 struct commit *commit = line->data;
6783 if (!commit->author)
6784 return FALSE;
6786 if (opt_date && draw_date(view, &commit->time))
6787 return TRUE;
6789 if (opt_author && draw_author(view, commit->author))
6790 return TRUE;
6792 if (opt_rev_graph && commit->graph_size &&
6793 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6794 return TRUE;
6796 if (opt_show_refs && commit->refs) {
6797 size_t i;
6799 for (i = 0; i < commit->refs->size; i++) {
6800 struct ref *ref = commit->refs->refs[i];
6801 enum line_type type;
6803 if (ref->head)
6804 type = LINE_MAIN_HEAD;
6805 else if (ref->ltag)
6806 type = LINE_MAIN_LOCAL_TAG;
6807 else if (ref->tag)
6808 type = LINE_MAIN_TAG;
6809 else if (ref->tracked)
6810 type = LINE_MAIN_TRACKED;
6811 else if (ref->remote)
6812 type = LINE_MAIN_REMOTE;
6813 else
6814 type = LINE_MAIN_REF;
6816 if (draw_text(view, type, "[", TRUE) ||
6817 draw_text(view, type, ref->name, TRUE) ||
6818 draw_text(view, type, "]", TRUE))
6819 return TRUE;
6821 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6822 return TRUE;
6823 }
6824 }
6826 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6827 return TRUE;
6828 }
6830 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6831 static bool
6832 main_read(struct view *view, char *line)
6833 {
6834 static struct rev_graph *graph = graph_stacks;
6835 enum line_type type;
6836 struct commit *commit;
6838 if (!line) {
6839 int i;
6841 if (!view->lines && !view->prev)
6842 die("No revisions match the given arguments.");
6843 if (view->lines > 0) {
6844 commit = view->line[view->lines - 1].data;
6845 view->line[view->lines - 1].dirty = 1;
6846 if (!commit->author) {
6847 view->lines--;
6848 free(commit);
6849 graph->commit = NULL;
6850 }
6851 }
6852 update_rev_graph(view, graph);
6854 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6855 clear_rev_graph(&graph_stacks[i]);
6856 return TRUE;
6857 }
6859 type = get_line_type(line);
6860 if (type == LINE_COMMIT) {
6861 commit = calloc(1, sizeof(struct commit));
6862 if (!commit)
6863 return FALSE;
6865 line += STRING_SIZE("commit ");
6866 if (*line == '-') {
6867 graph->boundary = 1;
6868 line++;
6869 }
6871 string_copy_rev(commit->id, line);
6872 commit->refs = get_ref_list(commit->id);
6873 graph->commit = commit;
6874 add_line_data(view, commit, LINE_MAIN_COMMIT);
6876 while ((line = strchr(line, ' '))) {
6877 line++;
6878 push_rev_graph(graph->parents, line);
6879 commit->has_parents = TRUE;
6880 }
6881 return TRUE;
6882 }
6884 if (!view->lines)
6885 return TRUE;
6886 commit = view->line[view->lines - 1].data;
6888 switch (type) {
6889 case LINE_PARENT:
6890 if (commit->has_parents)
6891 break;
6892 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6893 break;
6895 case LINE_AUTHOR:
6896 parse_author_line(line + STRING_SIZE("author "),
6897 &commit->author, &commit->time);
6898 update_rev_graph(view, graph);
6899 graph = graph->next;
6900 break;
6902 default:
6903 /* Fill in the commit title if it has not already been set. */
6904 if (commit->title[0])
6905 break;
6907 /* Require titles to start with a non-space character at the
6908 * offset used by git log. */
6909 if (strncmp(line, " ", 4))
6910 break;
6911 line += 4;
6912 /* Well, if the title starts with a whitespace character,
6913 * try to be forgiving. Otherwise we end up with no title. */
6914 while (isspace(*line))
6915 line++;
6916 if (*line == '\0')
6917 break;
6918 /* FIXME: More graceful handling of titles; append "..." to
6919 * shortened titles, etc. */
6921 string_expand(commit->title, sizeof(commit->title), line, 1);
6922 view->line[view->lines - 1].dirty = 1;
6923 }
6925 return TRUE;
6926 }
6928 static enum request
6929 main_request(struct view *view, enum request request, struct line *line)
6930 {
6931 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6933 switch (request) {
6934 case REQ_ENTER:
6935 if (view_is_displayed(view) && display[0] != view)
6936 maximize_view(view);
6937 open_view(view, REQ_VIEW_DIFF, flags);
6938 break;
6939 case REQ_REFRESH:
6940 load_refs();
6941 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6942 break;
6943 default:
6944 return request;
6945 }
6947 return REQ_NONE;
6948 }
6950 static bool
6951 grep_refs(struct ref_list *list, regex_t *regex)
6952 {
6953 regmatch_t pmatch;
6954 size_t i;
6956 if (!opt_show_refs || !list)
6957 return FALSE;
6959 for (i = 0; i < list->size; i++) {
6960 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6961 return TRUE;
6962 }
6964 return FALSE;
6965 }
6967 static bool
6968 main_grep(struct view *view, struct line *line)
6969 {
6970 struct commit *commit = line->data;
6971 const char *text[] = {
6972 commit->title,
6973 opt_author ? commit->author : "",
6974 mkdate(&commit->time, opt_date),
6975 NULL
6976 };
6978 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6979 }
6981 static void
6982 main_select(struct view *view, struct line *line)
6983 {
6984 struct commit *commit = line->data;
6986 string_copy_rev(view->ref, commit->id);
6987 string_copy_rev(ref_commit, view->ref);
6988 }
6990 static struct view_ops main_ops = {
6991 "commit",
6992 main_argv,
6993 NULL,
6994 main_read,
6995 main_draw,
6996 main_request,
6997 main_grep,
6998 main_select,
6999 };
7002 /*
7003 * Status management
7004 */
7006 /* Whether or not the curses interface has been initialized. */
7007 static bool cursed = FALSE;
7009 /* Terminal hacks and workarounds. */
7010 static bool use_scroll_redrawwin;
7011 static bool use_scroll_status_wclear;
7013 /* The status window is used for polling keystrokes. */
7014 static WINDOW *status_win;
7016 /* Reading from the prompt? */
7017 static bool input_mode = FALSE;
7019 static bool status_empty = FALSE;
7021 /* Update status and title window. */
7022 static void
7023 report(const char *msg, ...)
7024 {
7025 struct view *view = display[current_view];
7027 if (input_mode)
7028 return;
7030 if (!view) {
7031 char buf[SIZEOF_STR];
7032 va_list args;
7034 va_start(args, msg);
7035 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7036 buf[sizeof(buf) - 1] = 0;
7037 buf[sizeof(buf) - 2] = '.';
7038 buf[sizeof(buf) - 3] = '.';
7039 buf[sizeof(buf) - 4] = '.';
7040 }
7041 va_end(args);
7042 die("%s", buf);
7043 }
7045 if (!status_empty || *msg) {
7046 va_list args;
7048 va_start(args, msg);
7050 wmove(status_win, 0, 0);
7051 if (view->has_scrolled && use_scroll_status_wclear)
7052 wclear(status_win);
7053 if (*msg) {
7054 vwprintw(status_win, msg, args);
7055 status_empty = FALSE;
7056 } else {
7057 status_empty = TRUE;
7058 }
7059 wclrtoeol(status_win);
7060 wnoutrefresh(status_win);
7062 va_end(args);
7063 }
7065 update_view_title(view);
7066 }
7068 static void
7069 init_display(void)
7070 {
7071 const char *term;
7072 int x, y;
7074 /* Initialize the curses library */
7075 if (isatty(STDIN_FILENO)) {
7076 cursed = !!initscr();
7077 opt_tty = stdin;
7078 } else {
7079 /* Leave stdin and stdout alone when acting as a pager. */
7080 opt_tty = fopen("/dev/tty", "r+");
7081 if (!opt_tty)
7082 die("Failed to open /dev/tty");
7083 cursed = !!newterm(NULL, opt_tty, opt_tty);
7084 }
7086 if (!cursed)
7087 die("Failed to initialize curses");
7089 nonl(); /* Disable conversion and detect newlines from input. */
7090 cbreak(); /* Take input chars one at a time, no wait for \n */
7091 noecho(); /* Don't echo input */
7092 leaveok(stdscr, FALSE);
7094 if (has_colors())
7095 init_colors();
7097 getmaxyx(stdscr, y, x);
7098 status_win = newwin(1, 0, y - 1, 0);
7099 if (!status_win)
7100 die("Failed to create status window");
7102 /* Enable keyboard mapping */
7103 keypad(status_win, TRUE);
7104 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7106 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7107 set_tabsize(opt_tab_size);
7108 #else
7109 TABSIZE = opt_tab_size;
7110 #endif
7112 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7113 if (term && !strcmp(term, "gnome-terminal")) {
7114 /* In the gnome-terminal-emulator, the message from
7115 * scrolling up one line when impossible followed by
7116 * scrolling down one line causes corruption of the
7117 * status line. This is fixed by calling wclear. */
7118 use_scroll_status_wclear = TRUE;
7119 use_scroll_redrawwin = FALSE;
7121 } else if (term && !strcmp(term, "xrvt-xpm")) {
7122 /* No problems with full optimizations in xrvt-(unicode)
7123 * and aterm. */
7124 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7126 } else {
7127 /* When scrolling in (u)xterm the last line in the
7128 * scrolling direction will update slowly. */
7129 use_scroll_redrawwin = TRUE;
7130 use_scroll_status_wclear = FALSE;
7131 }
7132 }
7134 static int
7135 get_input(int prompt_position)
7136 {
7137 struct view *view;
7138 int i, key, cursor_y, cursor_x;
7140 if (prompt_position)
7141 input_mode = TRUE;
7143 while (TRUE) {
7144 bool loading = FALSE;
7146 foreach_view (view, i) {
7147 update_view(view);
7148 if (view_is_displayed(view) && view->has_scrolled &&
7149 use_scroll_redrawwin)
7150 redrawwin(view->win);
7151 view->has_scrolled = FALSE;
7152 if (view->pipe)
7153 loading = TRUE;
7154 }
7156 /* Update the cursor position. */
7157 if (prompt_position) {
7158 getbegyx(status_win, cursor_y, cursor_x);
7159 cursor_x = prompt_position;
7160 } else {
7161 view = display[current_view];
7162 getbegyx(view->win, cursor_y, cursor_x);
7163 cursor_x = view->width - 1;
7164 cursor_y += view->lineno - view->offset;
7165 }
7166 setsyx(cursor_y, cursor_x);
7168 /* Refresh, accept single keystroke of input */
7169 doupdate();
7170 nodelay(status_win, loading);
7171 key = wgetch(status_win);
7173 /* wgetch() with nodelay() enabled returns ERR when
7174 * there's no input. */
7175 if (key == ERR) {
7177 } else if (key == KEY_RESIZE) {
7178 int height, width;
7180 getmaxyx(stdscr, height, width);
7182 wresize(status_win, 1, width);
7183 mvwin(status_win, height - 1, 0);
7184 wnoutrefresh(status_win);
7185 resize_display();
7186 redraw_display(TRUE);
7188 } else {
7189 input_mode = FALSE;
7190 return key;
7191 }
7192 }
7193 }
7195 static char *
7196 prompt_input(const char *prompt, input_handler handler, void *data)
7197 {
7198 enum input_status status = INPUT_OK;
7199 static char buf[SIZEOF_STR];
7200 size_t pos = 0;
7202 buf[pos] = 0;
7204 while (status == INPUT_OK || status == INPUT_SKIP) {
7205 int key;
7207 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7208 wclrtoeol(status_win);
7210 key = get_input(pos + 1);
7211 switch (key) {
7212 case KEY_RETURN:
7213 case KEY_ENTER:
7214 case '\n':
7215 status = pos ? INPUT_STOP : INPUT_CANCEL;
7216 break;
7218 case KEY_BACKSPACE:
7219 if (pos > 0)
7220 buf[--pos] = 0;
7221 else
7222 status = INPUT_CANCEL;
7223 break;
7225 case KEY_ESC:
7226 status = INPUT_CANCEL;
7227 break;
7229 default:
7230 if (pos >= sizeof(buf)) {
7231 report("Input string too long");
7232 return NULL;
7233 }
7235 status = handler(data, buf, key);
7236 if (status == INPUT_OK)
7237 buf[pos++] = (char) key;
7238 }
7239 }
7241 /* Clear the status window */
7242 status_empty = FALSE;
7243 report("");
7245 if (status == INPUT_CANCEL)
7246 return NULL;
7248 buf[pos++] = 0;
7250 return buf;
7251 }
7253 static enum input_status
7254 prompt_yesno_handler(void *data, char *buf, int c)
7255 {
7256 if (c == 'y' || c == 'Y')
7257 return INPUT_STOP;
7258 if (c == 'n' || c == 'N')
7259 return INPUT_CANCEL;
7260 return INPUT_SKIP;
7261 }
7263 static bool
7264 prompt_yesno(const char *prompt)
7265 {
7266 char prompt2[SIZEOF_STR];
7268 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7269 return FALSE;
7271 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7272 }
7274 static enum input_status
7275 read_prompt_handler(void *data, char *buf, int c)
7276 {
7277 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7278 }
7280 static char *
7281 read_prompt(const char *prompt)
7282 {
7283 return prompt_input(prompt, read_prompt_handler, NULL);
7284 }
7286 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7287 {
7288 enum input_status status = INPUT_OK;
7289 int size = 0;
7291 while (items[size].text)
7292 size++;
7294 while (status == INPUT_OK) {
7295 const struct menu_item *item = &items[*selected];
7296 int key;
7297 int i;
7299 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7300 prompt, *selected + 1, size);
7301 if (item->hotkey)
7302 wprintw(status_win, "[%c] ", (char) item->hotkey);
7303 wprintw(status_win, "%s", item->text);
7304 wclrtoeol(status_win);
7306 key = get_input(COLS - 1);
7307 switch (key) {
7308 case KEY_RETURN:
7309 case KEY_ENTER:
7310 case '\n':
7311 status = INPUT_STOP;
7312 break;
7314 case KEY_LEFT:
7315 case KEY_UP:
7316 *selected = *selected - 1;
7317 if (*selected < 0)
7318 *selected = size - 1;
7319 break;
7321 case KEY_RIGHT:
7322 case KEY_DOWN:
7323 *selected = (*selected + 1) % size;
7324 break;
7326 case KEY_ESC:
7327 status = INPUT_CANCEL;
7328 break;
7330 default:
7331 for (i = 0; items[i].text; i++)
7332 if (items[i].hotkey == key) {
7333 *selected = i;
7334 status = INPUT_STOP;
7335 break;
7336 }
7337 }
7338 }
7340 /* Clear the status window */
7341 status_empty = FALSE;
7342 report("");
7344 return status != INPUT_CANCEL;
7345 }
7347 /*
7348 * Repository properties
7349 */
7351 static struct ref **refs = NULL;
7352 static size_t refs_size = 0;
7353 static struct ref *refs_head = NULL;
7355 static struct ref_list **ref_lists = NULL;
7356 static size_t ref_lists_size = 0;
7358 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7359 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7360 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7362 static int
7363 compare_refs(const void *ref1_, const void *ref2_)
7364 {
7365 const struct ref *ref1 = *(const struct ref **)ref1_;
7366 const struct ref *ref2 = *(const struct ref **)ref2_;
7368 if (ref1->tag != ref2->tag)
7369 return ref2->tag - ref1->tag;
7370 if (ref1->ltag != ref2->ltag)
7371 return ref2->ltag - ref2->ltag;
7372 if (ref1->head != ref2->head)
7373 return ref2->head - ref1->head;
7374 if (ref1->tracked != ref2->tracked)
7375 return ref2->tracked - ref1->tracked;
7376 if (ref1->remote != ref2->remote)
7377 return ref2->remote - ref1->remote;
7378 return strcmp(ref1->name, ref2->name);
7379 }
7381 static void
7382 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7383 {
7384 size_t i;
7386 for (i = 0; i < refs_size; i++)
7387 if (!visitor(data, refs[i]))
7388 break;
7389 }
7391 static struct ref *
7392 get_ref_head()
7393 {
7394 return refs_head;
7395 }
7397 static struct ref_list *
7398 get_ref_list(const char *id)
7399 {
7400 struct ref_list *list;
7401 size_t i;
7403 for (i = 0; i < ref_lists_size; i++)
7404 if (!strcmp(id, ref_lists[i]->id))
7405 return ref_lists[i];
7407 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7408 return NULL;
7409 list = calloc(1, sizeof(*list));
7410 if (!list)
7411 return NULL;
7413 for (i = 0; i < refs_size; i++) {
7414 if (!strcmp(id, refs[i]->id) &&
7415 realloc_refs_list(&list->refs, list->size, 1))
7416 list->refs[list->size++] = refs[i];
7417 }
7419 if (!list->refs) {
7420 free(list);
7421 return NULL;
7422 }
7424 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7425 ref_lists[ref_lists_size++] = list;
7426 return list;
7427 }
7429 static int
7430 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7431 {
7432 struct ref *ref = NULL;
7433 bool tag = FALSE;
7434 bool ltag = FALSE;
7435 bool remote = FALSE;
7436 bool tracked = FALSE;
7437 bool head = FALSE;
7438 int from = 0, to = refs_size - 1;
7440 if (!prefixcmp(name, "refs/tags/")) {
7441 if (!suffixcmp(name, namelen, "^{}")) {
7442 namelen -= 3;
7443 name[namelen] = 0;
7444 } else {
7445 ltag = TRUE;
7446 }
7448 tag = TRUE;
7449 namelen -= STRING_SIZE("refs/tags/");
7450 name += STRING_SIZE("refs/tags/");
7452 } else if (!prefixcmp(name, "refs/remotes/")) {
7453 remote = TRUE;
7454 namelen -= STRING_SIZE("refs/remotes/");
7455 name += STRING_SIZE("refs/remotes/");
7456 tracked = !strcmp(opt_remote, name);
7458 } else if (!prefixcmp(name, "refs/heads/")) {
7459 namelen -= STRING_SIZE("refs/heads/");
7460 name += STRING_SIZE("refs/heads/");
7461 if (!strncmp(opt_head, name, namelen))
7462 return OK;
7464 } else if (!strcmp(name, "HEAD")) {
7465 head = TRUE;
7466 if (*opt_head) {
7467 namelen = strlen(opt_head);
7468 name = opt_head;
7469 }
7470 }
7472 /* If we are reloading or it's an annotated tag, replace the
7473 * previous SHA1 with the resolved commit id; relies on the fact
7474 * git-ls-remote lists the commit id of an annotated tag right
7475 * before the commit id it points to. */
7476 while (from <= to) {
7477 size_t pos = (to + from) / 2;
7478 int cmp = strcmp(name, refs[pos]->name);
7480 if (!cmp) {
7481 ref = refs[pos];
7482 break;
7483 }
7485 if (cmp < 0)
7486 to = pos - 1;
7487 else
7488 from = pos + 1;
7489 }
7491 if (!ref) {
7492 if (!realloc_refs(&refs, refs_size, 1))
7493 return ERR;
7494 ref = calloc(1, sizeof(*ref) + namelen);
7495 if (!ref)
7496 return ERR;
7497 memmove(refs + from + 1, refs + from,
7498 (refs_size - from) * sizeof(*refs));
7499 refs[from] = ref;
7500 strncpy(ref->name, name, namelen);
7501 refs_size++;
7502 }
7504 ref->head = head;
7505 ref->tag = tag;
7506 ref->ltag = ltag;
7507 ref->remote = remote;
7508 ref->tracked = tracked;
7509 string_copy_rev(ref->id, id);
7511 if (head)
7512 refs_head = ref;
7513 return OK;
7514 }
7516 static int
7517 load_refs(void)
7518 {
7519 const char *head_argv[] = {
7520 "git", "symbolic-ref", "HEAD", NULL
7521 };
7522 static const char *ls_remote_argv[SIZEOF_ARG] = {
7523 "git", "ls-remote", opt_git_dir, NULL
7524 };
7525 static bool init = FALSE;
7526 size_t i;
7528 if (!init) {
7529 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7530 die("TIG_LS_REMOTE contains too many arguments");
7531 init = TRUE;
7532 }
7534 if (!*opt_git_dir)
7535 return OK;
7537 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7538 !prefixcmp(opt_head, "refs/heads/")) {
7539 char *offset = opt_head + STRING_SIZE("refs/heads/");
7541 memmove(opt_head, offset, strlen(offset) + 1);
7542 }
7544 refs_head = NULL;
7545 for (i = 0; i < refs_size; i++)
7546 refs[i]->id[0] = 0;
7548 if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7549 return ERR;
7551 /* Update the ref lists to reflect changes. */
7552 for (i = 0; i < ref_lists_size; i++) {
7553 struct ref_list *list = ref_lists[i];
7554 size_t old, new;
7556 for (old = new = 0; old < list->size; old++)
7557 if (!strcmp(list->id, list->refs[old]->id))
7558 list->refs[new++] = list->refs[old];
7559 list->size = new;
7560 }
7562 return OK;
7563 }
7565 static void
7566 set_remote_branch(const char *name, const char *value, size_t valuelen)
7567 {
7568 if (!strcmp(name, ".remote")) {
7569 string_ncopy(opt_remote, value, valuelen);
7571 } else if (*opt_remote && !strcmp(name, ".merge")) {
7572 size_t from = strlen(opt_remote);
7574 if (!prefixcmp(value, "refs/heads/"))
7575 value += STRING_SIZE("refs/heads/");
7577 if (!string_format_from(opt_remote, &from, "/%s", value))
7578 opt_remote[0] = 0;
7579 }
7580 }
7582 static void
7583 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7584 {
7585 const char *argv[SIZEOF_ARG] = { name, "=" };
7586 int argc = 1 + (cmd == option_set_command);
7587 enum option_code error;
7589 if (!argv_from_string(argv, &argc, value))
7590 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7591 else
7592 error = cmd(argc, argv);
7594 if (error != OPT_OK)
7595 warn("Option 'tig.%s': %s", name, option_errors[error]);
7596 }
7598 static bool
7599 set_environment_variable(const char *name, const char *value)
7600 {
7601 size_t len = strlen(name) + 1 + strlen(value) + 1;
7602 char *env = malloc(len);
7604 if (env &&
7605 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7606 putenv(env) == 0)
7607 return TRUE;
7608 free(env);
7609 return FALSE;
7610 }
7612 static void
7613 set_work_tree(const char *value)
7614 {
7615 char cwd[SIZEOF_STR];
7617 if (!getcwd(cwd, sizeof(cwd)))
7618 die("Failed to get cwd path: %s", strerror(errno));
7619 if (chdir(opt_git_dir) < 0)
7620 die("Failed to chdir(%s): %s", strerror(errno));
7621 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7622 die("Failed to get git path: %s", strerror(errno));
7623 if (chdir(cwd) < 0)
7624 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7625 if (chdir(value) < 0)
7626 die("Failed to chdir(%s): %s", value, strerror(errno));
7627 if (!getcwd(cwd, sizeof(cwd)))
7628 die("Failed to get cwd path: %s", strerror(errno));
7629 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7630 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7631 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7632 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7633 opt_is_inside_work_tree = TRUE;
7634 }
7636 static int
7637 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7638 {
7639 if (!strcmp(name, "i18n.commitencoding"))
7640 string_ncopy(opt_encoding, value, valuelen);
7642 else if (!strcmp(name, "core.editor"))
7643 string_ncopy(opt_editor, value, valuelen);
7645 else if (!strcmp(name, "core.worktree"))
7646 set_work_tree(value);
7648 else if (!prefixcmp(name, "tig.color."))
7649 set_repo_config_option(name + 10, value, option_color_command);
7651 else if (!prefixcmp(name, "tig.bind."))
7652 set_repo_config_option(name + 9, value, option_bind_command);
7654 else if (!prefixcmp(name, "tig."))
7655 set_repo_config_option(name + 4, value, option_set_command);
7657 else if (*opt_head && !prefixcmp(name, "branch.") &&
7658 !strncmp(name + 7, opt_head, strlen(opt_head)))
7659 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7661 return OK;
7662 }
7664 static int
7665 load_git_config(void)
7666 {
7667 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7669 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7670 }
7672 static int
7673 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7674 {
7675 if (!opt_git_dir[0]) {
7676 string_ncopy(opt_git_dir, name, namelen);
7678 } else if (opt_is_inside_work_tree == -1) {
7679 /* This can be 3 different values depending on the
7680 * version of git being used. If git-rev-parse does not
7681 * understand --is-inside-work-tree it will simply echo
7682 * the option else either "true" or "false" is printed.
7683 * Default to true for the unknown case. */
7684 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7686 } else if (*name == '.') {
7687 string_ncopy(opt_cdup, name, namelen);
7689 } else {
7690 string_ncopy(opt_prefix, name, namelen);
7691 }
7693 return OK;
7694 }
7696 static int
7697 load_repo_info(void)
7698 {
7699 const char *rev_parse_argv[] = {
7700 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7701 "--show-cdup", "--show-prefix", NULL
7702 };
7704 return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7705 }
7708 /*
7709 * Main
7710 */
7712 static const char usage[] =
7713 "tig " TIG_VERSION " (" __DATE__ ")\n"
7714 "\n"
7715 "Usage: tig [options] [revs] [--] [paths]\n"
7716 " or: tig show [options] [revs] [--] [paths]\n"
7717 " or: tig blame [rev] path\n"
7718 " or: tig status\n"
7719 " or: tig < [git command output]\n"
7720 "\n"
7721 "Options:\n"
7722 " -v, --version Show version and exit\n"
7723 " -h, --help Show help message and exit";
7725 static void __NORETURN
7726 quit(int sig)
7727 {
7728 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7729 if (cursed)
7730 endwin();
7731 exit(0);
7732 }
7734 static void __NORETURN
7735 die(const char *err, ...)
7736 {
7737 va_list args;
7739 endwin();
7741 va_start(args, err);
7742 fputs("tig: ", stderr);
7743 vfprintf(stderr, err, args);
7744 fputs("\n", stderr);
7745 va_end(args);
7747 exit(1);
7748 }
7750 static void
7751 warn(const char *msg, ...)
7752 {
7753 va_list args;
7755 va_start(args, msg);
7756 fputs("tig warning: ", stderr);
7757 vfprintf(stderr, msg, args);
7758 fputs("\n", stderr);
7759 va_end(args);
7760 }
7762 static int
7763 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7764 {
7765 const char ***filter_args = data;
7767 return argv_append(filter_args, name) ? OK : ERR;
7768 }
7770 static void
7771 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7772 {
7773 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7774 const char **all_argv = NULL;
7776 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7777 !argv_append_array(&all_argv, argv) ||
7778 !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7779 die("Failed to split arguments");
7780 argv_free(all_argv);
7781 free(all_argv);
7782 }
7784 static void
7785 filter_options(const char *argv[])
7786 {
7787 filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7788 filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7789 filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7790 }
7792 static enum request
7793 parse_options(int argc, const char *argv[])
7794 {
7795 enum request request = REQ_VIEW_MAIN;
7796 const char *subcommand;
7797 bool seen_dashdash = FALSE;
7798 const char **filter_argv = NULL;
7799 int i;
7801 if (!isatty(STDIN_FILENO))
7802 return REQ_VIEW_PAGER;
7804 if (argc <= 1)
7805 return REQ_VIEW_MAIN;
7807 subcommand = argv[1];
7808 if (!strcmp(subcommand, "status")) {
7809 if (argc > 2)
7810 warn("ignoring arguments after `%s'", subcommand);
7811 return REQ_VIEW_STATUS;
7813 } else if (!strcmp(subcommand, "blame")) {
7814 if (argc <= 2 || argc > 4)
7815 die("invalid number of options to blame\n\n%s", usage);
7817 i = 2;
7818 if (argc == 4) {
7819 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7820 i++;
7821 }
7823 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7824 return REQ_VIEW_BLAME;
7826 } else if (!strcmp(subcommand, "show")) {
7827 request = REQ_VIEW_DIFF;
7829 } else {
7830 subcommand = NULL;
7831 }
7833 for (i = 1 + !!subcommand; i < argc; i++) {
7834 const char *opt = argv[i];
7836 if (seen_dashdash) {
7837 argv_append(&opt_file_argv, opt);
7838 continue;
7840 } else if (!strcmp(opt, "--")) {
7841 seen_dashdash = TRUE;
7842 continue;
7844 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7845 printf("tig version %s\n", TIG_VERSION);
7846 quit(0);
7848 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7849 printf("%s\n", usage);
7850 quit(0);
7852 } else if (!strcmp(opt, "--all")) {
7853 argv_append(&opt_rev_argv, opt);
7854 continue;
7855 }
7857 if (!argv_append(&filter_argv, opt))
7858 die("command too long");
7859 }
7861 if (filter_argv)
7862 filter_options(filter_argv);
7864 return request;
7865 }
7867 int
7868 main(int argc, const char *argv[])
7869 {
7870 const char *codeset = "UTF-8";
7871 enum request request = parse_options(argc, argv);
7872 struct view *view;
7873 size_t i;
7875 signal(SIGINT, quit);
7876 signal(SIGPIPE, SIG_IGN);
7878 if (setlocale(LC_ALL, "")) {
7879 codeset = nl_langinfo(CODESET);
7880 }
7882 if (load_repo_info() == ERR)
7883 die("Failed to load repo info.");
7885 if (load_options() == ERR)
7886 die("Failed to load user config.");
7888 if (load_git_config() == ERR)
7889 die("Failed to load repo config.");
7891 /* Require a git repository unless when running in pager mode. */
7892 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7893 die("Not a git repository");
7895 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7896 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7897 if (opt_iconv_in == ICONV_NONE)
7898 die("Failed to initialize character set conversion");
7899 }
7901 if (codeset && strcmp(codeset, "UTF-8")) {
7902 opt_iconv_out = iconv_open(codeset, "UTF-8");
7903 if (opt_iconv_out == ICONV_NONE)
7904 die("Failed to initialize character set conversion");
7905 }
7907 if (load_refs() == ERR)
7908 die("Failed to load refs.");
7910 foreach_view (view, i) {
7911 if (getenv(view->cmd_env))
7912 warn("Use of the %s environment variable is deprecated,"
7913 " use options or TIG_DIFF_ARGS instead",
7914 view->cmd_env);
7915 if (!argv_from_env(view->ops->argv, view->cmd_env))
7916 die("Too many arguments in the `%s` environment variable",
7917 view->cmd_env);
7918 }
7920 init_display();
7922 while (view_driver(display[current_view], request)) {
7923 int key = get_input(0);
7925 view = display[current_view];
7926 request = get_keybinding(view->keymap, key);
7928 /* Some low-level request handling. This keeps access to
7929 * status_win restricted. */
7930 switch (request) {
7931 case REQ_NONE:
7932 report("Unknown key, press %s for help",
7933 get_key(view->keymap, REQ_VIEW_HELP));
7934 break;
7935 case REQ_PROMPT:
7936 {
7937 char *cmd = read_prompt(":");
7939 if (cmd && isdigit(*cmd)) {
7940 int lineno = view->lineno + 1;
7942 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7943 select_view_line(view, lineno - 1);
7944 report("");
7945 } else {
7946 report("Unable to parse '%s' as a line number", cmd);
7947 }
7949 } else if (cmd) {
7950 struct view *next = VIEW(REQ_VIEW_PAGER);
7951 const char *argv[SIZEOF_ARG] = { "git" };
7952 int argc = 1;
7954 /* When running random commands, initially show the
7955 * command in the title. However, it maybe later be
7956 * overwritten if a commit line is selected. */
7957 string_ncopy(next->ref, cmd, strlen(cmd));
7959 if (!argv_from_string(argv, &argc, cmd)) {
7960 report("Too many arguments");
7961 } else if (!prepare_update(next, argv, NULL)) {
7962 report("Failed to format command");
7963 } else {
7964 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7965 }
7966 }
7968 request = REQ_NONE;
7969 break;
7970 }
7971 case REQ_SEARCH:
7972 case REQ_SEARCH_BACK:
7973 {
7974 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7975 char *search = read_prompt(prompt);
7977 if (search)
7978 string_ncopy(opt_search, search, strlen(search));
7979 else if (*opt_search)
7980 request = request == REQ_SEARCH ?
7981 REQ_FIND_NEXT :
7982 REQ_FIND_PREV;
7983 else
7984 request = REQ_NONE;
7985 break;
7986 }
7987 default:
7988 break;
7989 }
7990 }
7992 quit(0);
7994 return 0;
7995 }