1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164 * Allocation helpers ... Entering macro hell to never be seen again.
165 */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
170 { \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
174 \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
179 } \
180 \
181 return tmp; \
182 }
184 /*
185 * String helpers
186 */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
190 {
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
196 }
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
214 {
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
227 }
228 }
230 dst[size] = 0;
231 }
233 static char *
234 chomp_string(char *name)
235 {
236 int namelen;
238 while (isspace(*name))
239 name++;
241 namelen = strlen(name) - 1;
242 while (namelen > 0 && isspace(name[namelen]))
243 name[namelen--] = 0;
245 return name;
246 }
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
250 {
251 va_list args;
252 size_t pos = bufpos ? *bufpos : 0;
254 va_start(args, fmt);
255 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256 va_end(args);
258 if (bufpos)
259 *bufpos = pos;
261 return pos >= bufsize ? FALSE : TRUE;
262 }
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
272 {
273 size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i = 0; i < len; i++) {
279 if (toupper(str1[i]) == toupper(str2[i]))
280 continue;
282 if (string_enum_sep(str1[i]) &&
283 string_enum_sep(str2[i]))
284 continue;
286 return str1[i] - str2[i];
287 }
289 return 0;
290 }
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296 const char *name;
297 int namelen;
298 int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
305 {
306 static char buf[SIZEOF_STR];
307 int bufpos;
309 for (bufpos = 0; bufpos <= namelen; bufpos++) {
310 buf[bufpos] = tolower(name[bufpos]);
311 if (buf[bufpos] == '_')
312 buf[bufpos] = '-';
313 }
315 buf[bufpos] = 0;
316 return buf;
317 }
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
323 {
324 size_t namelen = strlen(name);
325 int i;
327 for (i = 0; i < map_size; i++)
328 if (enum_equals(map[i], name, namelen)) {
329 *value = map[i].value;
330 return TRUE;
331 }
333 return FALSE;
334 }
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
344 {
345 size_t len = slen >= 0 ? slen : strlen(str);
346 size_t suffixlen = strlen(suffix);
348 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
349 }
352 /*
353 * Unicode / UTF-8 handling
354 *
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358 */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
362 {
363 if (c >= 0x1100 &&
364 (c <= 0x115f /* Hangul Jamo */
365 || c == 0x2329
366 || c == 0x232a
367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
368 /* CJK ... Yi */
369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
373 || (c >= 0xffe0 && c <= 0xffe6)
374 || (c >= 0x20000 && c <= 0x2fffd)
375 || (c >= 0x30000 && c <= 0x3fffd)))
376 return 2;
378 if (c == '\t')
379 return tab_size;
381 return 1;
382 }
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
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 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 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,
394 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,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
399 {
400 int c = *(unsigned char *) string;
402 return utf8_bytes[c];
403 }
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
408 {
409 unsigned long unicode;
411 switch (length) {
412 case 1:
413 unicode = string[0];
414 break;
415 case 2:
416 unicode = (string[0] & 0x1f) << 6;
417 unicode += (string[1] & 0x3f);
418 break;
419 case 3:
420 unicode = (string[0] & 0x0f) << 12;
421 unicode += ((string[1] & 0x3f) << 6);
422 unicode += (string[2] & 0x3f);
423 break;
424 case 4:
425 unicode = (string[0] & 0x0f) << 18;
426 unicode += ((string[1] & 0x3f) << 12);
427 unicode += ((string[2] & 0x3f) << 6);
428 unicode += (string[3] & 0x3f);
429 break;
430 case 5:
431 unicode = (string[0] & 0x0f) << 24;
432 unicode += ((string[1] & 0x3f) << 18);
433 unicode += ((string[2] & 0x3f) << 12);
434 unicode += ((string[3] & 0x3f) << 6);
435 unicode += (string[4] & 0x3f);
436 break;
437 case 6:
438 unicode = (string[0] & 0x01) << 30;
439 unicode += ((string[1] & 0x3f) << 24);
440 unicode += ((string[2] & 0x3f) << 18);
441 unicode += ((string[3] & 0x3f) << 12);
442 unicode += ((string[4] & 0x3f) << 6);
443 unicode += (string[5] & 0x3f);
444 break;
445 default:
446 return 0;
447 }
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode > 0xffff ? 0 : unicode;
452 }
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
458 *
459 * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
462 {
463 const char *string = *start;
464 const char *end = strchr(string, '\0');
465 unsigned char last_bytes = 0;
466 size_t last_ucwidth = 0;
468 *width = 0;
469 *trimmed = 0;
471 while (string < end) {
472 unsigned char bytes = utf8_char_length(string, end);
473 size_t ucwidth;
474 unsigned long unicode;
476 if (string + bytes > end)
477 break;
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode = utf8_to_unicode(string, bytes);
483 /* FIXME: Graceful handling of invalid Unicode character. */
484 if (!unicode)
485 break;
487 ucwidth = unicode_width(unicode, tab_size);
488 if (skip > 0) {
489 skip -= ucwidth <= skip ? ucwidth : skip;
490 *start += bytes;
491 }
492 *width += ucwidth;
493 if (*width > max_width) {
494 *trimmed = 1;
495 *width -= ucwidth;
496 if (reserve && *width == max_width) {
497 string -= last_bytes;
498 *width -= last_ucwidth;
499 }
500 break;
501 }
503 string += bytes;
504 last_bytes = ucwidth ? bytes : 0;
505 last_ucwidth = ucwidth;
506 }
508 return string - *start;
509 }
512 #define DATE_INFO \
513 DATE_(NO), \
514 DATE_(DEFAULT), \
515 DATE_(LOCAL), \
516 DATE_(RELATIVE), \
517 DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521 DATE_INFO
522 #undef DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527 DATE_INFO
528 #undef DATE_
529 };
531 struct time {
532 time_t sec;
533 int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
537 {
538 return t1->sec - t2->sec;
539 }
541 static const char *
542 mkdate(const struct time *time, enum date date)
543 {
544 static char buf[DATE_COLS + 1];
545 static const struct enum_map reldate[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
552 };
553 struct tm tm;
555 if (!date || !time || !time->sec)
556 return "";
558 if (date == DATE_RELATIVE) {
559 struct timeval now;
560 time_t date = time->sec + time->tz;
561 time_t seconds;
562 int i;
564 gettimeofday(&now, NULL);
565 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567 if (seconds >= reldate[i].value)
568 continue;
570 seconds /= reldate[i].namelen;
571 if (!string_format(buf, "%ld %s%s %s",
572 seconds, reldate[i].name,
573 seconds > 1 ? "s" : "",
574 now.tv_sec >= date ? "ago" : "ahead"))
575 break;
576 return buf;
577 }
578 }
580 if (date == DATE_LOCAL) {
581 time_t date = time->sec + time->tz;
582 localtime_r(&date, &tm);
583 }
584 else {
585 gmtime_r(&time->sec, &tm);
586 }
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
674 static void
675 argv_free(const char *argv[])
676 {
677 int argc;
679 if (!argv)
680 return;
681 for (argc = 0; argv[argc]; argc++)
682 free((void *) argv[argc]);
683 argv[0] = NULL;
684 }
686 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
688 static bool
689 argv_append(const char ***argv, const char *arg)
690 {
691 int argc = 0;
693 while (*argv && (*argv)[argc])
694 argc++;
696 if (!argv_realloc(argv, argc, 2))
697 return FALSE;
699 (*argv)[argc++] = strdup(arg);
700 (*argv)[argc] = NULL;
701 return TRUE;
702 }
704 static bool
705 argv_append_array(const char ***dst_argv, const char *src_argv[])
706 {
707 int i;
709 for (i = 0; src_argv && src_argv[i]; i++)
710 if (!argv_append(dst_argv, src_argv[i]))
711 return FALSE;
712 return TRUE;
713 }
715 static bool
716 argv_copy(const char ***dst, const char *src[])
717 {
718 int argc;
720 for (argc = 0; src[argc]; argc++)
721 if (!argv_append(dst, src[argc]))
722 return FALSE;
723 return TRUE;
724 }
727 /*
728 * Executing external commands.
729 */
731 enum io_type {
732 IO_FD, /* File descriptor based IO. */
733 IO_BG, /* Execute command in the background. */
734 IO_FG, /* Execute command with same std{in,out,err}. */
735 IO_RD, /* Read only fork+exec IO. */
736 IO_WR, /* Write only fork+exec IO. */
737 IO_AP, /* Append fork+exec output to file. */
738 };
740 struct io {
741 int pipe; /* Pipe end for reading or writing. */
742 pid_t pid; /* PID of spawned process. */
743 int error; /* Error status. */
744 char *buf; /* Read buffer. */
745 size_t bufalloc; /* Allocated buffer size. */
746 size_t bufsize; /* Buffer content size. */
747 char *bufpos; /* Current buffer position. */
748 unsigned int eof:1; /* Has end of file been reached. */
749 };
751 static void
752 io_init(struct io *io)
753 {
754 memset(io, 0, sizeof(*io));
755 io->pipe = -1;
756 }
758 static bool
759 io_open(struct io *io, const char *fmt, ...)
760 {
761 char name[SIZEOF_STR] = "";
762 bool fits;
763 va_list args;
765 io_init(io);
767 va_start(args, fmt);
768 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
769 va_end(args);
771 if (!fits) {
772 io->error = ENAMETOOLONG;
773 return FALSE;
774 }
775 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
776 if (io->pipe == -1)
777 io->error = errno;
778 return io->pipe != -1;
779 }
781 static bool
782 io_kill(struct io *io)
783 {
784 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
785 }
787 static bool
788 io_done(struct io *io)
789 {
790 pid_t pid = io->pid;
792 if (io->pipe != -1)
793 close(io->pipe);
794 free(io->buf);
795 io_init(io);
797 while (pid > 0) {
798 int status;
799 pid_t waiting = waitpid(pid, &status, 0);
801 if (waiting < 0) {
802 if (errno == EINTR)
803 continue;
804 io->error = errno;
805 return FALSE;
806 }
808 return waiting == pid &&
809 !WIFSIGNALED(status) &&
810 WIFEXITED(status) &&
811 !WEXITSTATUS(status);
812 }
814 return TRUE;
815 }
817 static bool
818 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
819 {
820 int pipefds[2] = { -1, -1 };
821 va_list args;
823 io_init(io);
825 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
826 io->error = errno;
827 return FALSE;
828 } else if (type == IO_AP) {
829 va_start(args, argv);
830 pipefds[1] = va_arg(args, int);
831 va_end(args);
832 }
834 if ((io->pid = fork())) {
835 if (io->pid == -1)
836 io->error = errno;
837 if (pipefds[!(type == IO_WR)] != -1)
838 close(pipefds[!(type == IO_WR)]);
839 if (io->pid != -1) {
840 io->pipe = pipefds[!!(type == IO_WR)];
841 return TRUE;
842 }
844 } else {
845 if (type != IO_FG) {
846 int devnull = open("/dev/null", O_RDWR);
847 int readfd = type == IO_WR ? pipefds[0] : devnull;
848 int writefd = (type == IO_RD || type == IO_AP)
849 ? pipefds[1] : devnull;
851 dup2(readfd, STDIN_FILENO);
852 dup2(writefd, STDOUT_FILENO);
853 dup2(devnull, STDERR_FILENO);
855 close(devnull);
856 if (pipefds[0] != -1)
857 close(pipefds[0]);
858 if (pipefds[1] != -1)
859 close(pipefds[1]);
860 }
862 if (dir && *dir && chdir(dir) == -1)
863 exit(errno);
865 execvp(argv[0], (char *const*) argv);
866 exit(errno);
867 }
869 if (pipefds[!!(type == IO_WR)] != -1)
870 close(pipefds[!!(type == IO_WR)]);
871 return FALSE;
872 }
874 static bool
875 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
876 {
877 struct io io;
879 return io_run(&io, type, dir, argv, fd) && io_done(&io);
880 }
882 static bool
883 io_run_bg(const char **argv)
884 {
885 return io_complete(IO_BG, argv, NULL, -1);
886 }
888 static bool
889 io_run_fg(const char **argv, const char *dir)
890 {
891 return io_complete(IO_FG, argv, dir, -1);
892 }
894 static bool
895 io_run_append(const char **argv, int fd)
896 {
897 return io_complete(IO_AP, argv, NULL, fd);
898 }
900 static bool
901 io_eof(struct io *io)
902 {
903 return io->eof;
904 }
906 static int
907 io_error(struct io *io)
908 {
909 return io->error;
910 }
912 static char *
913 io_strerror(struct io *io)
914 {
915 return strerror(io->error);
916 }
918 static bool
919 io_can_read(struct io *io)
920 {
921 struct timeval tv = { 0, 500 };
922 fd_set fds;
924 FD_ZERO(&fds);
925 FD_SET(io->pipe, &fds);
927 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
928 }
930 static ssize_t
931 io_read(struct io *io, void *buf, size_t bufsize)
932 {
933 do {
934 ssize_t readsize = read(io->pipe, buf, bufsize);
936 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
937 continue;
938 else if (readsize == -1)
939 io->error = errno;
940 else if (readsize == 0)
941 io->eof = 1;
942 return readsize;
943 } while (1);
944 }
946 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
948 static char *
949 io_get(struct io *io, int c, bool can_read)
950 {
951 char *eol;
952 ssize_t readsize;
954 while (TRUE) {
955 if (io->bufsize > 0) {
956 eol = memchr(io->bufpos, c, io->bufsize);
957 if (eol) {
958 char *line = io->bufpos;
960 *eol = 0;
961 io->bufpos = eol + 1;
962 io->bufsize -= io->bufpos - line;
963 return line;
964 }
965 }
967 if (io_eof(io)) {
968 if (io->bufsize) {
969 io->bufpos[io->bufsize] = 0;
970 io->bufsize = 0;
971 return io->bufpos;
972 }
973 return NULL;
974 }
976 if (!can_read)
977 return NULL;
979 if (io->bufsize > 0 && io->bufpos > io->buf)
980 memmove(io->buf, io->bufpos, io->bufsize);
982 if (io->bufalloc == io->bufsize) {
983 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
984 return NULL;
985 io->bufalloc += BUFSIZ;
986 }
988 io->bufpos = io->buf;
989 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
990 if (io_error(io))
991 return NULL;
992 io->bufsize += readsize;
993 }
994 }
996 static bool
997 io_write(struct io *io, const void *buf, size_t bufsize)
998 {
999 size_t written = 0;
1001 while (!io_error(io) && written < bufsize) {
1002 ssize_t size;
1004 size = write(io->pipe, buf + written, bufsize - written);
1005 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1006 continue;
1007 else if (size == -1)
1008 io->error = errno;
1009 else
1010 written += size;
1011 }
1013 return written == bufsize;
1014 }
1016 static bool
1017 io_read_buf(struct io *io, char buf[], size_t bufsize)
1018 {
1019 char *result = io_get(io, '\n', TRUE);
1021 if (result) {
1022 result = chomp_string(result);
1023 string_ncopy_do(buf, bufsize, result, strlen(result));
1024 }
1026 return io_done(io) && result;
1027 }
1029 static bool
1030 io_run_buf(const char **argv, char buf[], size_t bufsize)
1031 {
1032 struct io io;
1034 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1035 }
1037 static int
1038 io_load(struct io *io, const char *separators,
1039 int (*read_property)(char *, size_t, char *, size_t))
1040 {
1041 char *name;
1042 int state = OK;
1044 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1045 char *value;
1046 size_t namelen;
1047 size_t valuelen;
1049 name = chomp_string(name);
1050 namelen = strcspn(name, separators);
1052 if (name[namelen]) {
1053 name[namelen] = 0;
1054 value = chomp_string(name + namelen + 1);
1055 valuelen = strlen(value);
1057 } else {
1058 value = "";
1059 valuelen = 0;
1060 }
1062 state = read_property(name, namelen, value, valuelen);
1063 }
1065 if (state != ERR && io_error(io))
1066 state = ERR;
1067 io_done(io);
1069 return state;
1070 }
1072 static int
1073 io_run_load(const char **argv, const char *separators,
1074 int (*read_property)(char *, size_t, char *, size_t))
1075 {
1076 struct io io;
1078 if (!io_run(&io, IO_RD, NULL, argv))
1079 return ERR;
1080 return io_load(&io, separators, read_property);
1081 }
1084 /*
1085 * User requests
1086 */
1088 #define REQ_INFO \
1089 /* XXX: Keep the view request first and in sync with views[]. */ \
1090 REQ_GROUP("View switching") \
1091 REQ_(VIEW_MAIN, "Show main view"), \
1092 REQ_(VIEW_DIFF, "Show diff view"), \
1093 REQ_(VIEW_LOG, "Show log view"), \
1094 REQ_(VIEW_TREE, "Show tree view"), \
1095 REQ_(VIEW_BLOB, "Show blob view"), \
1096 REQ_(VIEW_BLAME, "Show blame view"), \
1097 REQ_(VIEW_BRANCH, "Show branch view"), \
1098 REQ_(VIEW_HELP, "Show help page"), \
1099 REQ_(VIEW_PAGER, "Show pager view"), \
1100 REQ_(VIEW_STATUS, "Show status view"), \
1101 REQ_(VIEW_STAGE, "Show stage view"), \
1102 \
1103 REQ_GROUP("View manipulation") \
1104 REQ_(ENTER, "Enter current line and scroll"), \
1105 REQ_(NEXT, "Move to next"), \
1106 REQ_(PREVIOUS, "Move to previous"), \
1107 REQ_(PARENT, "Move to parent"), \
1108 REQ_(VIEW_NEXT, "Move focus to next view"), \
1109 REQ_(REFRESH, "Reload and refresh"), \
1110 REQ_(MAXIMIZE, "Maximize the current view"), \
1111 REQ_(VIEW_CLOSE, "Close the current view"), \
1112 REQ_(QUIT, "Close all views and quit"), \
1113 \
1114 REQ_GROUP("View specific requests") \
1115 REQ_(STATUS_UPDATE, "Update file status"), \
1116 REQ_(STATUS_REVERT, "Revert file changes"), \
1117 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1118 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1119 \
1120 REQ_GROUP("Cursor navigation") \
1121 REQ_(MOVE_UP, "Move cursor one line up"), \
1122 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1123 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1124 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1125 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1126 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1127 \
1128 REQ_GROUP("Scrolling") \
1129 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1130 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1131 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1132 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1133 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1134 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1135 \
1136 REQ_GROUP("Searching") \
1137 REQ_(SEARCH, "Search the view"), \
1138 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1139 REQ_(FIND_NEXT, "Find next search match"), \
1140 REQ_(FIND_PREV, "Find previous search match"), \
1141 \
1142 REQ_GROUP("Option manipulation") \
1143 REQ_(OPTIONS, "Open option menu"), \
1144 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1145 REQ_(TOGGLE_DATE, "Toggle date display"), \
1146 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1147 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1148 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1149 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1150 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1151 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1152 \
1153 REQ_GROUP("Misc") \
1154 REQ_(PROMPT, "Bring up the prompt"), \
1155 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1156 REQ_(SHOW_VERSION, "Show version information"), \
1157 REQ_(STOP_LOADING, "Stop all loading views"), \
1158 REQ_(EDIT, "Open in editor"), \
1159 REQ_(NONE, "Do nothing")
1162 /* User action requests. */
1163 enum request {
1164 #define REQ_GROUP(help)
1165 #define REQ_(req, help) REQ_##req
1167 /* Offset all requests to avoid conflicts with ncurses getch values. */
1168 REQ_UNKNOWN = KEY_MAX + 1,
1169 REQ_OFFSET,
1170 REQ_INFO
1172 #undef REQ_GROUP
1173 #undef REQ_
1174 };
1176 struct request_info {
1177 enum request request;
1178 const char *name;
1179 int namelen;
1180 const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186 REQ_INFO
1187 #undef REQ_GROUP
1188 #undef REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1193 {
1194 int namelen = strlen(name);
1195 int i;
1197 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198 if (enum_equals(req_info[i], name, namelen))
1199 return req_info[i].request;
1201 return REQ_UNKNOWN;
1202 }
1205 /*
1206 * Options
1207 */
1209 /* Option and state variables. */
1210 static enum date opt_date = DATE_DEFAULT;
1211 static enum author opt_author = AUTHOR_DEFAULT;
1212 static bool opt_line_number = FALSE;
1213 static bool opt_line_graphics = TRUE;
1214 static bool opt_rev_graph = FALSE;
1215 static bool opt_show_refs = TRUE;
1216 static int opt_num_interval = 5;
1217 static double opt_hscroll = 0.50;
1218 static double opt_scale_split_view = 2.0 / 3.0;
1219 static int opt_tab_size = 8;
1220 static int opt_author_cols = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR] = "";
1222 static char opt_file[SIZEOF_STR] = "";
1223 static char opt_ref[SIZEOF_REF] = "";
1224 static char opt_head[SIZEOF_REF] = "";
1225 static char opt_remote[SIZEOF_REF] = "";
1226 static char opt_encoding[20] = "UTF-8";
1227 static iconv_t opt_iconv_in = ICONV_NONE;
1228 static iconv_t opt_iconv_out = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR] = "";
1230 static char opt_cdup[SIZEOF_STR] = "";
1231 static char opt_prefix[SIZEOF_STR] = "";
1232 static char opt_git_dir[SIZEOF_STR] = "";
1233 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR] = "";
1235 static FILE *opt_tty = NULL;
1236 static const char **opt_diff_args = NULL;
1237 static const char **opt_rev_args = NULL;
1238 static const char **opt_file_args = NULL;
1240 #define is_initial_commit() (!get_ref_head())
1241 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1244 /*
1245 * Line-oriented content detection.
1246 */
1248 #define LINE_INFO \
1249 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1264 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1265 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1266 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1270 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1271 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1274 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1275 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1280 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1281 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1283 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1285 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1286 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1287 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1288 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1293 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1295 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1297 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1298 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1299 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1300 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1301 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1303 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1306 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1307 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1309 enum line_type {
1310 #define LINE(type, line, fg, bg, attr) \
1311 LINE_##type
1312 LINE_INFO,
1313 LINE_NONE
1314 #undef LINE
1315 };
1317 struct line_info {
1318 const char *name; /* Option name. */
1319 int namelen; /* Size of option name. */
1320 const char *line; /* The start of line to match. */
1321 int linelen; /* Size of string to match. */
1322 int fg, bg, attr; /* Color and text attributes for the lines. */
1323 };
1325 static struct line_info line_info[] = {
1326 #define LINE(type, line, fg, bg, attr) \
1327 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1328 LINE_INFO
1329 #undef LINE
1330 };
1332 static enum line_type
1333 get_line_type(const char *line)
1334 {
1335 int linelen = strlen(line);
1336 enum line_type type;
1338 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1339 /* Case insensitive search matches Signed-off-by lines better. */
1340 if (linelen >= line_info[type].linelen &&
1341 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1342 return type;
1344 return LINE_DEFAULT;
1345 }
1347 static inline int
1348 get_line_attr(enum line_type type)
1349 {
1350 assert(type < ARRAY_SIZE(line_info));
1351 return COLOR_PAIR(type) | line_info[type].attr;
1352 }
1354 static struct line_info *
1355 get_line_info(const char *name)
1356 {
1357 size_t namelen = strlen(name);
1358 enum line_type type;
1360 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1361 if (enum_equals(line_info[type], name, namelen))
1362 return &line_info[type];
1364 return NULL;
1365 }
1367 static void
1368 init_colors(void)
1369 {
1370 int default_bg = line_info[LINE_DEFAULT].bg;
1371 int default_fg = line_info[LINE_DEFAULT].fg;
1372 enum line_type type;
1374 start_color();
1376 if (assume_default_colors(default_fg, default_bg) == ERR) {
1377 default_bg = COLOR_BLACK;
1378 default_fg = COLOR_WHITE;
1379 }
1381 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1382 struct line_info *info = &line_info[type];
1383 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1384 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1386 init_pair(type, fg, bg);
1387 }
1388 }
1390 struct line {
1391 enum line_type type;
1393 /* State flags */
1394 unsigned int selected:1;
1395 unsigned int dirty:1;
1396 unsigned int cleareol:1;
1397 unsigned int other:16;
1399 void *data; /* User data */
1400 };
1403 /*
1404 * Keys
1405 */
1407 struct keybinding {
1408 int alias;
1409 enum request request;
1410 };
1412 static struct keybinding default_keybindings[] = {
1413 /* View switching */
1414 { 'm', REQ_VIEW_MAIN },
1415 { 'd', REQ_VIEW_DIFF },
1416 { 'l', REQ_VIEW_LOG },
1417 { 't', REQ_VIEW_TREE },
1418 { 'f', REQ_VIEW_BLOB },
1419 { 'B', REQ_VIEW_BLAME },
1420 { 'H', REQ_VIEW_BRANCH },
1421 { 'p', REQ_VIEW_PAGER },
1422 { 'h', REQ_VIEW_HELP },
1423 { 'S', REQ_VIEW_STATUS },
1424 { 'c', REQ_VIEW_STAGE },
1426 /* View manipulation */
1427 { 'q', REQ_VIEW_CLOSE },
1428 { KEY_TAB, REQ_VIEW_NEXT },
1429 { KEY_RETURN, REQ_ENTER },
1430 { KEY_UP, REQ_PREVIOUS },
1431 { KEY_DOWN, REQ_NEXT },
1432 { 'R', REQ_REFRESH },
1433 { KEY_F(5), REQ_REFRESH },
1434 { 'O', REQ_MAXIMIZE },
1436 /* Cursor navigation */
1437 { 'k', REQ_MOVE_UP },
1438 { 'j', REQ_MOVE_DOWN },
1439 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1440 { KEY_END, REQ_MOVE_LAST_LINE },
1441 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1442 { ' ', REQ_MOVE_PAGE_DOWN },
1443 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1444 { 'b', REQ_MOVE_PAGE_UP },
1445 { '-', REQ_MOVE_PAGE_UP },
1447 /* Scrolling */
1448 { KEY_LEFT, REQ_SCROLL_LEFT },
1449 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1450 { KEY_IC, REQ_SCROLL_LINE_UP },
1451 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1452 { 'w', REQ_SCROLL_PAGE_UP },
1453 { 's', REQ_SCROLL_PAGE_DOWN },
1455 /* Searching */
1456 { '/', REQ_SEARCH },
1457 { '?', REQ_SEARCH_BACK },
1458 { 'n', REQ_FIND_NEXT },
1459 { 'N', REQ_FIND_PREV },
1461 /* Misc */
1462 { 'Q', REQ_QUIT },
1463 { 'z', REQ_STOP_LOADING },
1464 { 'v', REQ_SHOW_VERSION },
1465 { 'r', REQ_SCREEN_REDRAW },
1466 { 'o', REQ_OPTIONS },
1467 { '.', REQ_TOGGLE_LINENO },
1468 { 'D', REQ_TOGGLE_DATE },
1469 { 'A', REQ_TOGGLE_AUTHOR },
1470 { 'g', REQ_TOGGLE_REV_GRAPH },
1471 { 'F', REQ_TOGGLE_REFS },
1472 { 'I', REQ_TOGGLE_SORT_ORDER },
1473 { 'i', REQ_TOGGLE_SORT_FIELD },
1474 { ':', REQ_PROMPT },
1475 { 'u', REQ_STATUS_UPDATE },
1476 { '!', REQ_STATUS_REVERT },
1477 { 'M', REQ_STATUS_MERGE },
1478 { '@', REQ_STAGE_NEXT },
1479 { ',', REQ_PARENT },
1480 { 'e', REQ_EDIT },
1481 };
1483 #define KEYMAP_INFO \
1484 KEYMAP_(GENERIC), \
1485 KEYMAP_(MAIN), \
1486 KEYMAP_(DIFF), \
1487 KEYMAP_(LOG), \
1488 KEYMAP_(TREE), \
1489 KEYMAP_(BLOB), \
1490 KEYMAP_(BLAME), \
1491 KEYMAP_(BRANCH), \
1492 KEYMAP_(PAGER), \
1493 KEYMAP_(HELP), \
1494 KEYMAP_(STATUS), \
1495 KEYMAP_(STAGE)
1497 enum keymap {
1498 #define KEYMAP_(name) KEYMAP_##name
1499 KEYMAP_INFO
1500 #undef KEYMAP_
1501 };
1503 static const struct enum_map keymap_table[] = {
1504 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1505 KEYMAP_INFO
1506 #undef KEYMAP_
1507 };
1509 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1511 struct keybinding_table {
1512 struct keybinding *data;
1513 size_t size;
1514 };
1516 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1518 static void
1519 add_keybinding(enum keymap keymap, enum request request, int key)
1520 {
1521 struct keybinding_table *table = &keybindings[keymap];
1522 size_t i;
1524 for (i = 0; i < keybindings[keymap].size; i++) {
1525 if (keybindings[keymap].data[i].alias == key) {
1526 keybindings[keymap].data[i].request = request;
1527 return;
1528 }
1529 }
1531 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1532 if (!table->data)
1533 die("Failed to allocate keybinding");
1534 table->data[table->size].alias = key;
1535 table->data[table->size++].request = request;
1537 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1538 int i;
1540 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541 if (default_keybindings[i].alias == key)
1542 default_keybindings[i].request = REQ_NONE;
1543 }
1544 }
1546 /* Looks for a key binding first in the given map, then in the generic map, and
1547 * lastly in the default keybindings. */
1548 static enum request
1549 get_keybinding(enum keymap keymap, int key)
1550 {
1551 size_t i;
1553 for (i = 0; i < keybindings[keymap].size; i++)
1554 if (keybindings[keymap].data[i].alias == key)
1555 return keybindings[keymap].data[i].request;
1557 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1558 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1559 return keybindings[KEYMAP_GENERIC].data[i].request;
1561 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562 if (default_keybindings[i].alias == key)
1563 return default_keybindings[i].request;
1565 return (enum request) key;
1566 }
1569 struct key {
1570 const char *name;
1571 int value;
1572 };
1574 static const struct key key_table[] = {
1575 { "Enter", KEY_RETURN },
1576 { "Space", ' ' },
1577 { "Backspace", KEY_BACKSPACE },
1578 { "Tab", KEY_TAB },
1579 { "Escape", KEY_ESC },
1580 { "Left", KEY_LEFT },
1581 { "Right", KEY_RIGHT },
1582 { "Up", KEY_UP },
1583 { "Down", KEY_DOWN },
1584 { "Insert", KEY_IC },
1585 { "Delete", KEY_DC },
1586 { "Hash", '#' },
1587 { "Home", KEY_HOME },
1588 { "End", KEY_END },
1589 { "PageUp", KEY_PPAGE },
1590 { "PageDown", KEY_NPAGE },
1591 { "F1", KEY_F(1) },
1592 { "F2", KEY_F(2) },
1593 { "F3", KEY_F(3) },
1594 { "F4", KEY_F(4) },
1595 { "F5", KEY_F(5) },
1596 { "F6", KEY_F(6) },
1597 { "F7", KEY_F(7) },
1598 { "F8", KEY_F(8) },
1599 { "F9", KEY_F(9) },
1600 { "F10", KEY_F(10) },
1601 { "F11", KEY_F(11) },
1602 { "F12", KEY_F(12) },
1603 };
1605 static int
1606 get_key_value(const char *name)
1607 {
1608 int i;
1610 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1611 if (!strcasecmp(key_table[i].name, name))
1612 return key_table[i].value;
1614 if (strlen(name) == 1 && isprint(*name))
1615 return (int) *name;
1617 return ERR;
1618 }
1620 static const char *
1621 get_key_name(int key_value)
1622 {
1623 static char key_char[] = "'X'";
1624 const char *seq = NULL;
1625 int key;
1627 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1628 if (key_table[key].value == key_value)
1629 seq = key_table[key].name;
1631 if (seq == NULL &&
1632 key_value < 127 &&
1633 isprint(key_value)) {
1634 key_char[1] = (char) key_value;
1635 seq = key_char;
1636 }
1638 return seq ? seq : "(no key)";
1639 }
1641 static bool
1642 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1643 {
1644 const char *sep = *pos > 0 ? ", " : "";
1645 const char *keyname = get_key_name(keybinding->alias);
1647 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1648 }
1650 static bool
1651 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1652 enum keymap keymap, bool all)
1653 {
1654 int i;
1656 for (i = 0; i < keybindings[keymap].size; i++) {
1657 if (keybindings[keymap].data[i].request == request) {
1658 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1659 return FALSE;
1660 if (!all)
1661 break;
1662 }
1663 }
1665 return TRUE;
1666 }
1668 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1670 static const char *
1671 get_keys(enum keymap keymap, enum request request, bool all)
1672 {
1673 static char buf[BUFSIZ];
1674 size_t pos = 0;
1675 int i;
1677 buf[pos] = 0;
1679 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1680 return "Too many keybindings!";
1681 if (pos > 0 && !all)
1682 return buf;
1684 if (keymap != KEYMAP_GENERIC) {
1685 /* Only the generic keymap includes the default keybindings when
1686 * listing all keys. */
1687 if (all)
1688 return buf;
1690 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1691 return "Too many keybindings!";
1692 if (pos)
1693 return buf;
1694 }
1696 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1697 if (default_keybindings[i].request == request) {
1698 if (!append_key(buf, &pos, &default_keybindings[i]))
1699 return "Too many keybindings!";
1700 if (!all)
1701 return buf;
1702 }
1703 }
1705 return buf;
1706 }
1708 struct run_request {
1709 enum keymap keymap;
1710 int key;
1711 const char **argv;
1712 };
1714 static struct run_request *run_request;
1715 static size_t run_requests;
1717 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1719 static enum request
1720 add_run_request(enum keymap keymap, int key, const char **argv)
1721 {
1722 struct run_request *req;
1724 if (!realloc_run_requests(&run_request, run_requests, 1))
1725 return REQ_NONE;
1727 req = &run_request[run_requests];
1728 req->keymap = keymap;
1729 req->key = key;
1730 req->argv = NULL;
1732 if (!argv_copy(&req->argv, argv))
1733 return REQ_NONE;
1735 return REQ_NONE + ++run_requests;
1736 }
1738 static struct run_request *
1739 get_run_request(enum request request)
1740 {
1741 if (request <= REQ_NONE)
1742 return NULL;
1743 return &run_request[request - REQ_NONE - 1];
1744 }
1746 static void
1747 add_builtin_run_requests(void)
1748 {
1749 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1750 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1751 const char *commit[] = { "git", "commit", NULL };
1752 const char *gc[] = { "git", "gc", NULL };
1753 struct run_request reqs[] = {
1754 { KEYMAP_MAIN, 'C', cherry_pick },
1755 { KEYMAP_STATUS, 'C', commit },
1756 { KEYMAP_BRANCH, 'C', checkout },
1757 { KEYMAP_GENERIC, 'G', gc },
1758 };
1759 int i;
1761 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1762 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1764 if (req != reqs[i].key)
1765 continue;
1766 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1767 if (req != REQ_NONE)
1768 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1769 }
1770 }
1772 /*
1773 * User config file handling.
1774 */
1776 static int config_lineno;
1777 static bool config_errors;
1778 static const char *config_msg;
1780 static const struct enum_map color_map[] = {
1781 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1782 COLOR_MAP(DEFAULT),
1783 COLOR_MAP(BLACK),
1784 COLOR_MAP(BLUE),
1785 COLOR_MAP(CYAN),
1786 COLOR_MAP(GREEN),
1787 COLOR_MAP(MAGENTA),
1788 COLOR_MAP(RED),
1789 COLOR_MAP(WHITE),
1790 COLOR_MAP(YELLOW),
1791 };
1793 static const struct enum_map attr_map[] = {
1794 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1795 ATTR_MAP(NORMAL),
1796 ATTR_MAP(BLINK),
1797 ATTR_MAP(BOLD),
1798 ATTR_MAP(DIM),
1799 ATTR_MAP(REVERSE),
1800 ATTR_MAP(STANDOUT),
1801 ATTR_MAP(UNDERLINE),
1802 };
1804 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1806 static int parse_step(double *opt, const char *arg)
1807 {
1808 *opt = atoi(arg);
1809 if (!strchr(arg, '%'))
1810 return OK;
1812 /* "Shift down" so 100% and 1 does not conflict. */
1813 *opt = (*opt - 1) / 100;
1814 if (*opt >= 1.0) {
1815 *opt = 0.99;
1816 config_msg = "Step value larger than 100%";
1817 return ERR;
1818 }
1819 if (*opt < 0.0) {
1820 *opt = 1;
1821 config_msg = "Invalid step value";
1822 return ERR;
1823 }
1824 return OK;
1825 }
1827 static int
1828 parse_int(int *opt, const char *arg, int min, int max)
1829 {
1830 int value = atoi(arg);
1832 if (min <= value && value <= max) {
1833 *opt = value;
1834 return OK;
1835 }
1837 config_msg = "Integer value out of bound";
1838 return ERR;
1839 }
1841 static bool
1842 set_color(int *color, const char *name)
1843 {
1844 if (map_enum(color, color_map, name))
1845 return TRUE;
1846 if (!prefixcmp(name, "color"))
1847 return parse_int(color, name + 5, 0, 255) == OK;
1848 return FALSE;
1849 }
1851 /* Wants: object fgcolor bgcolor [attribute] */
1852 static int
1853 option_color_command(int argc, const char *argv[])
1854 {
1855 struct line_info *info;
1857 if (argc < 3) {
1858 config_msg = "Wrong number of arguments given to color command";
1859 return ERR;
1860 }
1862 info = get_line_info(argv[0]);
1863 if (!info) {
1864 static const struct enum_map obsolete[] = {
1865 ENUM_MAP("main-delim", LINE_DELIMITER),
1866 ENUM_MAP("main-date", LINE_DATE),
1867 ENUM_MAP("main-author", LINE_AUTHOR),
1868 };
1869 int index;
1871 if (!map_enum(&index, obsolete, argv[0])) {
1872 config_msg = "Unknown color name";
1873 return ERR;
1874 }
1875 info = &line_info[index];
1876 }
1878 if (!set_color(&info->fg, argv[1]) ||
1879 !set_color(&info->bg, argv[2])) {
1880 config_msg = "Unknown color";
1881 return ERR;
1882 }
1884 info->attr = 0;
1885 while (argc-- > 3) {
1886 int attr;
1888 if (!set_attribute(&attr, argv[argc])) {
1889 config_msg = "Unknown attribute";
1890 return ERR;
1891 }
1892 info->attr |= attr;
1893 }
1895 return OK;
1896 }
1898 static int parse_bool(bool *opt, const char *arg)
1899 {
1900 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1901 ? TRUE : FALSE;
1902 return OK;
1903 }
1905 static int parse_enum_do(unsigned int *opt, const char *arg,
1906 const struct enum_map *map, size_t map_size)
1907 {
1908 bool is_true;
1910 assert(map_size > 1);
1912 if (map_enum_do(map, map_size, (int *) opt, arg))
1913 return OK;
1915 if (parse_bool(&is_true, arg) != OK)
1916 return ERR;
1918 *opt = is_true ? map[1].value : map[0].value;
1919 return OK;
1920 }
1922 #define parse_enum(opt, arg, map) \
1923 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1925 static int
1926 parse_string(char *opt, const char *arg, size_t optsize)
1927 {
1928 int arglen = strlen(arg);
1930 switch (arg[0]) {
1931 case '\"':
1932 case '\'':
1933 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1934 config_msg = "Unmatched quotation";
1935 return ERR;
1936 }
1937 arg += 1; arglen -= 2;
1938 default:
1939 string_ncopy_do(opt, optsize, arg, arglen);
1940 return OK;
1941 }
1942 }
1944 /* Wants: name = value */
1945 static int
1946 option_set_command(int argc, const char *argv[])
1947 {
1948 if (argc != 3) {
1949 config_msg = "Wrong number of arguments given to set command";
1950 return ERR;
1951 }
1953 if (strcmp(argv[1], "=")) {
1954 config_msg = "No value assigned";
1955 return ERR;
1956 }
1958 if (!strcmp(argv[0], "show-author"))
1959 return parse_enum(&opt_author, argv[2], author_map);
1961 if (!strcmp(argv[0], "show-date"))
1962 return parse_enum(&opt_date, argv[2], date_map);
1964 if (!strcmp(argv[0], "show-rev-graph"))
1965 return parse_bool(&opt_rev_graph, argv[2]);
1967 if (!strcmp(argv[0], "show-refs"))
1968 return parse_bool(&opt_show_refs, argv[2]);
1970 if (!strcmp(argv[0], "show-line-numbers"))
1971 return parse_bool(&opt_line_number, argv[2]);
1973 if (!strcmp(argv[0], "line-graphics"))
1974 return parse_bool(&opt_line_graphics, argv[2]);
1976 if (!strcmp(argv[0], "line-number-interval"))
1977 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1979 if (!strcmp(argv[0], "author-width"))
1980 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1982 if (!strcmp(argv[0], "horizontal-scroll"))
1983 return parse_step(&opt_hscroll, argv[2]);
1985 if (!strcmp(argv[0], "split-view-height"))
1986 return parse_step(&opt_scale_split_view, argv[2]);
1988 if (!strcmp(argv[0], "tab-size"))
1989 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1991 if (!strcmp(argv[0], "commit-encoding"))
1992 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1994 config_msg = "Unknown variable name";
1995 return ERR;
1996 }
1998 /* Wants: mode request key */
1999 static int
2000 option_bind_command(int argc, const char *argv[])
2001 {
2002 enum request request;
2003 int keymap = -1;
2004 int key;
2006 if (argc < 3) {
2007 config_msg = "Wrong number of arguments given to bind command";
2008 return ERR;
2009 }
2011 if (!set_keymap(&keymap, argv[0])) {
2012 config_msg = "Unknown key map";
2013 return ERR;
2014 }
2016 key = get_key_value(argv[1]);
2017 if (key == ERR) {
2018 config_msg = "Unknown key";
2019 return ERR;
2020 }
2022 request = get_request(argv[2]);
2023 if (request == REQ_UNKNOWN) {
2024 static const struct enum_map obsolete[] = {
2025 ENUM_MAP("cherry-pick", REQ_NONE),
2026 ENUM_MAP("screen-resize", REQ_NONE),
2027 ENUM_MAP("tree-parent", REQ_PARENT),
2028 };
2029 int alias;
2031 if (map_enum(&alias, obsolete, argv[2])) {
2032 if (alias != REQ_NONE)
2033 add_keybinding(keymap, alias, key);
2034 config_msg = "Obsolete request name";
2035 return ERR;
2036 }
2037 }
2038 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2039 request = add_run_request(keymap, key, argv + 2);
2040 if (request == REQ_UNKNOWN) {
2041 config_msg = "Unknown request name";
2042 return ERR;
2043 }
2045 add_keybinding(keymap, request, key);
2047 return OK;
2048 }
2050 static int
2051 set_option(const char *opt, char *value)
2052 {
2053 const char *argv[SIZEOF_ARG];
2054 int argc = 0;
2056 if (!argv_from_string(argv, &argc, value)) {
2057 config_msg = "Too many option arguments";
2058 return ERR;
2059 }
2061 if (!strcmp(opt, "color"))
2062 return option_color_command(argc, argv);
2064 if (!strcmp(opt, "set"))
2065 return option_set_command(argc, argv);
2067 if (!strcmp(opt, "bind"))
2068 return option_bind_command(argc, argv);
2070 config_msg = "Unknown option command";
2071 return ERR;
2072 }
2074 static int
2075 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2076 {
2077 int status = OK;
2079 config_lineno++;
2080 config_msg = "Internal error";
2082 /* Check for comment markers, since read_properties() will
2083 * only ensure opt and value are split at first " \t". */
2084 optlen = strcspn(opt, "#");
2085 if (optlen == 0)
2086 return OK;
2088 if (opt[optlen] != 0) {
2089 config_msg = "No option value";
2090 status = ERR;
2092 } else {
2093 /* Look for comment endings in the value. */
2094 size_t len = strcspn(value, "#");
2096 if (len < valuelen) {
2097 valuelen = len;
2098 value[valuelen] = 0;
2099 }
2101 status = set_option(opt, value);
2102 }
2104 if (status == ERR) {
2105 warn("Error on line %d, near '%.*s': %s",
2106 config_lineno, (int) optlen, opt, config_msg);
2107 config_errors = TRUE;
2108 }
2110 /* Always keep going if errors are encountered. */
2111 return OK;
2112 }
2114 static void
2115 load_option_file(const char *path)
2116 {
2117 struct io io;
2119 /* It's OK that the file doesn't exist. */
2120 if (!io_open(&io, "%s", path))
2121 return;
2123 config_lineno = 0;
2124 config_errors = FALSE;
2126 if (io_load(&io, " \t", read_option) == ERR ||
2127 config_errors == TRUE)
2128 warn("Errors while loading %s.", path);
2129 }
2131 static int
2132 load_options(void)
2133 {
2134 const char *home = getenv("HOME");
2135 const char *tigrc_user = getenv("TIGRC_USER");
2136 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2137 char buf[SIZEOF_STR];
2139 if (!tigrc_system)
2140 tigrc_system = SYSCONFDIR "/tigrc";
2141 load_option_file(tigrc_system);
2143 if (!tigrc_user) {
2144 if (!home || !string_format(buf, "%s/.tigrc", home))
2145 return ERR;
2146 tigrc_user = buf;
2147 }
2148 load_option_file(tigrc_user);
2150 /* Add _after_ loading config files to avoid adding run requests
2151 * that conflict with keybindings. */
2152 add_builtin_run_requests();
2154 return OK;
2155 }
2158 /*
2159 * The viewer
2160 */
2162 struct view;
2163 struct view_ops;
2165 /* The display array of active views and the index of the current view. */
2166 static struct view *display[2];
2167 static unsigned int current_view;
2169 #define foreach_displayed_view(view, i) \
2170 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2172 #define displayed_views() (display[1] != NULL ? 2 : 1)
2174 /* Current head and commit ID */
2175 static char ref_blob[SIZEOF_REF] = "";
2176 static char ref_commit[SIZEOF_REF] = "HEAD";
2177 static char ref_head[SIZEOF_REF] = "HEAD";
2178 static char ref_branch[SIZEOF_REF] = "";
2180 enum view_type {
2181 VIEW_MAIN,
2182 VIEW_DIFF,
2183 VIEW_LOG,
2184 VIEW_TREE,
2185 VIEW_BLOB,
2186 VIEW_BLAME,
2187 VIEW_BRANCH,
2188 VIEW_HELP,
2189 VIEW_PAGER,
2190 VIEW_STATUS,
2191 VIEW_STAGE,
2192 };
2194 struct view {
2195 enum view_type type; /* View type */
2196 const char *name; /* View name */
2197 const char *cmd_env; /* Command line set via environment */
2198 const char *id; /* Points to either of ref_{head,commit,blob} */
2200 struct view_ops *ops; /* View operations */
2202 enum keymap keymap; /* What keymap does this view have */
2203 bool git_dir; /* Whether the view requires a git directory. */
2205 char ref[SIZEOF_REF]; /* Hovered commit reference */
2206 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2208 int height, width; /* The width and height of the main window */
2209 WINDOW *win; /* The main window */
2210 WINDOW *title; /* The title window living below the main window */
2212 /* Navigation */
2213 unsigned long offset; /* Offset of the window top */
2214 unsigned long yoffset; /* Offset from the window side. */
2215 unsigned long lineno; /* Current line number */
2216 unsigned long p_offset; /* Previous offset of the window top */
2217 unsigned long p_yoffset;/* Previous offset from the window side */
2218 unsigned long p_lineno; /* Previous current line number */
2219 bool p_restore; /* Should the previous position be restored. */
2221 /* Searching */
2222 char grep[SIZEOF_STR]; /* Search string */
2223 regex_t *regex; /* Pre-compiled regexp */
2225 /* If non-NULL, points to the view that opened this view. If this view
2226 * is closed tig will switch back to the parent view. */
2227 struct view *parent;
2228 struct view *prev;
2230 /* Buffering */
2231 size_t lines; /* Total number of lines */
2232 struct line *line; /* Line index */
2233 unsigned int digits; /* Number of digits in the lines member. */
2235 /* Drawing */
2236 struct line *curline; /* Line currently being drawn. */
2237 enum line_type curtype; /* Attribute currently used for drawing. */
2238 unsigned long col; /* Column when drawing. */
2239 bool has_scrolled; /* View was scrolled. */
2241 /* Loading */
2242 const char **argv; /* Shell command arguments. */
2243 const char *dir; /* Directory from which to execute. */
2244 struct io io;
2245 struct io *pipe;
2246 time_t start_time;
2247 time_t update_secs;
2248 };
2250 struct view_ops {
2251 /* What type of content being displayed. Used in the title bar. */
2252 const char *type;
2253 /* Default command arguments. */
2254 const char **argv;
2255 /* Open and reads in all view content. */
2256 bool (*open)(struct view *view);
2257 /* Read one line; updates view->line. */
2258 bool (*read)(struct view *view, char *data);
2259 /* Draw one line; @lineno must be < view->height. */
2260 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2261 /* Depending on view handle a special requests. */
2262 enum request (*request)(struct view *view, enum request request, struct line *line);
2263 /* Search for regexp in a line. */
2264 bool (*grep)(struct view *view, struct line *line);
2265 /* Select line */
2266 void (*select)(struct view *view, struct line *line);
2267 /* Prepare view for loading */
2268 bool (*prepare)(struct view *view);
2269 };
2271 static struct view_ops blame_ops;
2272 static struct view_ops blob_ops;
2273 static struct view_ops diff_ops;
2274 static struct view_ops help_ops;
2275 static struct view_ops log_ops;
2276 static struct view_ops main_ops;
2277 static struct view_ops pager_ops;
2278 static struct view_ops stage_ops;
2279 static struct view_ops status_ops;
2280 static struct view_ops tree_ops;
2281 static struct view_ops branch_ops;
2283 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2284 { type, name, #env, ref, ops, map, git }
2286 #define VIEW_(id, name, ops, git, ref) \
2287 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2289 static struct view views[] = {
2290 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2291 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2292 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2293 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2294 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2295 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2296 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2297 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2298 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2299 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2300 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2301 };
2303 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2305 #define foreach_view(view, i) \
2306 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2308 #define view_is_displayed(view) \
2309 (view == display[0] || view == display[1])
2311 static enum request
2312 view_request(struct view *view, enum request request)
2313 {
2314 if (!view || !view->lines)
2315 return request;
2316 return view->ops->request(view, request, &view->line[view->lineno]);
2317 }
2320 /*
2321 * View drawing.
2322 */
2324 static inline void
2325 set_view_attr(struct view *view, enum line_type type)
2326 {
2327 if (!view->curline->selected && view->curtype != type) {
2328 (void) wattrset(view->win, get_line_attr(type));
2329 wchgat(view->win, -1, 0, type, NULL);
2330 view->curtype = type;
2331 }
2332 }
2334 static int
2335 draw_chars(struct view *view, enum line_type type, const char *string,
2336 int max_len, bool use_tilde)
2337 {
2338 static char out_buffer[BUFSIZ * 2];
2339 int len = 0;
2340 int col = 0;
2341 int trimmed = FALSE;
2342 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2344 if (max_len <= 0)
2345 return 0;
2347 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2349 set_view_attr(view, type);
2350 if (len > 0) {
2351 if (opt_iconv_out != ICONV_NONE) {
2352 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2353 size_t inlen = len + 1;
2355 char *outbuf = out_buffer;
2356 size_t outlen = sizeof(out_buffer);
2358 size_t ret;
2360 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2361 if (ret != (size_t) -1) {
2362 string = out_buffer;
2363 len = sizeof(out_buffer) - outlen;
2364 }
2365 }
2367 waddnstr(view->win, string, len);
2368 }
2369 if (trimmed && use_tilde) {
2370 set_view_attr(view, LINE_DELIMITER);
2371 waddch(view->win, '~');
2372 col++;
2373 }
2375 return col;
2376 }
2378 static int
2379 draw_space(struct view *view, enum line_type type, int max, int spaces)
2380 {
2381 static char space[] = " ";
2382 int col = 0;
2384 spaces = MIN(max, spaces);
2386 while (spaces > 0) {
2387 int len = MIN(spaces, sizeof(space) - 1);
2389 col += draw_chars(view, type, space, len, FALSE);
2390 spaces -= len;
2391 }
2393 return col;
2394 }
2396 static bool
2397 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2398 {
2399 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2400 return view->width + view->yoffset <= view->col;
2401 }
2403 static bool
2404 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2405 {
2406 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2407 int max = view->width + view->yoffset - view->col;
2408 int i;
2410 if (max < size)
2411 size = max;
2413 set_view_attr(view, type);
2414 /* Using waddch() instead of waddnstr() ensures that
2415 * they'll be rendered correctly for the cursor line. */
2416 for (i = skip; i < size; i++)
2417 waddch(view->win, graphic[i]);
2419 view->col += size;
2420 if (size < max && skip <= size)
2421 waddch(view->win, ' ');
2422 view->col++;
2424 return view->width + view->yoffset <= view->col;
2425 }
2427 static bool
2428 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2429 {
2430 int max = MIN(view->width + view->yoffset - view->col, len);
2431 int col;
2433 if (text)
2434 col = draw_chars(view, type, text, max - 1, trim);
2435 else
2436 col = draw_space(view, type, max - 1, max - 1);
2438 view->col += col;
2439 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2440 return view->width + view->yoffset <= view->col;
2441 }
2443 static bool
2444 draw_date(struct view *view, struct time *time)
2445 {
2446 const char *date = mkdate(time, opt_date);
2447 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2449 return draw_field(view, LINE_DATE, date, cols, FALSE);
2450 }
2452 static bool
2453 draw_author(struct view *view, const char *author)
2454 {
2455 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2456 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2458 if (abbreviate && author)
2459 author = get_author_initials(author);
2461 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2462 }
2464 static bool
2465 draw_mode(struct view *view, mode_t mode)
2466 {
2467 const char *str;
2469 if (S_ISDIR(mode))
2470 str = "drwxr-xr-x";
2471 else if (S_ISLNK(mode))
2472 str = "lrwxrwxrwx";
2473 else if (S_ISGITLINK(mode))
2474 str = "m---------";
2475 else if (S_ISREG(mode) && mode & S_IXUSR)
2476 str = "-rwxr-xr-x";
2477 else if (S_ISREG(mode))
2478 str = "-rw-r--r--";
2479 else
2480 str = "----------";
2482 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2483 }
2485 static bool
2486 draw_lineno(struct view *view, unsigned int lineno)
2487 {
2488 char number[10];
2489 int digits3 = view->digits < 3 ? 3 : view->digits;
2490 int max = MIN(view->width + view->yoffset - view->col, digits3);
2491 char *text = NULL;
2492 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2494 lineno += view->offset + 1;
2495 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2496 static char fmt[] = "%1ld";
2498 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2499 if (string_format(number, fmt, lineno))
2500 text = number;
2501 }
2502 if (text)
2503 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2504 else
2505 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2506 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2507 }
2509 static bool
2510 draw_view_line(struct view *view, unsigned int lineno)
2511 {
2512 struct line *line;
2513 bool selected = (view->offset + lineno == view->lineno);
2515 assert(view_is_displayed(view));
2517 if (view->offset + lineno >= view->lines)
2518 return FALSE;
2520 line = &view->line[view->offset + lineno];
2522 wmove(view->win, lineno, 0);
2523 if (line->cleareol)
2524 wclrtoeol(view->win);
2525 view->col = 0;
2526 view->curline = line;
2527 view->curtype = LINE_NONE;
2528 line->selected = FALSE;
2529 line->dirty = line->cleareol = 0;
2531 if (selected) {
2532 set_view_attr(view, LINE_CURSOR);
2533 line->selected = TRUE;
2534 view->ops->select(view, line);
2535 }
2537 return view->ops->draw(view, line, lineno);
2538 }
2540 static void
2541 redraw_view_dirty(struct view *view)
2542 {
2543 bool dirty = FALSE;
2544 int lineno;
2546 for (lineno = 0; lineno < view->height; lineno++) {
2547 if (view->offset + lineno >= view->lines)
2548 break;
2549 if (!view->line[view->offset + lineno].dirty)
2550 continue;
2551 dirty = TRUE;
2552 if (!draw_view_line(view, lineno))
2553 break;
2554 }
2556 if (!dirty)
2557 return;
2558 wnoutrefresh(view->win);
2559 }
2561 static void
2562 redraw_view_from(struct view *view, int lineno)
2563 {
2564 assert(0 <= lineno && lineno < view->height);
2566 for (; lineno < view->height; lineno++) {
2567 if (!draw_view_line(view, lineno))
2568 break;
2569 }
2571 wnoutrefresh(view->win);
2572 }
2574 static void
2575 redraw_view(struct view *view)
2576 {
2577 werase(view->win);
2578 redraw_view_from(view, 0);
2579 }
2582 static void
2583 update_view_title(struct view *view)
2584 {
2585 char buf[SIZEOF_STR];
2586 char state[SIZEOF_STR];
2587 size_t bufpos = 0, statelen = 0;
2589 assert(view_is_displayed(view));
2591 if (view->type != VIEW_STATUS && view->lines) {
2592 unsigned int view_lines = view->offset + view->height;
2593 unsigned int lines = view->lines
2594 ? MIN(view_lines, view->lines) * 100 / view->lines
2595 : 0;
2597 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2598 view->ops->type,
2599 view->lineno + 1,
2600 view->lines,
2601 lines);
2603 }
2605 if (view->pipe) {
2606 time_t secs = time(NULL) - view->start_time;
2608 /* Three git seconds are a long time ... */
2609 if (secs > 2)
2610 string_format_from(state, &statelen, " loading %lds", secs);
2611 }
2613 string_format_from(buf, &bufpos, "[%s]", view->name);
2614 if (*view->ref && bufpos < view->width) {
2615 size_t refsize = strlen(view->ref);
2616 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2618 if (minsize < view->width)
2619 refsize = view->width - minsize + 7;
2620 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2621 }
2623 if (statelen && bufpos < view->width) {
2624 string_format_from(buf, &bufpos, "%s", state);
2625 }
2627 if (view == display[current_view])
2628 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2629 else
2630 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2632 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2633 wclrtoeol(view->title);
2634 wnoutrefresh(view->title);
2635 }
2637 static int
2638 apply_step(double step, int value)
2639 {
2640 if (step >= 1)
2641 return (int) step;
2642 value *= step + 0.01;
2643 return value ? value : 1;
2644 }
2646 static void
2647 resize_display(void)
2648 {
2649 int offset, i;
2650 struct view *base = display[0];
2651 struct view *view = display[1] ? display[1] : display[0];
2653 /* Setup window dimensions */
2655 getmaxyx(stdscr, base->height, base->width);
2657 /* Make room for the status window. */
2658 base->height -= 1;
2660 if (view != base) {
2661 /* Horizontal split. */
2662 view->width = base->width;
2663 view->height = apply_step(opt_scale_split_view, base->height);
2664 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2665 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2666 base->height -= view->height;
2668 /* Make room for the title bar. */
2669 view->height -= 1;
2670 }
2672 /* Make room for the title bar. */
2673 base->height -= 1;
2675 offset = 0;
2677 foreach_displayed_view (view, i) {
2678 if (!view->win) {
2679 view->win = newwin(view->height, 0, offset, 0);
2680 if (!view->win)
2681 die("Failed to create %s view", view->name);
2683 scrollok(view->win, FALSE);
2685 view->title = newwin(1, 0, offset + view->height, 0);
2686 if (!view->title)
2687 die("Failed to create title window");
2689 } else {
2690 wresize(view->win, view->height, view->width);
2691 mvwin(view->win, offset, 0);
2692 mvwin(view->title, offset + view->height, 0);
2693 }
2695 offset += view->height + 1;
2696 }
2697 }
2699 static void
2700 redraw_display(bool clear)
2701 {
2702 struct view *view;
2703 int i;
2705 foreach_displayed_view (view, i) {
2706 if (clear)
2707 wclear(view->win);
2708 redraw_view(view);
2709 update_view_title(view);
2710 }
2711 }
2714 /*
2715 * Option management
2716 */
2718 static void
2719 toggle_enum_option_do(unsigned int *opt, const char *help,
2720 const struct enum_map *map, size_t size)
2721 {
2722 *opt = (*opt + 1) % size;
2723 redraw_display(FALSE);
2724 report("Displaying %s %s", enum_name(map[*opt]), help);
2725 }
2727 #define toggle_enum_option(opt, help, map) \
2728 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2730 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2731 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2733 static void
2734 toggle_view_option(bool *option, const char *help)
2735 {
2736 *option = !*option;
2737 redraw_display(FALSE);
2738 report("%sabling %s", *option ? "En" : "Dis", help);
2739 }
2741 static void
2742 open_option_menu(void)
2743 {
2744 const struct menu_item menu[] = {
2745 { '.', "line numbers", &opt_line_number },
2746 { 'D', "date display", &opt_date },
2747 { 'A', "author display", &opt_author },
2748 { 'g', "revision graph display", &opt_rev_graph },
2749 { 'F', "reference display", &opt_show_refs },
2750 { 0 }
2751 };
2752 int selected = 0;
2754 if (prompt_menu("Toggle option", menu, &selected)) {
2755 if (menu[selected].data == &opt_date)
2756 toggle_date();
2757 else if (menu[selected].data == &opt_author)
2758 toggle_author();
2759 else
2760 toggle_view_option(menu[selected].data, menu[selected].text);
2761 }
2762 }
2764 static void
2765 maximize_view(struct view *view)
2766 {
2767 memset(display, 0, sizeof(display));
2768 current_view = 0;
2769 display[current_view] = view;
2770 resize_display();
2771 redraw_display(FALSE);
2772 report("");
2773 }
2776 /*
2777 * Navigation
2778 */
2780 static bool
2781 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2782 {
2783 if (lineno >= view->lines)
2784 lineno = view->lines > 0 ? view->lines - 1 : 0;
2786 if (offset > lineno || offset + view->height <= lineno) {
2787 unsigned long half = view->height / 2;
2789 if (lineno > half)
2790 offset = lineno - half;
2791 else
2792 offset = 0;
2793 }
2795 if (offset != view->offset || lineno != view->lineno) {
2796 view->offset = offset;
2797 view->lineno = lineno;
2798 return TRUE;
2799 }
2801 return FALSE;
2802 }
2804 /* Scrolling backend */
2805 static void
2806 do_scroll_view(struct view *view, int lines)
2807 {
2808 bool redraw_current_line = FALSE;
2810 /* The rendering expects the new offset. */
2811 view->offset += lines;
2813 assert(0 <= view->offset && view->offset < view->lines);
2814 assert(lines);
2816 /* Move current line into the view. */
2817 if (view->lineno < view->offset) {
2818 view->lineno = view->offset;
2819 redraw_current_line = TRUE;
2820 } else if (view->lineno >= view->offset + view->height) {
2821 view->lineno = view->offset + view->height - 1;
2822 redraw_current_line = TRUE;
2823 }
2825 assert(view->offset <= view->lineno && view->lineno < view->lines);
2827 /* Redraw the whole screen if scrolling is pointless. */
2828 if (view->height < ABS(lines)) {
2829 redraw_view(view);
2831 } else {
2832 int line = lines > 0 ? view->height - lines : 0;
2833 int end = line + ABS(lines);
2835 scrollok(view->win, TRUE);
2836 wscrl(view->win, lines);
2837 scrollok(view->win, FALSE);
2839 while (line < end && draw_view_line(view, line))
2840 line++;
2842 if (redraw_current_line)
2843 draw_view_line(view, view->lineno - view->offset);
2844 wnoutrefresh(view->win);
2845 }
2847 view->has_scrolled = TRUE;
2848 report("");
2849 }
2851 /* Scroll frontend */
2852 static void
2853 scroll_view(struct view *view, enum request request)
2854 {
2855 int lines = 1;
2857 assert(view_is_displayed(view));
2859 switch (request) {
2860 case REQ_SCROLL_LEFT:
2861 if (view->yoffset == 0) {
2862 report("Cannot scroll beyond the first column");
2863 return;
2864 }
2865 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2866 view->yoffset = 0;
2867 else
2868 view->yoffset -= apply_step(opt_hscroll, view->width);
2869 redraw_view_from(view, 0);
2870 report("");
2871 return;
2872 case REQ_SCROLL_RIGHT:
2873 view->yoffset += apply_step(opt_hscroll, view->width);
2874 redraw_view(view);
2875 report("");
2876 return;
2877 case REQ_SCROLL_PAGE_DOWN:
2878 lines = view->height;
2879 case REQ_SCROLL_LINE_DOWN:
2880 if (view->offset + lines > view->lines)
2881 lines = view->lines - view->offset;
2883 if (lines == 0 || view->offset + view->height >= view->lines) {
2884 report("Cannot scroll beyond the last line");
2885 return;
2886 }
2887 break;
2889 case REQ_SCROLL_PAGE_UP:
2890 lines = view->height;
2891 case REQ_SCROLL_LINE_UP:
2892 if (lines > view->offset)
2893 lines = view->offset;
2895 if (lines == 0) {
2896 report("Cannot scroll beyond the first line");
2897 return;
2898 }
2900 lines = -lines;
2901 break;
2903 default:
2904 die("request %d not handled in switch", request);
2905 }
2907 do_scroll_view(view, lines);
2908 }
2910 /* Cursor moving */
2911 static void
2912 move_view(struct view *view, enum request request)
2913 {
2914 int scroll_steps = 0;
2915 int steps;
2917 switch (request) {
2918 case REQ_MOVE_FIRST_LINE:
2919 steps = -view->lineno;
2920 break;
2922 case REQ_MOVE_LAST_LINE:
2923 steps = view->lines - view->lineno - 1;
2924 break;
2926 case REQ_MOVE_PAGE_UP:
2927 steps = view->height > view->lineno
2928 ? -view->lineno : -view->height;
2929 break;
2931 case REQ_MOVE_PAGE_DOWN:
2932 steps = view->lineno + view->height >= view->lines
2933 ? view->lines - view->lineno - 1 : view->height;
2934 break;
2936 case REQ_MOVE_UP:
2937 steps = -1;
2938 break;
2940 case REQ_MOVE_DOWN:
2941 steps = 1;
2942 break;
2944 default:
2945 die("request %d not handled in switch", request);
2946 }
2948 if (steps <= 0 && view->lineno == 0) {
2949 report("Cannot move beyond the first line");
2950 return;
2952 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2953 report("Cannot move beyond the last line");
2954 return;
2955 }
2957 /* Move the current line */
2958 view->lineno += steps;
2959 assert(0 <= view->lineno && view->lineno < view->lines);
2961 /* Check whether the view needs to be scrolled */
2962 if (view->lineno < view->offset ||
2963 view->lineno >= view->offset + view->height) {
2964 scroll_steps = steps;
2965 if (steps < 0 && -steps > view->offset) {
2966 scroll_steps = -view->offset;
2968 } else if (steps > 0) {
2969 if (view->lineno == view->lines - 1 &&
2970 view->lines > view->height) {
2971 scroll_steps = view->lines - view->offset - 1;
2972 if (scroll_steps >= view->height)
2973 scroll_steps -= view->height - 1;
2974 }
2975 }
2976 }
2978 if (!view_is_displayed(view)) {
2979 view->offset += scroll_steps;
2980 assert(0 <= view->offset && view->offset < view->lines);
2981 view->ops->select(view, &view->line[view->lineno]);
2982 return;
2983 }
2985 /* Repaint the old "current" line if we be scrolling */
2986 if (ABS(steps) < view->height)
2987 draw_view_line(view, view->lineno - steps - view->offset);
2989 if (scroll_steps) {
2990 do_scroll_view(view, scroll_steps);
2991 return;
2992 }
2994 /* Draw the current line */
2995 draw_view_line(view, view->lineno - view->offset);
2997 wnoutrefresh(view->win);
2998 report("");
2999 }
3002 /*
3003 * Searching
3004 */
3006 static void search_view(struct view *view, enum request request);
3008 static bool
3009 grep_text(struct view *view, const char *text[])
3010 {
3011 regmatch_t pmatch;
3012 size_t i;
3014 for (i = 0; text[i]; i++)
3015 if (*text[i] &&
3016 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3017 return TRUE;
3018 return FALSE;
3019 }
3021 static void
3022 select_view_line(struct view *view, unsigned long lineno)
3023 {
3024 unsigned long old_lineno = view->lineno;
3025 unsigned long old_offset = view->offset;
3027 if (goto_view_line(view, view->offset, lineno)) {
3028 if (view_is_displayed(view)) {
3029 if (old_offset != view->offset) {
3030 redraw_view(view);
3031 } else {
3032 draw_view_line(view, old_lineno - view->offset);
3033 draw_view_line(view, view->lineno - view->offset);
3034 wnoutrefresh(view->win);
3035 }
3036 } else {
3037 view->ops->select(view, &view->line[view->lineno]);
3038 }
3039 }
3040 }
3042 static void
3043 find_next(struct view *view, enum request request)
3044 {
3045 unsigned long lineno = view->lineno;
3046 int direction;
3048 if (!*view->grep) {
3049 if (!*opt_search)
3050 report("No previous search");
3051 else
3052 search_view(view, request);
3053 return;
3054 }
3056 switch (request) {
3057 case REQ_SEARCH:
3058 case REQ_FIND_NEXT:
3059 direction = 1;
3060 break;
3062 case REQ_SEARCH_BACK:
3063 case REQ_FIND_PREV:
3064 direction = -1;
3065 break;
3067 default:
3068 return;
3069 }
3071 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3072 lineno += direction;
3074 /* Note, lineno is unsigned long so will wrap around in which case it
3075 * will become bigger than view->lines. */
3076 for (; lineno < view->lines; lineno += direction) {
3077 if (view->ops->grep(view, &view->line[lineno])) {
3078 select_view_line(view, lineno);
3079 report("Line %ld matches '%s'", lineno + 1, view->grep);
3080 return;
3081 }
3082 }
3084 report("No match found for '%s'", view->grep);
3085 }
3087 static void
3088 search_view(struct view *view, enum request request)
3089 {
3090 int regex_err;
3092 if (view->regex) {
3093 regfree(view->regex);
3094 *view->grep = 0;
3095 } else {
3096 view->regex = calloc(1, sizeof(*view->regex));
3097 if (!view->regex)
3098 return;
3099 }
3101 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3102 if (regex_err != 0) {
3103 char buf[SIZEOF_STR] = "unknown error";
3105 regerror(regex_err, view->regex, buf, sizeof(buf));
3106 report("Search failed: %s", buf);
3107 return;
3108 }
3110 string_copy(view->grep, opt_search);
3112 find_next(view, request);
3113 }
3115 /*
3116 * Incremental updating
3117 */
3119 static void
3120 reset_view(struct view *view)
3121 {
3122 int i;
3124 for (i = 0; i < view->lines; i++)
3125 free(view->line[i].data);
3126 free(view->line);
3128 view->p_offset = view->offset;
3129 view->p_yoffset = view->yoffset;
3130 view->p_lineno = view->lineno;
3132 view->line = NULL;
3133 view->offset = 0;
3134 view->yoffset = 0;
3135 view->lines = 0;
3136 view->lineno = 0;
3137 view->vid[0] = 0;
3138 view->update_secs = 0;
3139 }
3141 static const char *
3142 format_arg(const char *name)
3143 {
3144 static struct {
3145 const char *name;
3146 size_t namelen;
3147 const char *value;
3148 const char *value_if_empty;
3149 } vars[] = {
3150 #define FORMAT_VAR(name, value, value_if_empty) \
3151 { name, STRING_SIZE(name), value, value_if_empty }
3152 FORMAT_VAR("%(directory)", opt_path, ""),
3153 FORMAT_VAR("%(file)", opt_file, ""),
3154 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3155 FORMAT_VAR("%(head)", ref_head, ""),
3156 FORMAT_VAR("%(commit)", ref_commit, ""),
3157 FORMAT_VAR("%(blob)", ref_blob, ""),
3158 FORMAT_VAR("%(branch)", ref_branch, ""),
3159 };
3160 int i;
3162 for (i = 0; i < ARRAY_SIZE(vars); i++)
3163 if (!strncmp(name, vars[i].name, vars[i].namelen))
3164 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3166 report("Unknown replacement: `%s`", name);
3167 return NULL;
3168 }
3170 static bool
3171 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3172 {
3173 char buf[SIZEOF_STR];
3174 int argc;
3176 argv_free(*dst_argv);
3178 for (argc = 0; src_argv[argc]; argc++) {
3179 const char *arg = src_argv[argc];
3180 size_t bufpos = 0;
3182 if (!strcmp(arg, "%(file-args)")) {
3183 if (!argv_append_array(dst_argv, opt_file_args))
3184 break;
3185 continue;
3187 } else if (!strcmp(arg, "%(diff-args)")) {
3188 if (!argv_append_array(dst_argv, opt_diff_args))
3189 break;
3190 continue;
3192 } else if (!strcmp(arg, "%(rev-args)")) {
3193 if (!argv_append_array(dst_argv, opt_rev_args))
3194 break;
3195 continue;
3196 }
3198 while (arg) {
3199 char *next = strstr(arg, "%(");
3200 int len = next - arg;
3201 const char *value;
3203 if (!next || !replace) {
3204 len = strlen(arg);
3205 value = "";
3207 } else {
3208 value = format_arg(next);
3210 if (!value) {
3211 return FALSE;
3212 }
3213 }
3215 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3216 return FALSE;
3218 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3219 }
3221 if (!argv_append(dst_argv, buf))
3222 break;
3223 }
3225 return src_argv[argc] == NULL;
3226 }
3228 static bool
3229 restore_view_position(struct view *view)
3230 {
3231 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3232 return FALSE;
3234 /* Changing the view position cancels the restoring. */
3235 /* FIXME: Changing back to the first line is not detected. */
3236 if (view->offset != 0 || view->lineno != 0) {
3237 view->p_restore = FALSE;
3238 return FALSE;
3239 }
3241 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3242 view_is_displayed(view))
3243 werase(view->win);
3245 view->yoffset = view->p_yoffset;
3246 view->p_restore = FALSE;
3248 return TRUE;
3249 }
3251 static void
3252 end_update(struct view *view, bool force)
3253 {
3254 if (!view->pipe)
3255 return;
3256 while (!view->ops->read(view, NULL))
3257 if (!force)
3258 return;
3259 if (force)
3260 io_kill(view->pipe);
3261 io_done(view->pipe);
3262 view->pipe = NULL;
3263 }
3265 static void
3266 setup_update(struct view *view, const char *vid)
3267 {
3268 reset_view(view);
3269 string_copy_rev(view->vid, vid);
3270 view->pipe = &view->io;
3271 view->start_time = time(NULL);
3272 }
3274 static bool
3275 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3276 {
3277 view->dir = dir;
3278 return format_argv(&view->argv, argv, replace);
3279 }
3281 static bool
3282 prepare_update(struct view *view, const char *argv[], const char *dir)
3283 {
3284 if (view->pipe)
3285 end_update(view, TRUE);
3286 return prepare_io(view, dir, argv, FALSE);
3287 }
3289 static bool
3290 start_update(struct view *view, const char **argv, const char *dir)
3291 {
3292 if (view->pipe)
3293 io_done(view->pipe);
3294 return prepare_io(view, dir, argv, FALSE) &&
3295 io_run(&view->io, IO_RD, dir, view->argv);
3296 }
3298 static bool
3299 prepare_update_file(struct view *view, const char *name)
3300 {
3301 if (view->pipe)
3302 end_update(view, TRUE);
3303 argv_free(view->argv);
3304 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3305 }
3307 static bool
3308 begin_update(struct view *view, bool refresh)
3309 {
3310 if (view->pipe)
3311 end_update(view, TRUE);
3313 if (!refresh) {
3314 if (view->ops->prepare) {
3315 if (!view->ops->prepare(view))
3316 return FALSE;
3317 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3318 return FALSE;
3319 }
3321 /* Put the current ref_* value to the view title ref
3322 * member. This is needed by the blob view. Most other
3323 * views sets it automatically after loading because the
3324 * first line is a commit line. */
3325 string_copy_rev(view->ref, view->id);
3326 }
3328 if (view->argv && view->argv[0] &&
3329 !io_run(&view->io, IO_RD, view->dir, view->argv))
3330 return FALSE;
3332 setup_update(view, view->id);
3334 return TRUE;
3335 }
3337 static bool
3338 update_view(struct view *view)
3339 {
3340 char out_buffer[BUFSIZ * 2];
3341 char *line;
3342 /* Clear the view and redraw everything since the tree sorting
3343 * might have rearranged things. */
3344 bool redraw = view->lines == 0;
3345 bool can_read = TRUE;
3347 if (!view->pipe)
3348 return TRUE;
3350 if (!io_can_read(view->pipe)) {
3351 if (view->lines == 0 && view_is_displayed(view)) {
3352 time_t secs = time(NULL) - view->start_time;
3354 if (secs > 1 && secs > view->update_secs) {
3355 if (view->update_secs == 0)
3356 redraw_view(view);
3357 update_view_title(view);
3358 view->update_secs = secs;
3359 }
3360 }
3361 return TRUE;
3362 }
3364 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3365 if (opt_iconv_in != ICONV_NONE) {
3366 ICONV_CONST char *inbuf = line;
3367 size_t inlen = strlen(line) + 1;
3369 char *outbuf = out_buffer;
3370 size_t outlen = sizeof(out_buffer);
3372 size_t ret;
3374 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3375 if (ret != (size_t) -1)
3376 line = out_buffer;
3377 }
3379 if (!view->ops->read(view, line)) {
3380 report("Allocation failure");
3381 end_update(view, TRUE);
3382 return FALSE;
3383 }
3384 }
3386 {
3387 unsigned long lines = view->lines;
3388 int digits;
3390 for (digits = 0; lines; digits++)
3391 lines /= 10;
3393 /* Keep the displayed view in sync with line number scaling. */
3394 if (digits != view->digits) {
3395 view->digits = digits;
3396 if (opt_line_number || view->type == VIEW_BLAME)
3397 redraw = TRUE;
3398 }
3399 }
3401 if (io_error(view->pipe)) {
3402 report("Failed to read: %s", io_strerror(view->pipe));
3403 end_update(view, TRUE);
3405 } else if (io_eof(view->pipe)) {
3406 if (view_is_displayed(view))
3407 report("");
3408 end_update(view, FALSE);
3409 }
3411 if (restore_view_position(view))
3412 redraw = TRUE;
3414 if (!view_is_displayed(view))
3415 return TRUE;
3417 if (redraw)
3418 redraw_view_from(view, 0);
3419 else
3420 redraw_view_dirty(view);
3422 /* Update the title _after_ the redraw so that if the redraw picks up a
3423 * commit reference in view->ref it'll be available here. */
3424 update_view_title(view);
3425 return TRUE;
3426 }
3428 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3430 static struct line *
3431 add_line_data(struct view *view, void *data, enum line_type type)
3432 {
3433 struct line *line;
3435 if (!realloc_lines(&view->line, view->lines, 1))
3436 return NULL;
3438 line = &view->line[view->lines++];
3439 memset(line, 0, sizeof(*line));
3440 line->type = type;
3441 line->data = data;
3442 line->dirty = 1;
3444 return line;
3445 }
3447 static struct line *
3448 add_line_text(struct view *view, const char *text, enum line_type type)
3449 {
3450 char *data = text ? strdup(text) : NULL;
3452 return data ? add_line_data(view, data, type) : NULL;
3453 }
3455 static struct line *
3456 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3457 {
3458 char buf[SIZEOF_STR];
3459 va_list args;
3461 va_start(args, fmt);
3462 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3463 buf[0] = 0;
3464 va_end(args);
3466 return buf[0] ? add_line_text(view, buf, type) : NULL;
3467 }
3469 /*
3470 * View opening
3471 */
3473 enum open_flags {
3474 OPEN_DEFAULT = 0, /* Use default view switching. */
3475 OPEN_SPLIT = 1, /* Split current view. */
3476 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3477 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3478 OPEN_PREPARED = 32, /* Open already prepared command. */
3479 };
3481 static void
3482 open_view(struct view *prev, enum request request, enum open_flags flags)
3483 {
3484 bool split = !!(flags & OPEN_SPLIT);
3485 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3486 bool nomaximize = !!(flags & OPEN_REFRESH);
3487 struct view *view = VIEW(request);
3488 int nviews = displayed_views();
3489 struct view *base_view = display[0];
3491 if (view == prev && nviews == 1 && !reload) {
3492 report("Already in %s view", view->name);
3493 return;
3494 }
3496 if (view->git_dir && !opt_git_dir[0]) {
3497 report("The %s view is disabled in pager view", view->name);
3498 return;
3499 }
3501 if (split) {
3502 display[1] = view;
3503 current_view = 1;
3504 view->parent = prev;
3505 } else if (!nomaximize) {
3506 /* Maximize the current view. */
3507 memset(display, 0, sizeof(display));
3508 current_view = 0;
3509 display[current_view] = view;
3510 }
3512 /* No prev signals that this is the first loaded view. */
3513 if (prev && view != prev) {
3514 view->prev = prev;
3515 }
3517 /* Resize the view when switching between split- and full-screen,
3518 * or when switching between two different full-screen views. */
3519 if (nviews != displayed_views() ||
3520 (nviews == 1 && base_view != display[0]))
3521 resize_display();
3523 if (view->ops->open) {
3524 if (view->pipe)
3525 end_update(view, TRUE);
3526 if (!view->ops->open(view)) {
3527 report("Failed to load %s view", view->name);
3528 return;
3529 }
3530 restore_view_position(view);
3532 } else if ((reload || strcmp(view->vid, view->id)) &&
3533 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3534 report("Failed to load %s view", view->name);
3535 return;
3536 }
3538 if (split && prev->lineno - prev->offset >= prev->height) {
3539 /* Take the title line into account. */
3540 int lines = prev->lineno - prev->offset - prev->height + 1;
3542 /* Scroll the view that was split if the current line is
3543 * outside the new limited view. */
3544 do_scroll_view(prev, lines);
3545 }
3547 if (prev && view != prev && split && view_is_displayed(prev)) {
3548 /* "Blur" the previous view. */
3549 update_view_title(prev);
3550 }
3552 if (view->pipe && view->lines == 0) {
3553 /* Clear the old view and let the incremental updating refill
3554 * the screen. */
3555 werase(view->win);
3556 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3557 report("");
3558 } else if (view_is_displayed(view)) {
3559 redraw_view(view);
3560 report("");
3561 }
3562 }
3564 static void
3565 open_external_viewer(const char *argv[], const char *dir)
3566 {
3567 def_prog_mode(); /* save current tty modes */
3568 endwin(); /* restore original tty modes */
3569 io_run_fg(argv, dir);
3570 fprintf(stderr, "Press Enter to continue");
3571 getc(opt_tty);
3572 reset_prog_mode();
3573 redraw_display(TRUE);
3574 }
3576 static void
3577 open_mergetool(const char *file)
3578 {
3579 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3581 open_external_viewer(mergetool_argv, opt_cdup);
3582 }
3584 static void
3585 open_editor(const char *file)
3586 {
3587 const char *editor_argv[] = { "vi", file, NULL };
3588 const char *editor;
3590 editor = getenv("GIT_EDITOR");
3591 if (!editor && *opt_editor)
3592 editor = opt_editor;
3593 if (!editor)
3594 editor = getenv("VISUAL");
3595 if (!editor)
3596 editor = getenv("EDITOR");
3597 if (!editor)
3598 editor = "vi";
3600 editor_argv[0] = editor;
3601 open_external_viewer(editor_argv, opt_cdup);
3602 }
3604 static void
3605 open_run_request(enum request request)
3606 {
3607 struct run_request *req = get_run_request(request);
3608 const char **argv = NULL;
3610 if (!req) {
3611 report("Unknown run request");
3612 return;
3613 }
3615 if (format_argv(&argv, req->argv, TRUE))
3616 open_external_viewer(argv, NULL);
3617 if (argv)
3618 argv_free(argv);
3619 free(argv);
3620 }
3622 /*
3623 * User request switch noodle
3624 */
3626 static int
3627 view_driver(struct view *view, enum request request)
3628 {
3629 int i;
3631 if (request == REQ_NONE)
3632 return TRUE;
3634 if (request > REQ_NONE) {
3635 open_run_request(request);
3636 view_request(view, REQ_REFRESH);
3637 return TRUE;
3638 }
3640 request = view_request(view, request);
3641 if (request == REQ_NONE)
3642 return TRUE;
3644 switch (request) {
3645 case REQ_MOVE_UP:
3646 case REQ_MOVE_DOWN:
3647 case REQ_MOVE_PAGE_UP:
3648 case REQ_MOVE_PAGE_DOWN:
3649 case REQ_MOVE_FIRST_LINE:
3650 case REQ_MOVE_LAST_LINE:
3651 move_view(view, request);
3652 break;
3654 case REQ_SCROLL_LEFT:
3655 case REQ_SCROLL_RIGHT:
3656 case REQ_SCROLL_LINE_DOWN:
3657 case REQ_SCROLL_LINE_UP:
3658 case REQ_SCROLL_PAGE_DOWN:
3659 case REQ_SCROLL_PAGE_UP:
3660 scroll_view(view, request);
3661 break;
3663 case REQ_VIEW_BLAME:
3664 if (!opt_file[0]) {
3665 report("No file chosen, press %s to open tree view",
3666 get_key(view->keymap, REQ_VIEW_TREE));
3667 break;
3668 }
3669 open_view(view, request, OPEN_DEFAULT);
3670 break;
3672 case REQ_VIEW_BLOB:
3673 if (!ref_blob[0]) {
3674 report("No file chosen, press %s to open tree view",
3675 get_key(view->keymap, REQ_VIEW_TREE));
3676 break;
3677 }
3678 open_view(view, request, OPEN_DEFAULT);
3679 break;
3681 case REQ_VIEW_PAGER:
3682 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3683 report("No pager content, press %s to run command from prompt",
3684 get_key(view->keymap, REQ_PROMPT));
3685 break;
3686 }
3687 open_view(view, request, OPEN_DEFAULT);
3688 break;
3690 case REQ_VIEW_STAGE:
3691 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3692 report("No stage content, press %s to open the status view and choose file",
3693 get_key(view->keymap, REQ_VIEW_STATUS));
3694 break;
3695 }
3696 open_view(view, request, OPEN_DEFAULT);
3697 break;
3699 case REQ_VIEW_STATUS:
3700 if (opt_is_inside_work_tree == FALSE) {
3701 report("The status view requires a working tree");
3702 break;
3703 }
3704 open_view(view, request, OPEN_DEFAULT);
3705 break;
3707 case REQ_VIEW_MAIN:
3708 case REQ_VIEW_DIFF:
3709 case REQ_VIEW_LOG:
3710 case REQ_VIEW_TREE:
3711 case REQ_VIEW_HELP:
3712 case REQ_VIEW_BRANCH:
3713 open_view(view, request, OPEN_DEFAULT);
3714 break;
3716 case REQ_NEXT:
3717 case REQ_PREVIOUS:
3718 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3720 if (view->parent) {
3721 int line;
3723 view = view->parent;
3724 line = view->lineno;
3725 move_view(view, request);
3726 if (view_is_displayed(view))
3727 update_view_title(view);
3728 if (line != view->lineno)
3729 view_request(view, REQ_ENTER);
3730 } else {
3731 move_view(view, request);
3732 }
3733 break;
3735 case REQ_VIEW_NEXT:
3736 {
3737 int nviews = displayed_views();
3738 int next_view = (current_view + 1) % nviews;
3740 if (next_view == current_view) {
3741 report("Only one view is displayed");
3742 break;
3743 }
3745 current_view = next_view;
3746 /* Blur out the title of the previous view. */
3747 update_view_title(view);
3748 report("");
3749 break;
3750 }
3751 case REQ_REFRESH:
3752 report("Refreshing is not yet supported for the %s view", view->name);
3753 break;
3755 case REQ_MAXIMIZE:
3756 if (displayed_views() == 2)
3757 maximize_view(view);
3758 break;
3760 case REQ_OPTIONS:
3761 open_option_menu();
3762 break;
3764 case REQ_TOGGLE_LINENO:
3765 toggle_view_option(&opt_line_number, "line numbers");
3766 break;
3768 case REQ_TOGGLE_DATE:
3769 toggle_date();
3770 break;
3772 case REQ_TOGGLE_AUTHOR:
3773 toggle_author();
3774 break;
3776 case REQ_TOGGLE_REV_GRAPH:
3777 toggle_view_option(&opt_rev_graph, "revision graph display");
3778 break;
3780 case REQ_TOGGLE_REFS:
3781 toggle_view_option(&opt_show_refs, "reference display");
3782 break;
3784 case REQ_TOGGLE_SORT_FIELD:
3785 case REQ_TOGGLE_SORT_ORDER:
3786 report("Sorting is not yet supported for the %s view", view->name);
3787 break;
3789 case REQ_SEARCH:
3790 case REQ_SEARCH_BACK:
3791 search_view(view, request);
3792 break;
3794 case REQ_FIND_NEXT:
3795 case REQ_FIND_PREV:
3796 find_next(view, request);
3797 break;
3799 case REQ_STOP_LOADING:
3800 foreach_view(view, i) {
3801 if (view->pipe)
3802 report("Stopped loading the %s view", view->name),
3803 end_update(view, TRUE);
3804 }
3805 break;
3807 case REQ_SHOW_VERSION:
3808 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3809 return TRUE;
3811 case REQ_SCREEN_REDRAW:
3812 redraw_display(TRUE);
3813 break;
3815 case REQ_EDIT:
3816 report("Nothing to edit");
3817 break;
3819 case REQ_ENTER:
3820 report("Nothing to enter");
3821 break;
3823 case REQ_VIEW_CLOSE:
3824 /* XXX: Mark closed views by letting view->prev point to the
3825 * view itself. Parents to closed view should never be
3826 * followed. */
3827 if (view->prev && view->prev != view) {
3828 maximize_view(view->prev);
3829 view->prev = view;
3830 break;
3831 }
3832 /* Fall-through */
3833 case REQ_QUIT:
3834 return FALSE;
3836 default:
3837 report("Unknown key, press %s for help",
3838 get_key(view->keymap, REQ_VIEW_HELP));
3839 return TRUE;
3840 }
3842 return TRUE;
3843 }
3846 /*
3847 * View backend utilities
3848 */
3850 enum sort_field {
3851 ORDERBY_NAME,
3852 ORDERBY_DATE,
3853 ORDERBY_AUTHOR,
3854 };
3856 struct sort_state {
3857 const enum sort_field *fields;
3858 size_t size, current;
3859 bool reverse;
3860 };
3862 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3863 #define get_sort_field(state) ((state).fields[(state).current])
3864 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3866 static void
3867 sort_view(struct view *view, enum request request, struct sort_state *state,
3868 int (*compare)(const void *, const void *))
3869 {
3870 switch (request) {
3871 case REQ_TOGGLE_SORT_FIELD:
3872 state->current = (state->current + 1) % state->size;
3873 break;
3875 case REQ_TOGGLE_SORT_ORDER:
3876 state->reverse = !state->reverse;
3877 break;
3878 default:
3879 die("Not a sort request");
3880 }
3882 qsort(view->line, view->lines, sizeof(*view->line), compare);
3883 redraw_view(view);
3884 }
3886 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3888 /* Small author cache to reduce memory consumption. It uses binary
3889 * search to lookup or find place to position new entries. No entries
3890 * are ever freed. */
3891 static const char *
3892 get_author(const char *name)
3893 {
3894 static const char **authors;
3895 static size_t authors_size;
3896 int from = 0, to = authors_size - 1;
3898 while (from <= to) {
3899 size_t pos = (to + from) / 2;
3900 int cmp = strcmp(name, authors[pos]);
3902 if (!cmp)
3903 return authors[pos];
3905 if (cmp < 0)
3906 to = pos - 1;
3907 else
3908 from = pos + 1;
3909 }
3911 if (!realloc_authors(&authors, authors_size, 1))
3912 return NULL;
3913 name = strdup(name);
3914 if (!name)
3915 return NULL;
3917 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3918 authors[from] = name;
3919 authors_size++;
3921 return name;
3922 }
3924 static void
3925 parse_timesec(struct time *time, const char *sec)
3926 {
3927 time->sec = (time_t) atol(sec);
3928 }
3930 static void
3931 parse_timezone(struct time *time, const char *zone)
3932 {
3933 long tz;
3935 tz = ('0' - zone[1]) * 60 * 60 * 10;
3936 tz += ('0' - zone[2]) * 60 * 60;
3937 tz += ('0' - zone[3]) * 60 * 10;
3938 tz += ('0' - zone[4]) * 60;
3940 if (zone[0] == '-')
3941 tz = -tz;
3943 time->tz = tz;
3944 time->sec -= tz;
3945 }
3947 /* Parse author lines where the name may be empty:
3948 * author <email@address.tld> 1138474660 +0100
3949 */
3950 static void
3951 parse_author_line(char *ident, const char **author, struct time *time)
3952 {
3953 char *nameend = strchr(ident, '<');
3954 char *emailend = strchr(ident, '>');
3956 if (nameend && emailend)
3957 *nameend = *emailend = 0;
3958 ident = chomp_string(ident);
3959 if (!*ident) {
3960 if (nameend)
3961 ident = chomp_string(nameend + 1);
3962 if (!*ident)
3963 ident = "Unknown";
3964 }
3966 *author = get_author(ident);
3968 /* Parse epoch and timezone */
3969 if (emailend && emailend[1] == ' ') {
3970 char *secs = emailend + 2;
3971 char *zone = strchr(secs, ' ');
3973 parse_timesec(time, secs);
3975 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3976 parse_timezone(time, zone + 1);
3977 }
3978 }
3980 static bool
3981 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3982 {
3983 char rev[SIZEOF_REV];
3984 const char *revlist_argv[] = {
3985 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3986 };
3987 struct menu_item *items;
3988 char text[SIZEOF_STR];
3989 bool ok = TRUE;
3990 int i;
3992 items = calloc(*parents + 1, sizeof(*items));
3993 if (!items)
3994 return FALSE;
3996 for (i = 0; i < *parents; i++) {
3997 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3998 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3999 !(items[i].text = strdup(text))) {
4000 ok = FALSE;
4001 break;
4002 }
4003 }
4005 if (ok) {
4006 *parents = 0;
4007 ok = prompt_menu("Select parent", items, parents);
4008 }
4009 for (i = 0; items[i].text; i++)
4010 free((char *) items[i].text);
4011 free(items);
4012 return ok;
4013 }
4015 static bool
4016 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4017 {
4018 char buf[SIZEOF_STR * 4];
4019 const char *revlist_argv[] = {
4020 "git", "log", "--no-color", "-1",
4021 "--pretty=format:%P", id, "--", path, NULL
4022 };
4023 int parents;
4025 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4026 (parents = strlen(buf) / 40) < 0) {
4027 report("Failed to get parent information");
4028 return FALSE;
4030 } else if (parents == 0) {
4031 if (path)
4032 report("Path '%s' does not exist in the parent", path);
4033 else
4034 report("The selected commit has no parents");
4035 return FALSE;
4036 }
4038 if (parents == 1)
4039 parents = 0;
4040 else if (!open_commit_parent_menu(buf, &parents))
4041 return FALSE;
4043 string_copy_rev(rev, &buf[41 * parents]);
4044 return TRUE;
4045 }
4047 /*
4048 * Pager backend
4049 */
4051 static bool
4052 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4053 {
4054 char text[SIZEOF_STR];
4056 if (opt_line_number && draw_lineno(view, lineno))
4057 return TRUE;
4059 string_expand(text, sizeof(text), line->data, opt_tab_size);
4060 draw_text(view, line->type, text, TRUE);
4061 return TRUE;
4062 }
4064 static bool
4065 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4066 {
4067 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4068 char ref[SIZEOF_STR];
4070 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4071 return TRUE;
4073 /* This is the only fatal call, since it can "corrupt" the buffer. */
4074 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4075 return FALSE;
4077 return TRUE;
4078 }
4080 static void
4081 add_pager_refs(struct view *view, struct line *line)
4082 {
4083 char buf[SIZEOF_STR];
4084 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4085 struct ref_list *list;
4086 size_t bufpos = 0, i;
4087 const char *sep = "Refs: ";
4088 bool is_tag = FALSE;
4090 assert(line->type == LINE_COMMIT);
4092 list = get_ref_list(commit_id);
4093 if (!list) {
4094 if (view->type == VIEW_DIFF)
4095 goto try_add_describe_ref;
4096 return;
4097 }
4099 for (i = 0; i < list->size; i++) {
4100 struct ref *ref = list->refs[i];
4101 const char *fmt = ref->tag ? "%s[%s]" :
4102 ref->remote ? "%s<%s>" : "%s%s";
4104 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4105 return;
4106 sep = ", ";
4107 if (ref->tag)
4108 is_tag = TRUE;
4109 }
4111 if (!is_tag && view->type == VIEW_DIFF) {
4112 try_add_describe_ref:
4113 /* Add <tag>-g<commit_id> "fake" reference. */
4114 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4115 return;
4116 }
4118 if (bufpos == 0)
4119 return;
4121 add_line_text(view, buf, LINE_PP_REFS);
4122 }
4124 static bool
4125 pager_read(struct view *view, char *data)
4126 {
4127 struct line *line;
4129 if (!data)
4130 return TRUE;
4132 line = add_line_text(view, data, get_line_type(data));
4133 if (!line)
4134 return FALSE;
4136 if (line->type == LINE_COMMIT &&
4137 (view->type == VIEW_DIFF ||
4138 view->type == VIEW_LOG))
4139 add_pager_refs(view, line);
4141 return TRUE;
4142 }
4144 static enum request
4145 pager_request(struct view *view, enum request request, struct line *line)
4146 {
4147 int split = 0;
4149 if (request != REQ_ENTER)
4150 return request;
4152 if (line->type == LINE_COMMIT &&
4153 (view->type == VIEW_LOG ||
4154 view->type == VIEW_PAGER)) {
4155 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4156 split = 1;
4157 }
4159 /* Always scroll the view even if it was split. That way
4160 * you can use Enter to scroll through the log view and
4161 * split open each commit diff. */
4162 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4164 /* FIXME: A minor workaround. Scrolling the view will call report("")
4165 * but if we are scrolling a non-current view this won't properly
4166 * update the view title. */
4167 if (split)
4168 update_view_title(view);
4170 return REQ_NONE;
4171 }
4173 static bool
4174 pager_grep(struct view *view, struct line *line)
4175 {
4176 const char *text[] = { line->data, NULL };
4178 return grep_text(view, text);
4179 }
4181 static void
4182 pager_select(struct view *view, struct line *line)
4183 {
4184 if (line->type == LINE_COMMIT) {
4185 char *text = (char *)line->data + STRING_SIZE("commit ");
4187 if (view->type != VIEW_PAGER)
4188 string_copy_rev(view->ref, text);
4189 string_copy_rev(ref_commit, text);
4190 }
4191 }
4193 static struct view_ops pager_ops = {
4194 "line",
4195 NULL,
4196 NULL,
4197 pager_read,
4198 pager_draw,
4199 pager_request,
4200 pager_grep,
4201 pager_select,
4202 };
4204 static const char *log_argv[SIZEOF_ARG] = {
4205 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4206 };
4208 static enum request
4209 log_request(struct view *view, enum request request, struct line *line)
4210 {
4211 switch (request) {
4212 case REQ_REFRESH:
4213 load_refs();
4214 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4215 return REQ_NONE;
4216 default:
4217 return pager_request(view, request, line);
4218 }
4219 }
4221 static struct view_ops log_ops = {
4222 "line",
4223 log_argv,
4224 NULL,
4225 pager_read,
4226 pager_draw,
4227 log_request,
4228 pager_grep,
4229 pager_select,
4230 };
4232 static const char *diff_argv[SIZEOF_ARG] = {
4233 "git", "show", "--pretty=fuller", "--no-color", "--root",
4234 "--patch-with-stat", "--find-copies-harder", "-C",
4235 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4236 };
4238 static struct view_ops diff_ops = {
4239 "line",
4240 diff_argv,
4241 NULL,
4242 pager_read,
4243 pager_draw,
4244 pager_request,
4245 pager_grep,
4246 pager_select,
4247 };
4249 /*
4250 * Help backend
4251 */
4253 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4255 static bool
4256 help_open_keymap_title(struct view *view, enum keymap keymap)
4257 {
4258 struct line *line;
4260 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4261 help_keymap_hidden[keymap] ? '+' : '-',
4262 enum_name(keymap_table[keymap]));
4263 if (line)
4264 line->other = keymap;
4266 return help_keymap_hidden[keymap];
4267 }
4269 static void
4270 help_open_keymap(struct view *view, enum keymap keymap)
4271 {
4272 const char *group = NULL;
4273 char buf[SIZEOF_STR];
4274 size_t bufpos;
4275 bool add_title = TRUE;
4276 int i;
4278 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4279 const char *key = NULL;
4281 if (req_info[i].request == REQ_NONE)
4282 continue;
4284 if (!req_info[i].request) {
4285 group = req_info[i].help;
4286 continue;
4287 }
4289 key = get_keys(keymap, req_info[i].request, TRUE);
4290 if (!key || !*key)
4291 continue;
4293 if (add_title && help_open_keymap_title(view, keymap))
4294 return;
4295 add_title = FALSE;
4297 if (group) {
4298 add_line_text(view, group, LINE_HELP_GROUP);
4299 group = NULL;
4300 }
4302 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4303 enum_name(req_info[i]), req_info[i].help);
4304 }
4306 group = "External commands:";
4308 for (i = 0; i < run_requests; i++) {
4309 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4310 const char *key;
4311 int argc;
4313 if (!req || req->keymap != keymap)
4314 continue;
4316 key = get_key_name(req->key);
4317 if (!*key)
4318 key = "(no key defined)";
4320 if (add_title && help_open_keymap_title(view, keymap))
4321 return;
4322 if (group) {
4323 add_line_text(view, group, LINE_HELP_GROUP);
4324 group = NULL;
4325 }
4327 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4328 if (!string_format_from(buf, &bufpos, "%s%s",
4329 argc ? " " : "", req->argv[argc]))
4330 return;
4332 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4333 }
4334 }
4336 static bool
4337 help_open(struct view *view)
4338 {
4339 enum keymap keymap;
4341 reset_view(view);
4342 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4343 add_line_text(view, "", LINE_DEFAULT);
4345 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4346 help_open_keymap(view, keymap);
4348 return TRUE;
4349 }
4351 static enum request
4352 help_request(struct view *view, enum request request, struct line *line)
4353 {
4354 switch (request) {
4355 case REQ_ENTER:
4356 if (line->type == LINE_HELP_KEYMAP) {
4357 help_keymap_hidden[line->other] =
4358 !help_keymap_hidden[line->other];
4359 view->p_restore = TRUE;
4360 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4361 }
4363 return REQ_NONE;
4364 default:
4365 return pager_request(view, request, line);
4366 }
4367 }
4369 static struct view_ops help_ops = {
4370 "line",
4371 NULL,
4372 help_open,
4373 NULL,
4374 pager_draw,
4375 help_request,
4376 pager_grep,
4377 pager_select,
4378 };
4381 /*
4382 * Tree backend
4383 */
4385 struct tree_stack_entry {
4386 struct tree_stack_entry *prev; /* Entry below this in the stack */
4387 unsigned long lineno; /* Line number to restore */
4388 char *name; /* Position of name in opt_path */
4389 };
4391 /* The top of the path stack. */
4392 static struct tree_stack_entry *tree_stack = NULL;
4393 unsigned long tree_lineno = 0;
4395 static void
4396 pop_tree_stack_entry(void)
4397 {
4398 struct tree_stack_entry *entry = tree_stack;
4400 tree_lineno = entry->lineno;
4401 entry->name[0] = 0;
4402 tree_stack = entry->prev;
4403 free(entry);
4404 }
4406 static void
4407 push_tree_stack_entry(const char *name, unsigned long lineno)
4408 {
4409 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4410 size_t pathlen = strlen(opt_path);
4412 if (!entry)
4413 return;
4415 entry->prev = tree_stack;
4416 entry->name = opt_path + pathlen;
4417 tree_stack = entry;
4419 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4420 pop_tree_stack_entry();
4421 return;
4422 }
4424 /* Move the current line to the first tree entry. */
4425 tree_lineno = 1;
4426 entry->lineno = lineno;
4427 }
4429 /* Parse output from git-ls-tree(1):
4430 *
4431 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4432 */
4434 #define SIZEOF_TREE_ATTR \
4435 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4437 #define SIZEOF_TREE_MODE \
4438 STRING_SIZE("100644 ")
4440 #define TREE_ID_OFFSET \
4441 STRING_SIZE("100644 blob ")
4443 struct tree_entry {
4444 char id[SIZEOF_REV];
4445 mode_t mode;
4446 struct time time; /* Date from the author ident. */
4447 const char *author; /* Author of the commit. */
4448 char name[1];
4449 };
4451 static const char *
4452 tree_path(const struct line *line)
4453 {
4454 return ((struct tree_entry *) line->data)->name;
4455 }
4457 static int
4458 tree_compare_entry(const struct line *line1, const struct line *line2)
4459 {
4460 if (line1->type != line2->type)
4461 return line1->type == LINE_TREE_DIR ? -1 : 1;
4462 return strcmp(tree_path(line1), tree_path(line2));
4463 }
4465 static const enum sort_field tree_sort_fields[] = {
4466 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4467 };
4468 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4470 static int
4471 tree_compare(const void *l1, const void *l2)
4472 {
4473 const struct line *line1 = (const struct line *) l1;
4474 const struct line *line2 = (const struct line *) l2;
4475 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4476 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4478 if (line1->type == LINE_TREE_HEAD)
4479 return -1;
4480 if (line2->type == LINE_TREE_HEAD)
4481 return 1;
4483 switch (get_sort_field(tree_sort_state)) {
4484 case ORDERBY_DATE:
4485 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4487 case ORDERBY_AUTHOR:
4488 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4490 case ORDERBY_NAME:
4491 default:
4492 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4493 }
4494 }
4497 static struct line *
4498 tree_entry(struct view *view, enum line_type type, const char *path,
4499 const char *mode, const char *id)
4500 {
4501 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4502 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4504 if (!entry || !line) {
4505 free(entry);
4506 return NULL;
4507 }
4509 strncpy(entry->name, path, strlen(path));
4510 if (mode)
4511 entry->mode = strtoul(mode, NULL, 8);
4512 if (id)
4513 string_copy_rev(entry->id, id);
4515 return line;
4516 }
4518 static bool
4519 tree_read_date(struct view *view, char *text, bool *read_date)
4520 {
4521 static const char *author_name;
4522 static struct time author_time;
4524 if (!text && *read_date) {
4525 *read_date = FALSE;
4526 return TRUE;
4528 } else if (!text) {
4529 char *path = *opt_path ? opt_path : ".";
4530 /* Find next entry to process */
4531 const char *log_file[] = {
4532 "git", "log", "--no-color", "--pretty=raw",
4533 "--cc", "--raw", view->id, "--", path, NULL
4534 };
4536 if (!view->lines) {
4537 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4538 report("Tree is empty");
4539 return TRUE;
4540 }
4542 if (!start_update(view, log_file, opt_cdup)) {
4543 report("Failed to load tree data");
4544 return TRUE;
4545 }
4547 *read_date = TRUE;
4548 return FALSE;
4550 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4551 parse_author_line(text + STRING_SIZE("author "),
4552 &author_name, &author_time);
4554 } else if (*text == ':') {
4555 char *pos;
4556 size_t annotated = 1;
4557 size_t i;
4559 pos = strchr(text, '\t');
4560 if (!pos)
4561 return TRUE;
4562 text = pos + 1;
4563 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4564 text += strlen(opt_path);
4565 pos = strchr(text, '/');
4566 if (pos)
4567 *pos = 0;
4569 for (i = 1; i < view->lines; i++) {
4570 struct line *line = &view->line[i];
4571 struct tree_entry *entry = line->data;
4573 annotated += !!entry->author;
4574 if (entry->author || strcmp(entry->name, text))
4575 continue;
4577 entry->author = author_name;
4578 entry->time = author_time;
4579 line->dirty = 1;
4580 break;
4581 }
4583 if (annotated == view->lines)
4584 io_kill(view->pipe);
4585 }
4586 return TRUE;
4587 }
4589 static bool
4590 tree_read(struct view *view, char *text)
4591 {
4592 static bool read_date = FALSE;
4593 struct tree_entry *data;
4594 struct line *entry, *line;
4595 enum line_type type;
4596 size_t textlen = text ? strlen(text) : 0;
4597 char *path = text + SIZEOF_TREE_ATTR;
4599 if (read_date || !text)
4600 return tree_read_date(view, text, &read_date);
4602 if (textlen <= SIZEOF_TREE_ATTR)
4603 return FALSE;
4604 if (view->lines == 0 &&
4605 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4606 return FALSE;
4608 /* Strip the path part ... */
4609 if (*opt_path) {
4610 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4611 size_t striplen = strlen(opt_path);
4613 if (pathlen > striplen)
4614 memmove(path, path + striplen,
4615 pathlen - striplen + 1);
4617 /* Insert "link" to parent directory. */
4618 if (view->lines == 1 &&
4619 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4620 return FALSE;
4621 }
4623 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4624 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4625 if (!entry)
4626 return FALSE;
4627 data = entry->data;
4629 /* Skip "Directory ..." and ".." line. */
4630 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4631 if (tree_compare_entry(line, entry) <= 0)
4632 continue;
4634 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4636 line->data = data;
4637 line->type = type;
4638 for (; line <= entry; line++)
4639 line->dirty = line->cleareol = 1;
4640 return TRUE;
4641 }
4643 if (tree_lineno > view->lineno) {
4644 view->lineno = tree_lineno;
4645 tree_lineno = 0;
4646 }
4648 return TRUE;
4649 }
4651 static bool
4652 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4653 {
4654 struct tree_entry *entry = line->data;
4656 if (line->type == LINE_TREE_HEAD) {
4657 if (draw_text(view, line->type, "Directory path /", TRUE))
4658 return TRUE;
4659 } else {
4660 if (draw_mode(view, entry->mode))
4661 return TRUE;
4663 if (opt_author && draw_author(view, entry->author))
4664 return TRUE;
4666 if (opt_date && draw_date(view, &entry->time))
4667 return TRUE;
4668 }
4669 if (draw_text(view, line->type, entry->name, TRUE))
4670 return TRUE;
4671 return TRUE;
4672 }
4674 static void
4675 open_blob_editor(const char *id)
4676 {
4677 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4678 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4679 int fd = mkstemp(file);
4681 if (fd == -1)
4682 report("Failed to create temporary file");
4683 else if (!io_run_append(blob_argv, fd))
4684 report("Failed to save blob data to file");
4685 else
4686 open_editor(file);
4687 if (fd != -1)
4688 unlink(file);
4689 }
4691 static enum request
4692 tree_request(struct view *view, enum request request, struct line *line)
4693 {
4694 enum open_flags flags;
4695 struct tree_entry *entry = line->data;
4697 switch (request) {
4698 case REQ_VIEW_BLAME:
4699 if (line->type != LINE_TREE_FILE) {
4700 report("Blame only supported for files");
4701 return REQ_NONE;
4702 }
4704 string_copy(opt_ref, view->vid);
4705 return request;
4707 case REQ_EDIT:
4708 if (line->type != LINE_TREE_FILE) {
4709 report("Edit only supported for files");
4710 } else if (!is_head_commit(view->vid)) {
4711 open_blob_editor(entry->id);
4712 } else {
4713 open_editor(opt_file);
4714 }
4715 return REQ_NONE;
4717 case REQ_TOGGLE_SORT_FIELD:
4718 case REQ_TOGGLE_SORT_ORDER:
4719 sort_view(view, request, &tree_sort_state, tree_compare);
4720 return REQ_NONE;
4722 case REQ_PARENT:
4723 if (!*opt_path) {
4724 /* quit view if at top of tree */
4725 return REQ_VIEW_CLOSE;
4726 }
4727 /* fake 'cd ..' */
4728 line = &view->line[1];
4729 break;
4731 case REQ_ENTER:
4732 break;
4734 default:
4735 return request;
4736 }
4738 /* Cleanup the stack if the tree view is at a different tree. */
4739 while (!*opt_path && tree_stack)
4740 pop_tree_stack_entry();
4742 switch (line->type) {
4743 case LINE_TREE_DIR:
4744 /* Depending on whether it is a subdirectory or parent link
4745 * mangle the path buffer. */
4746 if (line == &view->line[1] && *opt_path) {
4747 pop_tree_stack_entry();
4749 } else {
4750 const char *basename = tree_path(line);
4752 push_tree_stack_entry(basename, view->lineno);
4753 }
4755 /* Trees and subtrees share the same ID, so they are not not
4756 * unique like blobs. */
4757 flags = OPEN_RELOAD;
4758 request = REQ_VIEW_TREE;
4759 break;
4761 case LINE_TREE_FILE:
4762 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4763 request = REQ_VIEW_BLOB;
4764 break;
4766 default:
4767 return REQ_NONE;
4768 }
4770 open_view(view, request, flags);
4771 if (request == REQ_VIEW_TREE)
4772 view->lineno = tree_lineno;
4774 return REQ_NONE;
4775 }
4777 static bool
4778 tree_grep(struct view *view, struct line *line)
4779 {
4780 struct tree_entry *entry = line->data;
4781 const char *text[] = {
4782 entry->name,
4783 opt_author ? entry->author : "",
4784 mkdate(&entry->time, opt_date),
4785 NULL
4786 };
4788 return grep_text(view, text);
4789 }
4791 static void
4792 tree_select(struct view *view, struct line *line)
4793 {
4794 struct tree_entry *entry = line->data;
4796 if (line->type == LINE_TREE_FILE) {
4797 string_copy_rev(ref_blob, entry->id);
4798 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4800 } else if (line->type != LINE_TREE_DIR) {
4801 return;
4802 }
4804 string_copy_rev(view->ref, entry->id);
4805 }
4807 static bool
4808 tree_prepare(struct view *view)
4809 {
4810 if (view->lines == 0 && opt_prefix[0]) {
4811 char *pos = opt_prefix;
4813 while (pos && *pos) {
4814 char *end = strchr(pos, '/');
4816 if (end)
4817 *end = 0;
4818 push_tree_stack_entry(pos, 0);
4819 pos = end;
4820 if (end) {
4821 *end = '/';
4822 pos++;
4823 }
4824 }
4826 } else if (strcmp(view->vid, view->id)) {
4827 opt_path[0] = 0;
4828 }
4830 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4831 }
4833 static const char *tree_argv[SIZEOF_ARG] = {
4834 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4835 };
4837 static struct view_ops tree_ops = {
4838 "file",
4839 tree_argv,
4840 NULL,
4841 tree_read,
4842 tree_draw,
4843 tree_request,
4844 tree_grep,
4845 tree_select,
4846 tree_prepare,
4847 };
4849 static bool
4850 blob_read(struct view *view, char *line)
4851 {
4852 if (!line)
4853 return TRUE;
4854 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4855 }
4857 static enum request
4858 blob_request(struct view *view, enum request request, struct line *line)
4859 {
4860 switch (request) {
4861 case REQ_EDIT:
4862 open_blob_editor(view->vid);
4863 return REQ_NONE;
4864 default:
4865 return pager_request(view, request, line);
4866 }
4867 }
4869 static const char *blob_argv[SIZEOF_ARG] = {
4870 "git", "cat-file", "blob", "%(blob)", NULL
4871 };
4873 static struct view_ops blob_ops = {
4874 "line",
4875 blob_argv,
4876 NULL,
4877 blob_read,
4878 pager_draw,
4879 blob_request,
4880 pager_grep,
4881 pager_select,
4882 };
4884 /*
4885 * Blame backend
4886 *
4887 * Loading the blame view is a two phase job:
4888 *
4889 * 1. File content is read either using opt_file from the
4890 * filesystem or using git-cat-file.
4891 * 2. Then blame information is incrementally added by
4892 * reading output from git-blame.
4893 */
4895 struct blame_commit {
4896 char id[SIZEOF_REV]; /* SHA1 ID. */
4897 char title[128]; /* First line of the commit message. */
4898 const char *author; /* Author of the commit. */
4899 struct time time; /* Date from the author ident. */
4900 char filename[128]; /* Name of file. */
4901 bool has_previous; /* Was a "previous" line detected. */
4902 };
4904 struct blame {
4905 struct blame_commit *commit;
4906 unsigned long lineno;
4907 char text[1];
4908 };
4910 static bool
4911 blame_open(struct view *view)
4912 {
4913 char path[SIZEOF_STR];
4914 size_t i;
4916 if (!view->prev && *opt_prefix) {
4917 string_copy(path, opt_file);
4918 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4919 return FALSE;
4920 }
4922 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4923 const char *blame_cat_file_argv[] = {
4924 "git", "cat-file", "blob", path, NULL
4925 };
4927 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4928 !start_update(view, blame_cat_file_argv, opt_cdup))
4929 return FALSE;
4930 }
4932 /* First pass: remove multiple references to the same commit. */
4933 for (i = 0; i < view->lines; i++) {
4934 struct blame *blame = view->line[i].data;
4936 if (blame->commit && blame->commit->id[0])
4937 blame->commit->id[0] = 0;
4938 else
4939 blame->commit = NULL;
4940 }
4942 /* Second pass: free existing references. */
4943 for (i = 0; i < view->lines; i++) {
4944 struct blame *blame = view->line[i].data;
4946 if (blame->commit)
4947 free(blame->commit);
4948 }
4950 setup_update(view, opt_file);
4951 string_format(view->ref, "%s ...", opt_file);
4953 return TRUE;
4954 }
4956 static struct blame_commit *
4957 get_blame_commit(struct view *view, const char *id)
4958 {
4959 size_t i;
4961 for (i = 0; i < view->lines; i++) {
4962 struct blame *blame = view->line[i].data;
4964 if (!blame->commit)
4965 continue;
4967 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4968 return blame->commit;
4969 }
4971 {
4972 struct blame_commit *commit = calloc(1, sizeof(*commit));
4974 if (commit)
4975 string_ncopy(commit->id, id, SIZEOF_REV);
4976 return commit;
4977 }
4978 }
4980 static bool
4981 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4982 {
4983 const char *pos = *posref;
4985 *posref = NULL;
4986 pos = strchr(pos + 1, ' ');
4987 if (!pos || !isdigit(pos[1]))
4988 return FALSE;
4989 *number = atoi(pos + 1);
4990 if (*number < min || *number > max)
4991 return FALSE;
4993 *posref = pos;
4994 return TRUE;
4995 }
4997 static struct blame_commit *
4998 parse_blame_commit(struct view *view, const char *text, int *blamed)
4999 {
5000 struct blame_commit *commit;
5001 struct blame *blame;
5002 const char *pos = text + SIZEOF_REV - 2;
5003 size_t orig_lineno = 0;
5004 size_t lineno;
5005 size_t group;
5007 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5008 return NULL;
5010 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5011 !parse_number(&pos, &lineno, 1, view->lines) ||
5012 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5013 return NULL;
5015 commit = get_blame_commit(view, text);
5016 if (!commit)
5017 return NULL;
5019 *blamed += group;
5020 while (group--) {
5021 struct line *line = &view->line[lineno + group - 1];
5023 blame = line->data;
5024 blame->commit = commit;
5025 blame->lineno = orig_lineno + group - 1;
5026 line->dirty = 1;
5027 }
5029 return commit;
5030 }
5032 static bool
5033 blame_read_file(struct view *view, const char *line, bool *read_file)
5034 {
5035 if (!line) {
5036 const char *blame_argv[] = {
5037 "git", "blame", "--incremental",
5038 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5039 };
5041 if (view->lines == 0 && !view->prev)
5042 die("No blame exist for %s", view->vid);
5044 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5045 report("Failed to load blame data");
5046 return TRUE;
5047 }
5049 *read_file = FALSE;
5050 return FALSE;
5052 } else {
5053 size_t linelen = strlen(line);
5054 struct blame *blame = malloc(sizeof(*blame) + linelen);
5056 if (!blame)
5057 return FALSE;
5059 blame->commit = NULL;
5060 strncpy(blame->text, line, linelen);
5061 blame->text[linelen] = 0;
5062 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5063 }
5064 }
5066 static bool
5067 match_blame_header(const char *name, char **line)
5068 {
5069 size_t namelen = strlen(name);
5070 bool matched = !strncmp(name, *line, namelen);
5072 if (matched)
5073 *line += namelen;
5075 return matched;
5076 }
5078 static bool
5079 blame_read(struct view *view, char *line)
5080 {
5081 static struct blame_commit *commit = NULL;
5082 static int blamed = 0;
5083 static bool read_file = TRUE;
5085 if (read_file)
5086 return blame_read_file(view, line, &read_file);
5088 if (!line) {
5089 /* Reset all! */
5090 commit = NULL;
5091 blamed = 0;
5092 read_file = TRUE;
5093 string_format(view->ref, "%s", view->vid);
5094 if (view_is_displayed(view)) {
5095 update_view_title(view);
5096 redraw_view_from(view, 0);
5097 }
5098 return TRUE;
5099 }
5101 if (!commit) {
5102 commit = parse_blame_commit(view, line, &blamed);
5103 string_format(view->ref, "%s %2d%%", view->vid,
5104 view->lines ? blamed * 100 / view->lines : 0);
5106 } else if (match_blame_header("author ", &line)) {
5107 commit->author = get_author(line);
5109 } else if (match_blame_header("author-time ", &line)) {
5110 parse_timesec(&commit->time, line);
5112 } else if (match_blame_header("author-tz ", &line)) {
5113 parse_timezone(&commit->time, line);
5115 } else if (match_blame_header("summary ", &line)) {
5116 string_ncopy(commit->title, line, strlen(line));
5118 } else if (match_blame_header("previous ", &line)) {
5119 commit->has_previous = TRUE;
5121 } else if (match_blame_header("filename ", &line)) {
5122 string_ncopy(commit->filename, line, strlen(line));
5123 commit = NULL;
5124 }
5126 return TRUE;
5127 }
5129 static bool
5130 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5131 {
5132 struct blame *blame = line->data;
5133 struct time *time = NULL;
5134 const char *id = NULL, *author = NULL;
5135 char text[SIZEOF_STR];
5137 if (blame->commit && *blame->commit->filename) {
5138 id = blame->commit->id;
5139 author = blame->commit->author;
5140 time = &blame->commit->time;
5141 }
5143 if (opt_date && draw_date(view, time))
5144 return TRUE;
5146 if (opt_author && draw_author(view, author))
5147 return TRUE;
5149 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5150 return TRUE;
5152 if (draw_lineno(view, lineno))
5153 return TRUE;
5155 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5156 draw_text(view, LINE_DEFAULT, text, TRUE);
5157 return TRUE;
5158 }
5160 static bool
5161 check_blame_commit(struct blame *blame, bool check_null_id)
5162 {
5163 if (!blame->commit)
5164 report("Commit data not loaded yet");
5165 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5166 report("No commit exist for the selected line");
5167 else
5168 return TRUE;
5169 return FALSE;
5170 }
5172 static void
5173 setup_blame_parent_line(struct view *view, struct blame *blame)
5174 {
5175 const char *diff_tree_argv[] = {
5176 "git", "diff-tree", "-U0", blame->commit->id,
5177 "--", blame->commit->filename, NULL
5178 };
5179 struct io io;
5180 int parent_lineno = -1;
5181 int blamed_lineno = -1;
5182 char *line;
5184 if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5185 return;
5187 while ((line = io_get(&io, '\n', TRUE))) {
5188 if (*line == '@') {
5189 char *pos = strchr(line, '+');
5191 parent_lineno = atoi(line + 4);
5192 if (pos)
5193 blamed_lineno = atoi(pos + 1);
5195 } else if (*line == '+' && parent_lineno != -1) {
5196 if (blame->lineno == blamed_lineno - 1 &&
5197 !strcmp(blame->text, line + 1)) {
5198 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5199 break;
5200 }
5201 blamed_lineno++;
5202 }
5203 }
5205 io_done(&io);
5206 }
5208 static enum request
5209 blame_request(struct view *view, enum request request, struct line *line)
5210 {
5211 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5212 struct blame *blame = line->data;
5214 switch (request) {
5215 case REQ_VIEW_BLAME:
5216 if (check_blame_commit(blame, TRUE)) {
5217 string_copy(opt_ref, blame->commit->id);
5218 string_copy(opt_file, blame->commit->filename);
5219 if (blame->lineno)
5220 view->lineno = blame->lineno;
5221 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5222 }
5223 break;
5225 case REQ_PARENT:
5226 if (check_blame_commit(blame, TRUE) &&
5227 select_commit_parent(blame->commit->id, opt_ref,
5228 blame->commit->filename)) {
5229 string_copy(opt_file, blame->commit->filename);
5230 setup_blame_parent_line(view, blame);
5231 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5232 }
5233 break;
5235 case REQ_ENTER:
5236 if (!check_blame_commit(blame, FALSE))
5237 break;
5239 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5240 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5241 break;
5243 if (!strcmp(blame->commit->id, NULL_ID)) {
5244 struct view *diff = VIEW(REQ_VIEW_DIFF);
5245 const char *diff_index_argv[] = {
5246 "git", "diff-index", "--root", "--patch-with-stat",
5247 "-C", "-M", "HEAD", "--", view->vid, NULL
5248 };
5250 if (!blame->commit->has_previous) {
5251 diff_index_argv[1] = "diff";
5252 diff_index_argv[2] = "--no-color";
5253 diff_index_argv[6] = "--";
5254 diff_index_argv[7] = "/dev/null";
5255 }
5257 if (!prepare_update(diff, diff_index_argv, NULL)) {
5258 report("Failed to allocate diff command");
5259 break;
5260 }
5261 flags |= OPEN_PREPARED;
5262 }
5264 open_view(view, REQ_VIEW_DIFF, flags);
5265 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5266 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5267 break;
5269 default:
5270 return request;
5271 }
5273 return REQ_NONE;
5274 }
5276 static bool
5277 blame_grep(struct view *view, struct line *line)
5278 {
5279 struct blame *blame = line->data;
5280 struct blame_commit *commit = blame->commit;
5281 const char *text[] = {
5282 blame->text,
5283 commit ? commit->title : "",
5284 commit ? commit->id : "",
5285 commit && opt_author ? commit->author : "",
5286 commit ? mkdate(&commit->time, opt_date) : "",
5287 NULL
5288 };
5290 return grep_text(view, text);
5291 }
5293 static void
5294 blame_select(struct view *view, struct line *line)
5295 {
5296 struct blame *blame = line->data;
5297 struct blame_commit *commit = blame->commit;
5299 if (!commit)
5300 return;
5302 if (!strcmp(commit->id, NULL_ID))
5303 string_ncopy(ref_commit, "HEAD", 4);
5304 else
5305 string_copy_rev(ref_commit, commit->id);
5306 }
5308 static struct view_ops blame_ops = {
5309 "line",
5310 NULL,
5311 blame_open,
5312 blame_read,
5313 blame_draw,
5314 blame_request,
5315 blame_grep,
5316 blame_select,
5317 };
5319 /*
5320 * Branch backend
5321 */
5323 struct branch {
5324 const char *author; /* Author of the last commit. */
5325 struct time time; /* Date of the last activity. */
5326 const struct ref *ref; /* Name and commit ID information. */
5327 };
5329 static const struct ref branch_all;
5331 static const enum sort_field branch_sort_fields[] = {
5332 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5333 };
5334 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5336 static int
5337 branch_compare(const void *l1, const void *l2)
5338 {
5339 const struct branch *branch1 = ((const struct line *) l1)->data;
5340 const struct branch *branch2 = ((const struct line *) l2)->data;
5342 switch (get_sort_field(branch_sort_state)) {
5343 case ORDERBY_DATE:
5344 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5346 case ORDERBY_AUTHOR:
5347 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5349 case ORDERBY_NAME:
5350 default:
5351 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5352 }
5353 }
5355 static bool
5356 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5357 {
5358 struct branch *branch = line->data;
5359 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5361 if (opt_date && draw_date(view, &branch->time))
5362 return TRUE;
5364 if (opt_author && draw_author(view, branch->author))
5365 return TRUE;
5367 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5368 return TRUE;
5369 }
5371 static enum request
5372 branch_request(struct view *view, enum request request, struct line *line)
5373 {
5374 struct branch *branch = line->data;
5376 switch (request) {
5377 case REQ_REFRESH:
5378 load_refs();
5379 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5380 return REQ_NONE;
5382 case REQ_TOGGLE_SORT_FIELD:
5383 case REQ_TOGGLE_SORT_ORDER:
5384 sort_view(view, request, &branch_sort_state, branch_compare);
5385 return REQ_NONE;
5387 case REQ_ENTER:
5388 {
5389 const struct ref *ref = branch->ref;
5390 const char *all_branches_argv[] = {
5391 "git", "log", "--no-color", "--pretty=raw", "--parents",
5392 "--topo-order",
5393 ref == &branch_all ? "--all" : ref->name, NULL
5394 };
5395 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5397 if (!prepare_update(main_view, all_branches_argv, NULL))
5398 report("Failed to load view of all branches");
5399 else
5400 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5401 return REQ_NONE;
5402 }
5403 default:
5404 return request;
5405 }
5406 }
5408 static bool
5409 branch_read(struct view *view, char *line)
5410 {
5411 static char id[SIZEOF_REV];
5412 struct branch *reference;
5413 size_t i;
5415 if (!line)
5416 return TRUE;
5418 switch (get_line_type(line)) {
5419 case LINE_COMMIT:
5420 string_copy_rev(id, line + STRING_SIZE("commit "));
5421 return TRUE;
5423 case LINE_AUTHOR:
5424 for (i = 0, reference = NULL; i < view->lines; i++) {
5425 struct branch *branch = view->line[i].data;
5427 if (strcmp(branch->ref->id, id))
5428 continue;
5430 view->line[i].dirty = TRUE;
5431 if (reference) {
5432 branch->author = reference->author;
5433 branch->time = reference->time;
5434 continue;
5435 }
5437 parse_author_line(line + STRING_SIZE("author "),
5438 &branch->author, &branch->time);
5439 reference = branch;
5440 }
5441 return TRUE;
5443 default:
5444 return TRUE;
5445 }
5447 }
5449 static bool
5450 branch_open_visitor(void *data, const struct ref *ref)
5451 {
5452 struct view *view = data;
5453 struct branch *branch;
5455 if (ref->tag || ref->ltag || ref->remote)
5456 return TRUE;
5458 branch = calloc(1, sizeof(*branch));
5459 if (!branch)
5460 return FALSE;
5462 branch->ref = ref;
5463 return !!add_line_data(view, branch, LINE_DEFAULT);
5464 }
5466 static bool
5467 branch_open(struct view *view)
5468 {
5469 const char *branch_log[] = {
5470 "git", "log", "--no-color", "--pretty=raw",
5471 "--simplify-by-decoration", "--all", NULL
5472 };
5474 if (!start_update(view, branch_log, NULL)) {
5475 report("Failed to load branch data");
5476 return TRUE;
5477 }
5479 setup_update(view, view->id);
5480 branch_open_visitor(view, &branch_all);
5481 foreach_ref(branch_open_visitor, view);
5482 view->p_restore = TRUE;
5484 return TRUE;
5485 }
5487 static bool
5488 branch_grep(struct view *view, struct line *line)
5489 {
5490 struct branch *branch = line->data;
5491 const char *text[] = {
5492 branch->ref->name,
5493 branch->author,
5494 NULL
5495 };
5497 return grep_text(view, text);
5498 }
5500 static void
5501 branch_select(struct view *view, struct line *line)
5502 {
5503 struct branch *branch = line->data;
5505 string_copy_rev(view->ref, branch->ref->id);
5506 string_copy_rev(ref_commit, branch->ref->id);
5507 string_copy_rev(ref_head, branch->ref->id);
5508 string_copy_rev(ref_branch, branch->ref->name);
5509 }
5511 static struct view_ops branch_ops = {
5512 "branch",
5513 NULL,
5514 branch_open,
5515 branch_read,
5516 branch_draw,
5517 branch_request,
5518 branch_grep,
5519 branch_select,
5520 };
5522 /*
5523 * Status backend
5524 */
5526 struct status {
5527 char status;
5528 struct {
5529 mode_t mode;
5530 char rev[SIZEOF_REV];
5531 char name[SIZEOF_STR];
5532 } old;
5533 struct {
5534 mode_t mode;
5535 char rev[SIZEOF_REV];
5536 char name[SIZEOF_STR];
5537 } new;
5538 };
5540 static char status_onbranch[SIZEOF_STR];
5541 static struct status stage_status;
5542 static enum line_type stage_line_type;
5543 static size_t stage_chunks;
5544 static int *stage_chunk;
5546 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5548 /* This should work even for the "On branch" line. */
5549 static inline bool
5550 status_has_none(struct view *view, struct line *line)
5551 {
5552 return line < view->line + view->lines && !line[1].data;
5553 }
5555 /* Get fields from the diff line:
5556 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5557 */
5558 static inline bool
5559 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5560 {
5561 const char *old_mode = buf + 1;
5562 const char *new_mode = buf + 8;
5563 const char *old_rev = buf + 15;
5564 const char *new_rev = buf + 56;
5565 const char *status = buf + 97;
5567 if (bufsize < 98 ||
5568 old_mode[-1] != ':' ||
5569 new_mode[-1] != ' ' ||
5570 old_rev[-1] != ' ' ||
5571 new_rev[-1] != ' ' ||
5572 status[-1] != ' ')
5573 return FALSE;
5575 file->status = *status;
5577 string_copy_rev(file->old.rev, old_rev);
5578 string_copy_rev(file->new.rev, new_rev);
5580 file->old.mode = strtoul(old_mode, NULL, 8);
5581 file->new.mode = strtoul(new_mode, NULL, 8);
5583 file->old.name[0] = file->new.name[0] = 0;
5585 return TRUE;
5586 }
5588 static bool
5589 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5590 {
5591 struct status *unmerged = NULL;
5592 char *buf;
5593 struct io io;
5595 if (!io_run(&io, IO_RD, opt_cdup, argv))
5596 return FALSE;
5598 add_line_data(view, NULL, type);
5600 while ((buf = io_get(&io, 0, TRUE))) {
5601 struct status *file = unmerged;
5603 if (!file) {
5604 file = calloc(1, sizeof(*file));
5605 if (!file || !add_line_data(view, file, type))
5606 goto error_out;
5607 }
5609 /* Parse diff info part. */
5610 if (status) {
5611 file->status = status;
5612 if (status == 'A')
5613 string_copy(file->old.rev, NULL_ID);
5615 } else if (!file->status || file == unmerged) {
5616 if (!status_get_diff(file, buf, strlen(buf)))
5617 goto error_out;
5619 buf = io_get(&io, 0, TRUE);
5620 if (!buf)
5621 break;
5623 /* Collapse all modified entries that follow an
5624 * associated unmerged entry. */
5625 if (unmerged == file) {
5626 unmerged->status = 'U';
5627 unmerged = NULL;
5628 } else if (file->status == 'U') {
5629 unmerged = file;
5630 }
5631 }
5633 /* Grab the old name for rename/copy. */
5634 if (!*file->old.name &&
5635 (file->status == 'R' || file->status == 'C')) {
5636 string_ncopy(file->old.name, buf, strlen(buf));
5638 buf = io_get(&io, 0, TRUE);
5639 if (!buf)
5640 break;
5641 }
5643 /* git-ls-files just delivers a NUL separated list of
5644 * file names similar to the second half of the
5645 * git-diff-* output. */
5646 string_ncopy(file->new.name, buf, strlen(buf));
5647 if (!*file->old.name)
5648 string_copy(file->old.name, file->new.name);
5649 file = NULL;
5650 }
5652 if (io_error(&io)) {
5653 error_out:
5654 io_done(&io);
5655 return FALSE;
5656 }
5658 if (!view->line[view->lines - 1].data)
5659 add_line_data(view, NULL, LINE_STAT_NONE);
5661 io_done(&io);
5662 return TRUE;
5663 }
5665 /* Don't show unmerged entries in the staged section. */
5666 static const char *status_diff_index_argv[] = {
5667 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5668 "--cached", "-M", "HEAD", NULL
5669 };
5671 static const char *status_diff_files_argv[] = {
5672 "git", "diff-files", "-z", NULL
5673 };
5675 static const char *status_list_other_argv[] = {
5676 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5677 };
5679 static const char *status_list_no_head_argv[] = {
5680 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5681 };
5683 static const char *update_index_argv[] = {
5684 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5685 };
5687 /* Restore the previous line number to stay in the context or select a
5688 * line with something that can be updated. */
5689 static void
5690 status_restore(struct view *view)
5691 {
5692 if (view->p_lineno >= view->lines)
5693 view->p_lineno = view->lines - 1;
5694 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5695 view->p_lineno++;
5696 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5697 view->p_lineno--;
5699 /* If the above fails, always skip the "On branch" line. */
5700 if (view->p_lineno < view->lines)
5701 view->lineno = view->p_lineno;
5702 else
5703 view->lineno = 1;
5705 if (view->lineno < view->offset)
5706 view->offset = view->lineno;
5707 else if (view->offset + view->height <= view->lineno)
5708 view->offset = view->lineno - view->height + 1;
5710 view->p_restore = FALSE;
5711 }
5713 static void
5714 status_update_onbranch(void)
5715 {
5716 static const char *paths[][2] = {
5717 { "rebase-apply/rebasing", "Rebasing" },
5718 { "rebase-apply/applying", "Applying mailbox" },
5719 { "rebase-apply/", "Rebasing mailbox" },
5720 { "rebase-merge/interactive", "Interactive rebase" },
5721 { "rebase-merge/", "Rebase merge" },
5722 { "MERGE_HEAD", "Merging" },
5723 { "BISECT_LOG", "Bisecting" },
5724 { "HEAD", "On branch" },
5725 };
5726 char buf[SIZEOF_STR];
5727 struct stat stat;
5728 int i;
5730 if (is_initial_commit()) {
5731 string_copy(status_onbranch, "Initial commit");
5732 return;
5733 }
5735 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5736 char *head = opt_head;
5738 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5739 lstat(buf, &stat) < 0)
5740 continue;
5742 if (!*opt_head) {
5743 struct io io;
5745 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5746 io_read_buf(&io, buf, sizeof(buf))) {
5747 head = buf;
5748 if (!prefixcmp(head, "refs/heads/"))
5749 head += STRING_SIZE("refs/heads/");
5750 }
5751 }
5753 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5754 string_copy(status_onbranch, opt_head);
5755 return;
5756 }
5758 string_copy(status_onbranch, "Not currently on any branch");
5759 }
5761 /* First parse staged info using git-diff-index(1), then parse unstaged
5762 * info using git-diff-files(1), and finally untracked files using
5763 * git-ls-files(1). */
5764 static bool
5765 status_open(struct view *view)
5766 {
5767 reset_view(view);
5769 add_line_data(view, NULL, LINE_STAT_HEAD);
5770 status_update_onbranch();
5772 io_run_bg(update_index_argv);
5774 if (is_initial_commit()) {
5775 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5776 return FALSE;
5777 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5778 return FALSE;
5779 }
5781 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5782 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5783 return FALSE;
5785 /* Restore the exact position or use the specialized restore
5786 * mode? */
5787 if (!view->p_restore)
5788 status_restore(view);
5789 return TRUE;
5790 }
5792 static bool
5793 status_draw(struct view *view, struct line *line, unsigned int lineno)
5794 {
5795 struct status *status = line->data;
5796 enum line_type type;
5797 const char *text;
5799 if (!status) {
5800 switch (line->type) {
5801 case LINE_STAT_STAGED:
5802 type = LINE_STAT_SECTION;
5803 text = "Changes to be committed:";
5804 break;
5806 case LINE_STAT_UNSTAGED:
5807 type = LINE_STAT_SECTION;
5808 text = "Changed but not updated:";
5809 break;
5811 case LINE_STAT_UNTRACKED:
5812 type = LINE_STAT_SECTION;
5813 text = "Untracked files:";
5814 break;
5816 case LINE_STAT_NONE:
5817 type = LINE_DEFAULT;
5818 text = " (no files)";
5819 break;
5821 case LINE_STAT_HEAD:
5822 type = LINE_STAT_HEAD;
5823 text = status_onbranch;
5824 break;
5826 default:
5827 return FALSE;
5828 }
5829 } else {
5830 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5832 buf[0] = status->status;
5833 if (draw_text(view, line->type, buf, TRUE))
5834 return TRUE;
5835 type = LINE_DEFAULT;
5836 text = status->new.name;
5837 }
5839 draw_text(view, type, text, TRUE);
5840 return TRUE;
5841 }
5843 static enum request
5844 status_load_error(struct view *view, struct view *stage, const char *path)
5845 {
5846 if (displayed_views() == 2 || display[current_view] != view)
5847 maximize_view(view);
5848 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5849 return REQ_NONE;
5850 }
5852 static enum request
5853 status_enter(struct view *view, struct line *line)
5854 {
5855 struct status *status = line->data;
5856 const char *oldpath = status ? status->old.name : NULL;
5857 /* Diffs for unmerged entries are empty when passing the new
5858 * path, so leave it empty. */
5859 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5860 const char *info;
5861 enum open_flags split;
5862 struct view *stage = VIEW(REQ_VIEW_STAGE);
5864 if (line->type == LINE_STAT_NONE ||
5865 (!status && line[1].type == LINE_STAT_NONE)) {
5866 report("No file to diff");
5867 return REQ_NONE;
5868 }
5870 switch (line->type) {
5871 case LINE_STAT_STAGED:
5872 if (is_initial_commit()) {
5873 const char *no_head_diff_argv[] = {
5874 "git", "diff", "--no-color", "--patch-with-stat",
5875 "--", "/dev/null", newpath, NULL
5876 };
5878 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5879 return status_load_error(view, stage, newpath);
5880 } else {
5881 const char *index_show_argv[] = {
5882 "git", "diff-index", "--root", "--patch-with-stat",
5883 "-C", "-M", "--cached", "HEAD", "--",
5884 oldpath, newpath, NULL
5885 };
5887 if (!prepare_update(stage, index_show_argv, opt_cdup))
5888 return status_load_error(view, stage, newpath);
5889 }
5891 if (status)
5892 info = "Staged changes to %s";
5893 else
5894 info = "Staged changes";
5895 break;
5897 case LINE_STAT_UNSTAGED:
5898 {
5899 const char *files_show_argv[] = {
5900 "git", "diff-files", "--root", "--patch-with-stat",
5901 "-C", "-M", "--", oldpath, newpath, NULL
5902 };
5904 if (!prepare_update(stage, files_show_argv, opt_cdup))
5905 return status_load_error(view, stage, newpath);
5906 if (status)
5907 info = "Unstaged changes to %s";
5908 else
5909 info = "Unstaged changes";
5910 break;
5911 }
5912 case LINE_STAT_UNTRACKED:
5913 if (!newpath) {
5914 report("No file to show");
5915 return REQ_NONE;
5916 }
5918 if (!suffixcmp(status->new.name, -1, "/")) {
5919 report("Cannot display a directory");
5920 return REQ_NONE;
5921 }
5923 if (!prepare_update_file(stage, newpath))
5924 return status_load_error(view, stage, newpath);
5925 info = "Untracked file %s";
5926 break;
5928 case LINE_STAT_HEAD:
5929 return REQ_NONE;
5931 default:
5932 die("line type %d not handled in switch", line->type);
5933 }
5935 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5936 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5937 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5938 if (status) {
5939 stage_status = *status;
5940 } else {
5941 memset(&stage_status, 0, sizeof(stage_status));
5942 }
5944 stage_line_type = line->type;
5945 stage_chunks = 0;
5946 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5947 }
5949 return REQ_NONE;
5950 }
5952 static bool
5953 status_exists(struct status *status, enum line_type type)
5954 {
5955 struct view *view = VIEW(REQ_VIEW_STATUS);
5956 unsigned long lineno;
5958 for (lineno = 0; lineno < view->lines; lineno++) {
5959 struct line *line = &view->line[lineno];
5960 struct status *pos = line->data;
5962 if (line->type != type)
5963 continue;
5964 if (!pos && (!status || !status->status) && line[1].data) {
5965 select_view_line(view, lineno);
5966 return TRUE;
5967 }
5968 if (pos && !strcmp(status->new.name, pos->new.name)) {
5969 select_view_line(view, lineno);
5970 return TRUE;
5971 }
5972 }
5974 return FALSE;
5975 }
5978 static bool
5979 status_update_prepare(struct io *io, enum line_type type)
5980 {
5981 const char *staged_argv[] = {
5982 "git", "update-index", "-z", "--index-info", NULL
5983 };
5984 const char *others_argv[] = {
5985 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5986 };
5988 switch (type) {
5989 case LINE_STAT_STAGED:
5990 return io_run(io, IO_WR, opt_cdup, staged_argv);
5992 case LINE_STAT_UNSTAGED:
5993 case LINE_STAT_UNTRACKED:
5994 return io_run(io, IO_WR, opt_cdup, others_argv);
5996 default:
5997 die("line type %d not handled in switch", type);
5998 return FALSE;
5999 }
6000 }
6002 static bool
6003 status_update_write(struct io *io, struct status *status, enum line_type type)
6004 {
6005 char buf[SIZEOF_STR];
6006 size_t bufsize = 0;
6008 switch (type) {
6009 case LINE_STAT_STAGED:
6010 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6011 status->old.mode,
6012 status->old.rev,
6013 status->old.name, 0))
6014 return FALSE;
6015 break;
6017 case LINE_STAT_UNSTAGED:
6018 case LINE_STAT_UNTRACKED:
6019 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6020 return FALSE;
6021 break;
6023 default:
6024 die("line type %d not handled in switch", type);
6025 }
6027 return io_write(io, buf, bufsize);
6028 }
6030 static bool
6031 status_update_file(struct status *status, enum line_type type)
6032 {
6033 struct io io;
6034 bool result;
6036 if (!status_update_prepare(&io, type))
6037 return FALSE;
6039 result = status_update_write(&io, status, type);
6040 return io_done(&io) && result;
6041 }
6043 static bool
6044 status_update_files(struct view *view, struct line *line)
6045 {
6046 char buf[sizeof(view->ref)];
6047 struct io io;
6048 bool result = TRUE;
6049 struct line *pos = view->line + view->lines;
6050 int files = 0;
6051 int file, done;
6052 int cursor_y = -1, cursor_x = -1;
6054 if (!status_update_prepare(&io, line->type))
6055 return FALSE;
6057 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6058 files++;
6060 string_copy(buf, view->ref);
6061 getsyx(cursor_y, cursor_x);
6062 for (file = 0, done = 5; result && file < files; line++, file++) {
6063 int almost_done = file * 100 / files;
6065 if (almost_done > done) {
6066 done = almost_done;
6067 string_format(view->ref, "updating file %u of %u (%d%% done)",
6068 file, files, done);
6069 update_view_title(view);
6070 setsyx(cursor_y, cursor_x);
6071 doupdate();
6072 }
6073 result = status_update_write(&io, line->data, line->type);
6074 }
6075 string_copy(view->ref, buf);
6077 return io_done(&io) && result;
6078 }
6080 static bool
6081 status_update(struct view *view)
6082 {
6083 struct line *line = &view->line[view->lineno];
6085 assert(view->lines);
6087 if (!line->data) {
6088 /* This should work even for the "On branch" line. */
6089 if (line < view->line + view->lines && !line[1].data) {
6090 report("Nothing to update");
6091 return FALSE;
6092 }
6094 if (!status_update_files(view, line + 1)) {
6095 report("Failed to update file status");
6096 return FALSE;
6097 }
6099 } else if (!status_update_file(line->data, line->type)) {
6100 report("Failed to update file status");
6101 return FALSE;
6102 }
6104 return TRUE;
6105 }
6107 static bool
6108 status_revert(struct status *status, enum line_type type, bool has_none)
6109 {
6110 if (!status || type != LINE_STAT_UNSTAGED) {
6111 if (type == LINE_STAT_STAGED) {
6112 report("Cannot revert changes to staged files");
6113 } else if (type == LINE_STAT_UNTRACKED) {
6114 report("Cannot revert changes to untracked files");
6115 } else if (has_none) {
6116 report("Nothing to revert");
6117 } else {
6118 report("Cannot revert changes to multiple files");
6119 }
6121 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6122 char mode[10] = "100644";
6123 const char *reset_argv[] = {
6124 "git", "update-index", "--cacheinfo", mode,
6125 status->old.rev, status->old.name, NULL
6126 };
6127 const char *checkout_argv[] = {
6128 "git", "checkout", "--", status->old.name, NULL
6129 };
6131 if (status->status == 'U') {
6132 string_format(mode, "%5o", status->old.mode);
6134 if (status->old.mode == 0 && status->new.mode == 0) {
6135 reset_argv[2] = "--force-remove";
6136 reset_argv[3] = status->old.name;
6137 reset_argv[4] = NULL;
6138 }
6140 if (!io_run_fg(reset_argv, opt_cdup))
6141 return FALSE;
6142 if (status->old.mode == 0 && status->new.mode == 0)
6143 return TRUE;
6144 }
6146 return io_run_fg(checkout_argv, opt_cdup);
6147 }
6149 return FALSE;
6150 }
6152 static enum request
6153 status_request(struct view *view, enum request request, struct line *line)
6154 {
6155 struct status *status = line->data;
6157 switch (request) {
6158 case REQ_STATUS_UPDATE:
6159 if (!status_update(view))
6160 return REQ_NONE;
6161 break;
6163 case REQ_STATUS_REVERT:
6164 if (!status_revert(status, line->type, status_has_none(view, line)))
6165 return REQ_NONE;
6166 break;
6168 case REQ_STATUS_MERGE:
6169 if (!status || status->status != 'U') {
6170 report("Merging only possible for files with unmerged status ('U').");
6171 return REQ_NONE;
6172 }
6173 open_mergetool(status->new.name);
6174 break;
6176 case REQ_EDIT:
6177 if (!status)
6178 return request;
6179 if (status->status == 'D') {
6180 report("File has been deleted.");
6181 return REQ_NONE;
6182 }
6184 open_editor(status->new.name);
6185 break;
6187 case REQ_VIEW_BLAME:
6188 if (status)
6189 opt_ref[0] = 0;
6190 return request;
6192 case REQ_ENTER:
6193 /* After returning the status view has been split to
6194 * show the stage view. No further reloading is
6195 * necessary. */
6196 return status_enter(view, line);
6198 case REQ_REFRESH:
6199 /* Simply reload the view. */
6200 break;
6202 default:
6203 return request;
6204 }
6206 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6208 return REQ_NONE;
6209 }
6211 static void
6212 status_select(struct view *view, struct line *line)
6213 {
6214 struct status *status = line->data;
6215 char file[SIZEOF_STR] = "all files";
6216 const char *text;
6217 const char *key;
6219 if (status && !string_format(file, "'%s'", status->new.name))
6220 return;
6222 if (!status && line[1].type == LINE_STAT_NONE)
6223 line++;
6225 switch (line->type) {
6226 case LINE_STAT_STAGED:
6227 text = "Press %s to unstage %s for commit";
6228 break;
6230 case LINE_STAT_UNSTAGED:
6231 text = "Press %s to stage %s for commit";
6232 break;
6234 case LINE_STAT_UNTRACKED:
6235 text = "Press %s to stage %s for addition";
6236 break;
6238 case LINE_STAT_HEAD:
6239 case LINE_STAT_NONE:
6240 text = "Nothing to update";
6241 break;
6243 default:
6244 die("line type %d not handled in switch", line->type);
6245 }
6247 if (status && status->status == 'U') {
6248 text = "Press %s to resolve conflict in %s";
6249 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6251 } else {
6252 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6253 }
6255 string_format(view->ref, text, key, file);
6256 if (status)
6257 string_copy(opt_file, status->new.name);
6258 }
6260 static bool
6261 status_grep(struct view *view, struct line *line)
6262 {
6263 struct status *status = line->data;
6265 if (status) {
6266 const char buf[2] = { status->status, 0 };
6267 const char *text[] = { status->new.name, buf, NULL };
6269 return grep_text(view, text);
6270 }
6272 return FALSE;
6273 }
6275 static struct view_ops status_ops = {
6276 "file",
6277 NULL,
6278 status_open,
6279 NULL,
6280 status_draw,
6281 status_request,
6282 status_grep,
6283 status_select,
6284 };
6287 static bool
6288 stage_diff_write(struct io *io, struct line *line, struct line *end)
6289 {
6290 while (line < end) {
6291 if (!io_write(io, line->data, strlen(line->data)) ||
6292 !io_write(io, "\n", 1))
6293 return FALSE;
6294 line++;
6295 if (line->type == LINE_DIFF_CHUNK ||
6296 line->type == LINE_DIFF_HEADER)
6297 break;
6298 }
6300 return TRUE;
6301 }
6303 static struct line *
6304 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6305 {
6306 for (; view->line < line; line--)
6307 if (line->type == type)
6308 return line;
6310 return NULL;
6311 }
6313 static bool
6314 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6315 {
6316 const char *apply_argv[SIZEOF_ARG] = {
6317 "git", "apply", "--whitespace=nowarn", NULL
6318 };
6319 struct line *diff_hdr;
6320 struct io io;
6321 int argc = 3;
6323 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6324 if (!diff_hdr)
6325 return FALSE;
6327 if (!revert)
6328 apply_argv[argc++] = "--cached";
6329 if (revert || stage_line_type == LINE_STAT_STAGED)
6330 apply_argv[argc++] = "-R";
6331 apply_argv[argc++] = "-";
6332 apply_argv[argc++] = NULL;
6333 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6334 return FALSE;
6336 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6337 !stage_diff_write(&io, chunk, view->line + view->lines))
6338 chunk = NULL;
6340 io_done(&io);
6341 io_run_bg(update_index_argv);
6343 return chunk ? TRUE : FALSE;
6344 }
6346 static bool
6347 stage_update(struct view *view, struct line *line)
6348 {
6349 struct line *chunk = NULL;
6351 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6352 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6354 if (chunk) {
6355 if (!stage_apply_chunk(view, chunk, FALSE)) {
6356 report("Failed to apply chunk");
6357 return FALSE;
6358 }
6360 } else if (!stage_status.status) {
6361 view = VIEW(REQ_VIEW_STATUS);
6363 for (line = view->line; line < view->line + view->lines; line++)
6364 if (line->type == stage_line_type)
6365 break;
6367 if (!status_update_files(view, line + 1)) {
6368 report("Failed to update files");
6369 return FALSE;
6370 }
6372 } else if (!status_update_file(&stage_status, stage_line_type)) {
6373 report("Failed to update file");
6374 return FALSE;
6375 }
6377 return TRUE;
6378 }
6380 static bool
6381 stage_revert(struct view *view, struct line *line)
6382 {
6383 struct line *chunk = NULL;
6385 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6386 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6388 if (chunk) {
6389 if (!prompt_yesno("Are you sure you want to revert changes?"))
6390 return FALSE;
6392 if (!stage_apply_chunk(view, chunk, TRUE)) {
6393 report("Failed to revert chunk");
6394 return FALSE;
6395 }
6396 return TRUE;
6398 } else {
6399 return status_revert(stage_status.status ? &stage_status : NULL,
6400 stage_line_type, FALSE);
6401 }
6402 }
6405 static void
6406 stage_next(struct view *view, struct line *line)
6407 {
6408 int i;
6410 if (!stage_chunks) {
6411 for (line = view->line; line < view->line + view->lines; line++) {
6412 if (line->type != LINE_DIFF_CHUNK)
6413 continue;
6415 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6416 report("Allocation failure");
6417 return;
6418 }
6420 stage_chunk[stage_chunks++] = line - view->line;
6421 }
6422 }
6424 for (i = 0; i < stage_chunks; i++) {
6425 if (stage_chunk[i] > view->lineno) {
6426 do_scroll_view(view, stage_chunk[i] - view->lineno);
6427 report("Chunk %d of %d", i + 1, stage_chunks);
6428 return;
6429 }
6430 }
6432 report("No next chunk found");
6433 }
6435 static enum request
6436 stage_request(struct view *view, enum request request, struct line *line)
6437 {
6438 switch (request) {
6439 case REQ_STATUS_UPDATE:
6440 if (!stage_update(view, line))
6441 return REQ_NONE;
6442 break;
6444 case REQ_STATUS_REVERT:
6445 if (!stage_revert(view, line))
6446 return REQ_NONE;
6447 break;
6449 case REQ_STAGE_NEXT:
6450 if (stage_line_type == LINE_STAT_UNTRACKED) {
6451 report("File is untracked; press %s to add",
6452 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6453 return REQ_NONE;
6454 }
6455 stage_next(view, line);
6456 return REQ_NONE;
6458 case REQ_EDIT:
6459 if (!stage_status.new.name[0])
6460 return request;
6461 if (stage_status.status == 'D') {
6462 report("File has been deleted.");
6463 return REQ_NONE;
6464 }
6466 open_editor(stage_status.new.name);
6467 break;
6469 case REQ_REFRESH:
6470 /* Reload everything ... */
6471 break;
6473 case REQ_VIEW_BLAME:
6474 if (stage_status.new.name[0]) {
6475 string_copy(opt_file, stage_status.new.name);
6476 opt_ref[0] = 0;
6477 }
6478 return request;
6480 case REQ_ENTER:
6481 return pager_request(view, request, line);
6483 default:
6484 return request;
6485 }
6487 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6488 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6490 /* Check whether the staged entry still exists, and close the
6491 * stage view if it doesn't. */
6492 if (!status_exists(&stage_status, stage_line_type)) {
6493 status_restore(VIEW(REQ_VIEW_STATUS));
6494 return REQ_VIEW_CLOSE;
6495 }
6497 if (stage_line_type == LINE_STAT_UNTRACKED) {
6498 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6499 report("Cannot display a directory");
6500 return REQ_NONE;
6501 }
6503 if (!prepare_update_file(view, stage_status.new.name)) {
6504 report("Failed to open file: %s", strerror(errno));
6505 return REQ_NONE;
6506 }
6507 }
6508 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6510 return REQ_NONE;
6511 }
6513 static struct view_ops stage_ops = {
6514 "line",
6515 NULL,
6516 NULL,
6517 pager_read,
6518 pager_draw,
6519 stage_request,
6520 pager_grep,
6521 pager_select,
6522 };
6525 /*
6526 * Revision graph
6527 */
6529 struct commit {
6530 char id[SIZEOF_REV]; /* SHA1 ID. */
6531 char title[128]; /* First line of the commit message. */
6532 const char *author; /* Author of the commit. */
6533 struct time time; /* Date from the author ident. */
6534 struct ref_list *refs; /* Repository references. */
6535 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6536 size_t graph_size; /* The width of the graph array. */
6537 bool has_parents; /* Rewritten --parents seen. */
6538 };
6540 /* Size of rev graph with no "padding" columns */
6541 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6543 struct rev_graph {
6544 struct rev_graph *prev, *next, *parents;
6545 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6546 size_t size;
6547 struct commit *commit;
6548 size_t pos;
6549 unsigned int boundary:1;
6550 };
6552 /* Parents of the commit being visualized. */
6553 static struct rev_graph graph_parents[4];
6555 /* The current stack of revisions on the graph. */
6556 static struct rev_graph graph_stacks[4] = {
6557 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6558 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6559 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6560 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6561 };
6563 static inline bool
6564 graph_parent_is_merge(struct rev_graph *graph)
6565 {
6566 return graph->parents->size > 1;
6567 }
6569 static inline void
6570 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6571 {
6572 struct commit *commit = graph->commit;
6574 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6575 commit->graph[commit->graph_size++] = symbol;
6576 }
6578 static void
6579 clear_rev_graph(struct rev_graph *graph)
6580 {
6581 graph->boundary = 0;
6582 graph->size = graph->pos = 0;
6583 graph->commit = NULL;
6584 memset(graph->parents, 0, sizeof(*graph->parents));
6585 }
6587 static void
6588 done_rev_graph(struct rev_graph *graph)
6589 {
6590 if (graph_parent_is_merge(graph) &&
6591 graph->pos < graph->size - 1 &&
6592 graph->next->size == graph->size + graph->parents->size - 1) {
6593 size_t i = graph->pos + graph->parents->size - 1;
6595 graph->commit->graph_size = i * 2;
6596 while (i < graph->next->size - 1) {
6597 append_to_rev_graph(graph, ' ');
6598 append_to_rev_graph(graph, '\\');
6599 i++;
6600 }
6601 }
6603 clear_rev_graph(graph);
6604 }
6606 static void
6607 push_rev_graph(struct rev_graph *graph, const char *parent)
6608 {
6609 int i;
6611 /* "Collapse" duplicate parents lines.
6612 *
6613 * FIXME: This needs to also update update the drawn graph but
6614 * for now it just serves as a method for pruning graph lines. */
6615 for (i = 0; i < graph->size; i++)
6616 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6617 return;
6619 if (graph->size < SIZEOF_REVITEMS) {
6620 string_copy_rev(graph->rev[graph->size++], parent);
6621 }
6622 }
6624 static chtype
6625 get_rev_graph_symbol(struct rev_graph *graph)
6626 {
6627 chtype symbol;
6629 if (graph->boundary)
6630 symbol = REVGRAPH_BOUND;
6631 else if (graph->parents->size == 0)
6632 symbol = REVGRAPH_INIT;
6633 else if (graph_parent_is_merge(graph))
6634 symbol = REVGRAPH_MERGE;
6635 else if (graph->pos >= graph->size)
6636 symbol = REVGRAPH_BRANCH;
6637 else
6638 symbol = REVGRAPH_COMMIT;
6640 return symbol;
6641 }
6643 static void
6644 draw_rev_graph(struct rev_graph *graph)
6645 {
6646 struct rev_filler {
6647 chtype separator, line;
6648 };
6649 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6650 static struct rev_filler fillers[] = {
6651 { ' ', '|' },
6652 { '`', '.' },
6653 { '\'', ' ' },
6654 { '/', ' ' },
6655 };
6656 chtype symbol = get_rev_graph_symbol(graph);
6657 struct rev_filler *filler;
6658 size_t i;
6660 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6661 filler = &fillers[DEFAULT];
6663 for (i = 0; i < graph->pos; i++) {
6664 append_to_rev_graph(graph, filler->line);
6665 if (graph_parent_is_merge(graph->prev) &&
6666 graph->prev->pos == i)
6667 filler = &fillers[RSHARP];
6669 append_to_rev_graph(graph, filler->separator);
6670 }
6672 /* Place the symbol for this revision. */
6673 append_to_rev_graph(graph, symbol);
6675 if (graph->prev->size > graph->size)
6676 filler = &fillers[RDIAG];
6677 else
6678 filler = &fillers[DEFAULT];
6680 i++;
6682 for (; i < graph->size; i++) {
6683 append_to_rev_graph(graph, filler->separator);
6684 append_to_rev_graph(graph, filler->line);
6685 if (graph_parent_is_merge(graph->prev) &&
6686 i < graph->prev->pos + graph->parents->size)
6687 filler = &fillers[RSHARP];
6688 if (graph->prev->size > graph->size)
6689 filler = &fillers[LDIAG];
6690 }
6692 if (graph->prev->size > graph->size) {
6693 append_to_rev_graph(graph, filler->separator);
6694 if (filler->line != ' ')
6695 append_to_rev_graph(graph, filler->line);
6696 }
6697 }
6699 /* Prepare the next rev graph */
6700 static void
6701 prepare_rev_graph(struct rev_graph *graph)
6702 {
6703 size_t i;
6705 /* First, traverse all lines of revisions up to the active one. */
6706 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6707 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6708 break;
6710 push_rev_graph(graph->next, graph->rev[graph->pos]);
6711 }
6713 /* Interleave the new revision parent(s). */
6714 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6715 push_rev_graph(graph->next, graph->parents->rev[i]);
6717 /* Lastly, put any remaining revisions. */
6718 for (i = graph->pos + 1; i < graph->size; i++)
6719 push_rev_graph(graph->next, graph->rev[i]);
6720 }
6722 static void
6723 update_rev_graph(struct view *view, struct rev_graph *graph)
6724 {
6725 /* If this is the finalizing update ... */
6726 if (graph->commit)
6727 prepare_rev_graph(graph);
6729 /* Graph visualization needs a one rev look-ahead,
6730 * so the first update doesn't visualize anything. */
6731 if (!graph->prev->commit)
6732 return;
6734 if (view->lines > 2)
6735 view->line[view->lines - 3].dirty = 1;
6736 if (view->lines > 1)
6737 view->line[view->lines - 2].dirty = 1;
6738 draw_rev_graph(graph->prev);
6739 done_rev_graph(graph->prev->prev);
6740 }
6743 /*
6744 * Main view backend
6745 */
6747 static const char *main_argv[SIZEOF_ARG] = {
6748 "git", "log", "--no-color", "--pretty=raw", "--parents",
6749 "--topo-order", "%(diff-args)", "%(rev-args)",
6750 "--", "%(file-args)", NULL
6751 };
6753 static bool
6754 main_draw(struct view *view, struct line *line, unsigned int lineno)
6755 {
6756 struct commit *commit = line->data;
6758 if (!commit->author)
6759 return FALSE;
6761 if (opt_date && draw_date(view, &commit->time))
6762 return TRUE;
6764 if (opt_author && draw_author(view, commit->author))
6765 return TRUE;
6767 if (opt_rev_graph && commit->graph_size &&
6768 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6769 return TRUE;
6771 if (opt_show_refs && commit->refs) {
6772 size_t i;
6774 for (i = 0; i < commit->refs->size; i++) {
6775 struct ref *ref = commit->refs->refs[i];
6776 enum line_type type;
6778 if (ref->head)
6779 type = LINE_MAIN_HEAD;
6780 else if (ref->ltag)
6781 type = LINE_MAIN_LOCAL_TAG;
6782 else if (ref->tag)
6783 type = LINE_MAIN_TAG;
6784 else if (ref->tracked)
6785 type = LINE_MAIN_TRACKED;
6786 else if (ref->remote)
6787 type = LINE_MAIN_REMOTE;
6788 else
6789 type = LINE_MAIN_REF;
6791 if (draw_text(view, type, "[", TRUE) ||
6792 draw_text(view, type, ref->name, TRUE) ||
6793 draw_text(view, type, "]", TRUE))
6794 return TRUE;
6796 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6797 return TRUE;
6798 }
6799 }
6801 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6802 return TRUE;
6803 }
6805 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6806 static bool
6807 main_read(struct view *view, char *line)
6808 {
6809 static struct rev_graph *graph = graph_stacks;
6810 enum line_type type;
6811 struct commit *commit;
6813 if (!line) {
6814 int i;
6816 if (!view->lines && !view->prev)
6817 die("No revisions match the given arguments.");
6818 if (view->lines > 0) {
6819 commit = view->line[view->lines - 1].data;
6820 view->line[view->lines - 1].dirty = 1;
6821 if (!commit->author) {
6822 view->lines--;
6823 free(commit);
6824 graph->commit = NULL;
6825 }
6826 }
6827 update_rev_graph(view, graph);
6829 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6830 clear_rev_graph(&graph_stacks[i]);
6831 return TRUE;
6832 }
6834 type = get_line_type(line);
6835 if (type == LINE_COMMIT) {
6836 commit = calloc(1, sizeof(struct commit));
6837 if (!commit)
6838 return FALSE;
6840 line += STRING_SIZE("commit ");
6841 if (*line == '-') {
6842 graph->boundary = 1;
6843 line++;
6844 }
6846 string_copy_rev(commit->id, line);
6847 commit->refs = get_ref_list(commit->id);
6848 graph->commit = commit;
6849 add_line_data(view, commit, LINE_MAIN_COMMIT);
6851 while ((line = strchr(line, ' '))) {
6852 line++;
6853 push_rev_graph(graph->parents, line);
6854 commit->has_parents = TRUE;
6855 }
6856 return TRUE;
6857 }
6859 if (!view->lines)
6860 return TRUE;
6861 commit = view->line[view->lines - 1].data;
6863 switch (type) {
6864 case LINE_PARENT:
6865 if (commit->has_parents)
6866 break;
6867 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6868 break;
6870 case LINE_AUTHOR:
6871 parse_author_line(line + STRING_SIZE("author "),
6872 &commit->author, &commit->time);
6873 update_rev_graph(view, graph);
6874 graph = graph->next;
6875 break;
6877 default:
6878 /* Fill in the commit title if it has not already been set. */
6879 if (commit->title[0])
6880 break;
6882 /* Require titles to start with a non-space character at the
6883 * offset used by git log. */
6884 if (strncmp(line, " ", 4))
6885 break;
6886 line += 4;
6887 /* Well, if the title starts with a whitespace character,
6888 * try to be forgiving. Otherwise we end up with no title. */
6889 while (isspace(*line))
6890 line++;
6891 if (*line == '\0')
6892 break;
6893 /* FIXME: More graceful handling of titles; append "..." to
6894 * shortened titles, etc. */
6896 string_expand(commit->title, sizeof(commit->title), line, 1);
6897 view->line[view->lines - 1].dirty = 1;
6898 }
6900 return TRUE;
6901 }
6903 static enum request
6904 main_request(struct view *view, enum request request, struct line *line)
6905 {
6906 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6908 switch (request) {
6909 case REQ_ENTER:
6910 open_view(view, REQ_VIEW_DIFF, flags);
6911 break;
6912 case REQ_REFRESH:
6913 load_refs();
6914 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6915 break;
6916 default:
6917 return request;
6918 }
6920 return REQ_NONE;
6921 }
6923 static bool
6924 grep_refs(struct ref_list *list, regex_t *regex)
6925 {
6926 regmatch_t pmatch;
6927 size_t i;
6929 if (!opt_show_refs || !list)
6930 return FALSE;
6932 for (i = 0; i < list->size; i++) {
6933 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6934 return TRUE;
6935 }
6937 return FALSE;
6938 }
6940 static bool
6941 main_grep(struct view *view, struct line *line)
6942 {
6943 struct commit *commit = line->data;
6944 const char *text[] = {
6945 commit->title,
6946 opt_author ? commit->author : "",
6947 mkdate(&commit->time, opt_date),
6948 NULL
6949 };
6951 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6952 }
6954 static void
6955 main_select(struct view *view, struct line *line)
6956 {
6957 struct commit *commit = line->data;
6959 string_copy_rev(view->ref, commit->id);
6960 string_copy_rev(ref_commit, view->ref);
6961 }
6963 static struct view_ops main_ops = {
6964 "commit",
6965 main_argv,
6966 NULL,
6967 main_read,
6968 main_draw,
6969 main_request,
6970 main_grep,
6971 main_select,
6972 };
6975 /*
6976 * Status management
6977 */
6979 /* Whether or not the curses interface has been initialized. */
6980 static bool cursed = FALSE;
6982 /* Terminal hacks and workarounds. */
6983 static bool use_scroll_redrawwin;
6984 static bool use_scroll_status_wclear;
6986 /* The status window is used for polling keystrokes. */
6987 static WINDOW *status_win;
6989 /* Reading from the prompt? */
6990 static bool input_mode = FALSE;
6992 static bool status_empty = FALSE;
6994 /* Update status and title window. */
6995 static void
6996 report(const char *msg, ...)
6997 {
6998 struct view *view = display[current_view];
7000 if (input_mode)
7001 return;
7003 if (!view) {
7004 char buf[SIZEOF_STR];
7005 va_list args;
7007 va_start(args, msg);
7008 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7009 buf[sizeof(buf) - 1] = 0;
7010 buf[sizeof(buf) - 2] = '.';
7011 buf[sizeof(buf) - 3] = '.';
7012 buf[sizeof(buf) - 4] = '.';
7013 }
7014 va_end(args);
7015 die("%s", buf);
7016 }
7018 if (!status_empty || *msg) {
7019 va_list args;
7021 va_start(args, msg);
7023 wmove(status_win, 0, 0);
7024 if (view->has_scrolled && use_scroll_status_wclear)
7025 wclear(status_win);
7026 if (*msg) {
7027 vwprintw(status_win, msg, args);
7028 status_empty = FALSE;
7029 } else {
7030 status_empty = TRUE;
7031 }
7032 wclrtoeol(status_win);
7033 wnoutrefresh(status_win);
7035 va_end(args);
7036 }
7038 update_view_title(view);
7039 }
7041 static void
7042 init_display(void)
7043 {
7044 const char *term;
7045 int x, y;
7047 /* Initialize the curses library */
7048 if (isatty(STDIN_FILENO)) {
7049 cursed = !!initscr();
7050 opt_tty = stdin;
7051 } else {
7052 /* Leave stdin and stdout alone when acting as a pager. */
7053 opt_tty = fopen("/dev/tty", "r+");
7054 if (!opt_tty)
7055 die("Failed to open /dev/tty");
7056 cursed = !!newterm(NULL, opt_tty, opt_tty);
7057 }
7059 if (!cursed)
7060 die("Failed to initialize curses");
7062 nonl(); /* Disable conversion and detect newlines from input. */
7063 cbreak(); /* Take input chars one at a time, no wait for \n */
7064 noecho(); /* Don't echo input */
7065 leaveok(stdscr, FALSE);
7067 if (has_colors())
7068 init_colors();
7070 getmaxyx(stdscr, y, x);
7071 status_win = newwin(1, 0, y - 1, 0);
7072 if (!status_win)
7073 die("Failed to create status window");
7075 /* Enable keyboard mapping */
7076 keypad(status_win, TRUE);
7077 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7079 TABSIZE = opt_tab_size;
7081 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7082 if (term && !strcmp(term, "gnome-terminal")) {
7083 /* In the gnome-terminal-emulator, the message from
7084 * scrolling up one line when impossible followed by
7085 * scrolling down one line causes corruption of the
7086 * status line. This is fixed by calling wclear. */
7087 use_scroll_status_wclear = TRUE;
7088 use_scroll_redrawwin = FALSE;
7090 } else if (term && !strcmp(term, "xrvt-xpm")) {
7091 /* No problems with full optimizations in xrvt-(unicode)
7092 * and aterm. */
7093 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7095 } else {
7096 /* When scrolling in (u)xterm the last line in the
7097 * scrolling direction will update slowly. */
7098 use_scroll_redrawwin = TRUE;
7099 use_scroll_status_wclear = FALSE;
7100 }
7101 }
7103 static int
7104 get_input(int prompt_position)
7105 {
7106 struct view *view;
7107 int i, key, cursor_y, cursor_x;
7109 if (prompt_position)
7110 input_mode = TRUE;
7112 while (TRUE) {
7113 bool loading = FALSE;
7115 foreach_view (view, i) {
7116 update_view(view);
7117 if (view_is_displayed(view) && view->has_scrolled &&
7118 use_scroll_redrawwin)
7119 redrawwin(view->win);
7120 view->has_scrolled = FALSE;
7121 if (view->pipe)
7122 loading = TRUE;
7123 }
7125 /* Update the cursor position. */
7126 if (prompt_position) {
7127 getbegyx(status_win, cursor_y, cursor_x);
7128 cursor_x = prompt_position;
7129 } else {
7130 view = display[current_view];
7131 getbegyx(view->win, cursor_y, cursor_x);
7132 cursor_x = view->width - 1;
7133 cursor_y += view->lineno - view->offset;
7134 }
7135 setsyx(cursor_y, cursor_x);
7137 /* Refresh, accept single keystroke of input */
7138 doupdate();
7139 nodelay(status_win, loading);
7140 key = wgetch(status_win);
7142 /* wgetch() with nodelay() enabled returns ERR when
7143 * there's no input. */
7144 if (key == ERR) {
7146 } else if (key == KEY_RESIZE) {
7147 int height, width;
7149 getmaxyx(stdscr, height, width);
7151 wresize(status_win, 1, width);
7152 mvwin(status_win, height - 1, 0);
7153 wnoutrefresh(status_win);
7154 resize_display();
7155 redraw_display(TRUE);
7157 } else {
7158 input_mode = FALSE;
7159 return key;
7160 }
7161 }
7162 }
7164 static char *
7165 prompt_input(const char *prompt, input_handler handler, void *data)
7166 {
7167 enum input_status status = INPUT_OK;
7168 static char buf[SIZEOF_STR];
7169 size_t pos = 0;
7171 buf[pos] = 0;
7173 while (status == INPUT_OK || status == INPUT_SKIP) {
7174 int key;
7176 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7177 wclrtoeol(status_win);
7179 key = get_input(pos + 1);
7180 switch (key) {
7181 case KEY_RETURN:
7182 case KEY_ENTER:
7183 case '\n':
7184 status = pos ? INPUT_STOP : INPUT_CANCEL;
7185 break;
7187 case KEY_BACKSPACE:
7188 if (pos > 0)
7189 buf[--pos] = 0;
7190 else
7191 status = INPUT_CANCEL;
7192 break;
7194 case KEY_ESC:
7195 status = INPUT_CANCEL;
7196 break;
7198 default:
7199 if (pos >= sizeof(buf)) {
7200 report("Input string too long");
7201 return NULL;
7202 }
7204 status = handler(data, buf, key);
7205 if (status == INPUT_OK)
7206 buf[pos++] = (char) key;
7207 }
7208 }
7210 /* Clear the status window */
7211 status_empty = FALSE;
7212 report("");
7214 if (status == INPUT_CANCEL)
7215 return NULL;
7217 buf[pos++] = 0;
7219 return buf;
7220 }
7222 static enum input_status
7223 prompt_yesno_handler(void *data, char *buf, int c)
7224 {
7225 if (c == 'y' || c == 'Y')
7226 return INPUT_STOP;
7227 if (c == 'n' || c == 'N')
7228 return INPUT_CANCEL;
7229 return INPUT_SKIP;
7230 }
7232 static bool
7233 prompt_yesno(const char *prompt)
7234 {
7235 char prompt2[SIZEOF_STR];
7237 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7238 return FALSE;
7240 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7241 }
7243 static enum input_status
7244 read_prompt_handler(void *data, char *buf, int c)
7245 {
7246 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7247 }
7249 static char *
7250 read_prompt(const char *prompt)
7251 {
7252 return prompt_input(prompt, read_prompt_handler, NULL);
7253 }
7255 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7256 {
7257 enum input_status status = INPUT_OK;
7258 int size = 0;
7260 while (items[size].text)
7261 size++;
7263 while (status == INPUT_OK) {
7264 const struct menu_item *item = &items[*selected];
7265 int key;
7266 int i;
7268 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7269 prompt, *selected + 1, size);
7270 if (item->hotkey)
7271 wprintw(status_win, "[%c] ", (char) item->hotkey);
7272 wprintw(status_win, "%s", item->text);
7273 wclrtoeol(status_win);
7275 key = get_input(COLS - 1);
7276 switch (key) {
7277 case KEY_RETURN:
7278 case KEY_ENTER:
7279 case '\n':
7280 status = INPUT_STOP;
7281 break;
7283 case KEY_LEFT:
7284 case KEY_UP:
7285 *selected = *selected - 1;
7286 if (*selected < 0)
7287 *selected = size - 1;
7288 break;
7290 case KEY_RIGHT:
7291 case KEY_DOWN:
7292 *selected = (*selected + 1) % size;
7293 break;
7295 case KEY_ESC:
7296 status = INPUT_CANCEL;
7297 break;
7299 default:
7300 for (i = 0; items[i].text; i++)
7301 if (items[i].hotkey == key) {
7302 *selected = i;
7303 status = INPUT_STOP;
7304 break;
7305 }
7306 }
7307 }
7309 /* Clear the status window */
7310 status_empty = FALSE;
7311 report("");
7313 return status != INPUT_CANCEL;
7314 }
7316 /*
7317 * Repository properties
7318 */
7320 static struct ref **refs = NULL;
7321 static size_t refs_size = 0;
7322 static struct ref *refs_head = NULL;
7324 static struct ref_list **ref_lists = NULL;
7325 static size_t ref_lists_size = 0;
7327 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7328 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7329 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7331 static int
7332 compare_refs(const void *ref1_, const void *ref2_)
7333 {
7334 const struct ref *ref1 = *(const struct ref **)ref1_;
7335 const struct ref *ref2 = *(const struct ref **)ref2_;
7337 if (ref1->tag != ref2->tag)
7338 return ref2->tag - ref1->tag;
7339 if (ref1->ltag != ref2->ltag)
7340 return ref2->ltag - ref2->ltag;
7341 if (ref1->head != ref2->head)
7342 return ref2->head - ref1->head;
7343 if (ref1->tracked != ref2->tracked)
7344 return ref2->tracked - ref1->tracked;
7345 if (ref1->remote != ref2->remote)
7346 return ref2->remote - ref1->remote;
7347 return strcmp(ref1->name, ref2->name);
7348 }
7350 static void
7351 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7352 {
7353 size_t i;
7355 for (i = 0; i < refs_size; i++)
7356 if (!visitor(data, refs[i]))
7357 break;
7358 }
7360 static struct ref *
7361 get_ref_head()
7362 {
7363 return refs_head;
7364 }
7366 static struct ref_list *
7367 get_ref_list(const char *id)
7368 {
7369 struct ref_list *list;
7370 size_t i;
7372 for (i = 0; i < ref_lists_size; i++)
7373 if (!strcmp(id, ref_lists[i]->id))
7374 return ref_lists[i];
7376 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7377 return NULL;
7378 list = calloc(1, sizeof(*list));
7379 if (!list)
7380 return NULL;
7382 for (i = 0; i < refs_size; i++) {
7383 if (!strcmp(id, refs[i]->id) &&
7384 realloc_refs_list(&list->refs, list->size, 1))
7385 list->refs[list->size++] = refs[i];
7386 }
7388 if (!list->refs) {
7389 free(list);
7390 return NULL;
7391 }
7393 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7394 ref_lists[ref_lists_size++] = list;
7395 return list;
7396 }
7398 static int
7399 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7400 {
7401 struct ref *ref = NULL;
7402 bool tag = FALSE;
7403 bool ltag = FALSE;
7404 bool remote = FALSE;
7405 bool tracked = FALSE;
7406 bool head = FALSE;
7407 int from = 0, to = refs_size - 1;
7409 if (!prefixcmp(name, "refs/tags/")) {
7410 if (!suffixcmp(name, namelen, "^{}")) {
7411 namelen -= 3;
7412 name[namelen] = 0;
7413 } else {
7414 ltag = TRUE;
7415 }
7417 tag = TRUE;
7418 namelen -= STRING_SIZE("refs/tags/");
7419 name += STRING_SIZE("refs/tags/");
7421 } else if (!prefixcmp(name, "refs/remotes/")) {
7422 remote = TRUE;
7423 namelen -= STRING_SIZE("refs/remotes/");
7424 name += STRING_SIZE("refs/remotes/");
7425 tracked = !strcmp(opt_remote, name);
7427 } else if (!prefixcmp(name, "refs/heads/")) {
7428 namelen -= STRING_SIZE("refs/heads/");
7429 name += STRING_SIZE("refs/heads/");
7430 if (!strncmp(opt_head, name, namelen))
7431 return OK;
7433 } else if (!strcmp(name, "HEAD")) {
7434 head = TRUE;
7435 if (*opt_head) {
7436 namelen = strlen(opt_head);
7437 name = opt_head;
7438 }
7439 }
7441 /* If we are reloading or it's an annotated tag, replace the
7442 * previous SHA1 with the resolved commit id; relies on the fact
7443 * git-ls-remote lists the commit id of an annotated tag right
7444 * before the commit id it points to. */
7445 while (from <= to) {
7446 size_t pos = (to + from) / 2;
7447 int cmp = strcmp(name, refs[pos]->name);
7449 if (!cmp) {
7450 ref = refs[pos];
7451 break;
7452 }
7454 if (cmp < 0)
7455 to = pos - 1;
7456 else
7457 from = pos + 1;
7458 }
7460 if (!ref) {
7461 if (!realloc_refs(&refs, refs_size, 1))
7462 return ERR;
7463 ref = calloc(1, sizeof(*ref) + namelen);
7464 if (!ref)
7465 return ERR;
7466 memmove(refs + from + 1, refs + from,
7467 (refs_size - from) * sizeof(*refs));
7468 refs[from] = ref;
7469 strncpy(ref->name, name, namelen);
7470 refs_size++;
7471 }
7473 ref->head = head;
7474 ref->tag = tag;
7475 ref->ltag = ltag;
7476 ref->remote = remote;
7477 ref->tracked = tracked;
7478 string_copy_rev(ref->id, id);
7480 if (head)
7481 refs_head = ref;
7482 return OK;
7483 }
7485 static int
7486 load_refs(void)
7487 {
7488 const char *head_argv[] = {
7489 "git", "symbolic-ref", "HEAD", NULL
7490 };
7491 static const char *ls_remote_argv[SIZEOF_ARG] = {
7492 "git", "ls-remote", opt_git_dir, NULL
7493 };
7494 static bool init = FALSE;
7495 size_t i;
7497 if (!init) {
7498 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7499 die("TIG_LS_REMOTE contains too many arguments");
7500 init = TRUE;
7501 }
7503 if (!*opt_git_dir)
7504 return OK;
7506 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7507 !prefixcmp(opt_head, "refs/heads/")) {
7508 char *offset = opt_head + STRING_SIZE("refs/heads/");
7510 memmove(opt_head, offset, strlen(offset) + 1);
7511 }
7513 refs_head = NULL;
7514 for (i = 0; i < refs_size; i++)
7515 refs[i]->id[0] = 0;
7517 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7518 return ERR;
7520 /* Update the ref lists to reflect changes. */
7521 for (i = 0; i < ref_lists_size; i++) {
7522 struct ref_list *list = ref_lists[i];
7523 size_t old, new;
7525 for (old = new = 0; old < list->size; old++)
7526 if (!strcmp(list->id, list->refs[old]->id))
7527 list->refs[new++] = list->refs[old];
7528 list->size = new;
7529 }
7531 return OK;
7532 }
7534 static void
7535 set_remote_branch(const char *name, const char *value, size_t valuelen)
7536 {
7537 if (!strcmp(name, ".remote")) {
7538 string_ncopy(opt_remote, value, valuelen);
7540 } else if (*opt_remote && !strcmp(name, ".merge")) {
7541 size_t from = strlen(opt_remote);
7543 if (!prefixcmp(value, "refs/heads/"))
7544 value += STRING_SIZE("refs/heads/");
7546 if (!string_format_from(opt_remote, &from, "/%s", value))
7547 opt_remote[0] = 0;
7548 }
7549 }
7551 static void
7552 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7553 {
7554 const char *argv[SIZEOF_ARG] = { name, "=" };
7555 int argc = 1 + (cmd == option_set_command);
7556 int error = ERR;
7558 if (!argv_from_string(argv, &argc, value))
7559 config_msg = "Too many option arguments";
7560 else
7561 error = cmd(argc, argv);
7563 if (error == ERR)
7564 warn("Option 'tig.%s': %s", name, config_msg);
7565 }
7567 static bool
7568 set_environment_variable(const char *name, const char *value)
7569 {
7570 size_t len = strlen(name) + 1 + strlen(value) + 1;
7571 char *env = malloc(len);
7573 if (env &&
7574 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7575 putenv(env) == 0)
7576 return TRUE;
7577 free(env);
7578 return FALSE;
7579 }
7581 static void
7582 set_work_tree(const char *value)
7583 {
7584 char cwd[SIZEOF_STR];
7586 if (!getcwd(cwd, sizeof(cwd)))
7587 die("Failed to get cwd path: %s", strerror(errno));
7588 if (chdir(opt_git_dir) < 0)
7589 die("Failed to chdir(%s): %s", strerror(errno));
7590 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7591 die("Failed to get git path: %s", strerror(errno));
7592 if (chdir(cwd) < 0)
7593 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7594 if (chdir(value) < 0)
7595 die("Failed to chdir(%s): %s", value, strerror(errno));
7596 if (!getcwd(cwd, sizeof(cwd)))
7597 die("Failed to get cwd path: %s", strerror(errno));
7598 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7599 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7600 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7601 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7602 opt_is_inside_work_tree = TRUE;
7603 }
7605 static int
7606 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7607 {
7608 if (!strcmp(name, "i18n.commitencoding"))
7609 string_ncopy(opt_encoding, value, valuelen);
7611 else if (!strcmp(name, "core.editor"))
7612 string_ncopy(opt_editor, value, valuelen);
7614 else if (!strcmp(name, "core.worktree"))
7615 set_work_tree(value);
7617 else if (!prefixcmp(name, "tig.color."))
7618 set_repo_config_option(name + 10, value, option_color_command);
7620 else if (!prefixcmp(name, "tig.bind."))
7621 set_repo_config_option(name + 9, value, option_bind_command);
7623 else if (!prefixcmp(name, "tig."))
7624 set_repo_config_option(name + 4, value, option_set_command);
7626 else if (*opt_head && !prefixcmp(name, "branch.") &&
7627 !strncmp(name + 7, opt_head, strlen(opt_head)))
7628 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7630 return OK;
7631 }
7633 static int
7634 load_git_config(void)
7635 {
7636 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7638 return io_run_load(config_list_argv, "=", read_repo_config_option);
7639 }
7641 static int
7642 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7643 {
7644 if (!opt_git_dir[0]) {
7645 string_ncopy(opt_git_dir, name, namelen);
7647 } else if (opt_is_inside_work_tree == -1) {
7648 /* This can be 3 different values depending on the
7649 * version of git being used. If git-rev-parse does not
7650 * understand --is-inside-work-tree it will simply echo
7651 * the option else either "true" or "false" is printed.
7652 * Default to true for the unknown case. */
7653 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7655 } else if (*name == '.') {
7656 string_ncopy(opt_cdup, name, namelen);
7658 } else {
7659 string_ncopy(opt_prefix, name, namelen);
7660 }
7662 return OK;
7663 }
7665 static int
7666 load_repo_info(void)
7667 {
7668 const char *rev_parse_argv[] = {
7669 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7670 "--show-cdup", "--show-prefix", NULL
7671 };
7673 return io_run_load(rev_parse_argv, "=", read_repo_info);
7674 }
7677 /*
7678 * Main
7679 */
7681 static const char usage[] =
7682 "tig " TIG_VERSION " (" __DATE__ ")\n"
7683 "\n"
7684 "Usage: tig [options] [revs] [--] [paths]\n"
7685 " or: tig show [options] [revs] [--] [paths]\n"
7686 " or: tig blame [rev] path\n"
7687 " or: tig status\n"
7688 " or: tig < [git command output]\n"
7689 "\n"
7690 "Options:\n"
7691 " -v, --version Show version and exit\n"
7692 " -h, --help Show help message and exit";
7694 static void __NORETURN
7695 quit(int sig)
7696 {
7697 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7698 if (cursed)
7699 endwin();
7700 exit(0);
7701 }
7703 static void __NORETURN
7704 die(const char *err, ...)
7705 {
7706 va_list args;
7708 endwin();
7710 va_start(args, err);
7711 fputs("tig: ", stderr);
7712 vfprintf(stderr, err, args);
7713 fputs("\n", stderr);
7714 va_end(args);
7716 exit(1);
7717 }
7719 static void
7720 warn(const char *msg, ...)
7721 {
7722 va_list args;
7724 va_start(args, msg);
7725 fputs("tig warning: ", stderr);
7726 vfprintf(stderr, msg, args);
7727 fputs("\n", stderr);
7728 va_end(args);
7729 }
7731 static const char ***filter_args;
7733 static int
7734 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7735 {
7736 return argv_append(filter_args, name) ? OK : ERR;
7737 }
7739 static void
7740 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7741 {
7742 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7743 const char **all_argv = NULL;
7745 filter_args = args;
7746 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7747 !argv_append_array(&all_argv, argv) ||
7748 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7749 die("Failed to split arguments");
7750 argv_free(all_argv);
7751 free(all_argv);
7752 }
7754 static void
7755 filter_options(const char *argv[])
7756 {
7757 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7758 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7759 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7760 }
7762 static enum request
7763 parse_options(int argc, const char *argv[])
7764 {
7765 enum request request = REQ_VIEW_MAIN;
7766 const char *subcommand;
7767 bool seen_dashdash = FALSE;
7768 const char **filter_argv = NULL;
7769 int i;
7771 if (!isatty(STDIN_FILENO)) {
7772 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7773 return REQ_VIEW_PAGER;
7774 }
7776 if (argc <= 1)
7777 return REQ_VIEW_MAIN;
7779 subcommand = argv[1];
7780 if (!strcmp(subcommand, "status")) {
7781 if (argc > 2)
7782 warn("ignoring arguments after `%s'", subcommand);
7783 return REQ_VIEW_STATUS;
7785 } else if (!strcmp(subcommand, "blame")) {
7786 if (argc <= 2 || argc > 4)
7787 die("invalid number of options to blame\n\n%s", usage);
7789 i = 2;
7790 if (argc == 4) {
7791 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7792 i++;
7793 }
7795 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7796 return REQ_VIEW_BLAME;
7798 } else if (!strcmp(subcommand, "show")) {
7799 request = REQ_VIEW_DIFF;
7801 } else {
7802 subcommand = NULL;
7803 }
7805 for (i = 1 + !!subcommand; i < argc; i++) {
7806 const char *opt = argv[i];
7808 if (seen_dashdash) {
7809 argv_append(&opt_file_args, opt);
7810 continue;
7812 } else if (!strcmp(opt, "--")) {
7813 seen_dashdash = TRUE;
7814 continue;
7816 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7817 printf("tig version %s\n", TIG_VERSION);
7818 quit(0);
7820 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7821 printf("%s\n", usage);
7822 quit(0);
7824 } else if (!strcmp(opt, "--all")) {
7825 argv_append(&opt_rev_args, opt);
7826 continue;
7827 }
7829 if (!argv_append(&filter_argv, opt))
7830 die("command too long");
7831 }
7833 if (filter_argv)
7834 filter_options(filter_argv);
7836 return request;
7837 }
7839 int
7840 main(int argc, const char *argv[])
7841 {
7842 const char *codeset = "UTF-8";
7843 enum request request = parse_options(argc, argv);
7844 struct view *view;
7845 size_t i;
7847 signal(SIGINT, quit);
7848 signal(SIGPIPE, SIG_IGN);
7850 if (setlocale(LC_ALL, "")) {
7851 codeset = nl_langinfo(CODESET);
7852 }
7854 if (load_repo_info() == ERR)
7855 die("Failed to load repo info.");
7857 if (load_options() == ERR)
7858 die("Failed to load user config.");
7860 if (load_git_config() == ERR)
7861 die("Failed to load repo config.");
7863 /* Require a git repository unless when running in pager mode. */
7864 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7865 die("Not a git repository");
7867 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7868 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7869 if (opt_iconv_in == ICONV_NONE)
7870 die("Failed to initialize character set conversion");
7871 }
7873 if (codeset && strcmp(codeset, "UTF-8")) {
7874 opt_iconv_out = iconv_open(codeset, "UTF-8");
7875 if (opt_iconv_out == ICONV_NONE)
7876 die("Failed to initialize character set conversion");
7877 }
7879 if (load_refs() == ERR)
7880 die("Failed to load refs.");
7882 foreach_view (view, i) {
7883 if (getenv(view->cmd_env))
7884 warn("Use of the %s environment variable is deprecated,"
7885 " use options or TIG_DIFF_ARGS instead",
7886 view->cmd_env);
7887 if (!argv_from_env(view->ops->argv, view->cmd_env))
7888 die("Too many arguments in the `%s` environment variable",
7889 view->cmd_env);
7890 }
7892 init_display();
7894 while (view_driver(display[current_view], request)) {
7895 int key = get_input(0);
7897 view = display[current_view];
7898 request = get_keybinding(view->keymap, key);
7900 /* Some low-level request handling. This keeps access to
7901 * status_win restricted. */
7902 switch (request) {
7903 case REQ_NONE:
7904 report("Unknown key, press %s for help",
7905 get_key(view->keymap, REQ_VIEW_HELP));
7906 break;
7907 case REQ_PROMPT:
7908 {
7909 char *cmd = read_prompt(":");
7911 if (cmd && isdigit(*cmd)) {
7912 int lineno = view->lineno + 1;
7914 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7915 select_view_line(view, lineno - 1);
7916 report("");
7917 } else {
7918 report("Unable to parse '%s' as a line number", cmd);
7919 }
7921 } else if (cmd) {
7922 struct view *next = VIEW(REQ_VIEW_PAGER);
7923 const char *argv[SIZEOF_ARG] = { "git" };
7924 int argc = 1;
7926 /* When running random commands, initially show the
7927 * command in the title. However, it maybe later be
7928 * overwritten if a commit line is selected. */
7929 string_ncopy(next->ref, cmd, strlen(cmd));
7931 if (!argv_from_string(argv, &argc, cmd)) {
7932 report("Too many arguments");
7933 } else if (!prepare_update(next, argv, NULL)) {
7934 report("Failed to format command");
7935 } else {
7936 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7937 }
7938 }
7940 request = REQ_NONE;
7941 break;
7942 }
7943 case REQ_SEARCH:
7944 case REQ_SEARCH_BACK:
7945 {
7946 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7947 char *search = read_prompt(prompt);
7949 if (search)
7950 string_ncopy(opt_search, search, strlen(search));
7951 else if (*opt_search)
7952 request = request == REQ_SEARCH ?
7953 REQ_FIND_NEXT :
7954 REQ_FIND_PREV;
7955 else
7956 request = REQ_NONE;
7957 break;
7958 }
7959 default:
7960 break;
7961 }
7962 }
7964 quit(0);
7966 return 0;
7967 }