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 format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_NONE /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151 INPUT_OK,
152 INPUT_SKIP,
153 INPUT_STOP,
154 INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163 int hotkey;
164 const char *text;
165 void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171 * Allocation helpers ... Entering macro hell to never be seen again.
172 */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
175 static type * \
176 name(type **mem, size_t size, size_t increase) \
177 { \
178 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
179 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180 type *tmp = *mem; \
181 \
182 if (mem == NULL || num_chunks != num_chunks_new) { \
183 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184 if (tmp) \
185 *mem = tmp; \
186 } \
187 \
188 return tmp; \
189 }
191 /*
192 * String helpers
193 */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
197 {
198 if (srclen > dstlen - 1)
199 srclen = dstlen - 1;
201 strncpy(dst, src, srclen);
202 dst[srclen] = 0;
203 }
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211 string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
221 {
222 size_t size, pos;
224 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225 if (src[pos] == '\t') {
226 size_t expanded = tabsize - (size % tabsize);
228 if (expanded + size >= dstlen - 1)
229 expanded = dstlen - size - 1;
230 memcpy(dst + size, " ", expanded);
231 size += expanded;
232 } else {
233 dst[size++] = src[pos];
234 }
235 }
237 dst[size] = 0;
238 }
240 static char *
241 chomp_string(char *name)
242 {
243 int namelen;
245 while (isspace(*name))
246 name++;
248 namelen = strlen(name) - 1;
249 while (namelen > 0 && isspace(name[namelen]))
250 name[namelen--] = 0;
252 return name;
253 }
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
257 {
258 va_list args;
259 size_t pos = bufpos ? *bufpos : 0;
261 va_start(args, fmt);
262 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 va_end(args);
265 if (bufpos)
266 *bufpos = pos;
268 return pos >= bufsize ? FALSE : TRUE;
269 }
271 #define string_format(buf, fmt, args...) \
272 string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275 string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
279 {
280 size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284 /* Diff-Header == DIFF_HEADER */
285 for (i = 0; i < len; i++) {
286 if (toupper(str1[i]) == toupper(str2[i]))
287 continue;
289 if (string_enum_sep(str1[i]) &&
290 string_enum_sep(str2[i]))
291 continue;
293 return str1[i] - str2[i];
294 }
296 return 0;
297 }
299 #define enum_equals(entry, str, len) \
300 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303 const char *name;
304 int namelen;
305 int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
312 {
313 static char buf[SIZEOF_STR];
314 int bufpos;
316 for (bufpos = 0; bufpos <= namelen; bufpos++) {
317 buf[bufpos] = tolower(name[bufpos]);
318 if (buf[bufpos] == '_')
319 buf[bufpos] = '-';
320 }
322 buf[bufpos] = 0;
323 return buf;
324 }
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
330 {
331 size_t namelen = strlen(name);
332 int i;
334 for (i = 0; i < map_size; i++)
335 if (enum_equals(map[i], name, namelen)) {
336 *value = map[i].value;
337 return TRUE;
338 }
340 return FALSE;
341 }
343 #define map_enum(attr, map, name) \
344 map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347 strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
351 {
352 size_t len = slen >= 0 ? slen : strlen(str);
353 size_t suffixlen = strlen(suffix);
355 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
356 }
359 /*
360 * Unicode / UTF-8 handling
361 *
362 * NOTE: Much of the following code for dealing with Unicode is derived from
363 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365 */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
369 {
370 if (c >= 0x1100 &&
371 (c <= 0x115f /* Hangul Jamo */
372 || c == 0x2329
373 || c == 0x232a
374 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
375 /* CJK ... Yi */
376 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
377 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
378 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
379 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
380 || (c >= 0xffe0 && c <= 0xffe6)
381 || (c >= 0x20000 && c <= 0x2fffd)
382 || (c >= 0x30000 && c <= 0x3fffd)))
383 return 2;
385 if (c == '\t')
386 return tab_size;
388 return 1;
389 }
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392 * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 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,
396 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,
397 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,
398 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,
399 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,
400 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,
401 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,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
406 {
407 int c = *(unsigned char *) string;
409 return utf8_bytes[c];
410 }
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
415 {
416 unsigned long unicode;
418 switch (length) {
419 case 1:
420 unicode = string[0];
421 break;
422 case 2:
423 unicode = (string[0] & 0x1f) << 6;
424 unicode += (string[1] & 0x3f);
425 break;
426 case 3:
427 unicode = (string[0] & 0x0f) << 12;
428 unicode += ((string[1] & 0x3f) << 6);
429 unicode += (string[2] & 0x3f);
430 break;
431 case 4:
432 unicode = (string[0] & 0x0f) << 18;
433 unicode += ((string[1] & 0x3f) << 12);
434 unicode += ((string[2] & 0x3f) << 6);
435 unicode += (string[3] & 0x3f);
436 break;
437 case 5:
438 unicode = (string[0] & 0x0f) << 24;
439 unicode += ((string[1] & 0x3f) << 18);
440 unicode += ((string[2] & 0x3f) << 12);
441 unicode += ((string[3] & 0x3f) << 6);
442 unicode += (string[4] & 0x3f);
443 break;
444 case 6:
445 unicode = (string[0] & 0x01) << 30;
446 unicode += ((string[1] & 0x3f) << 24);
447 unicode += ((string[2] & 0x3f) << 18);
448 unicode += ((string[3] & 0x3f) << 12);
449 unicode += ((string[4] & 0x3f) << 6);
450 unicode += (string[5] & 0x3f);
451 break;
452 default:
453 return 0;
454 }
456 /* Invalid characters could return the special 0xfffd value but NUL
457 * should be just as good. */
458 return unicode > 0xffff ? 0 : unicode;
459 }
461 /* Calculates how much of string can be shown within the given maximum width
462 * and sets trimmed parameter to non-zero value if all of string could not be
463 * shown. If the reserve flag is TRUE, it will reserve at least one
464 * trailing character, which can be useful when drawing a delimiter.
465 *
466 * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
469 {
470 const char *string = *start;
471 const char *end = strchr(string, '\0');
472 unsigned char last_bytes = 0;
473 size_t last_ucwidth = 0;
475 *width = 0;
476 *trimmed = 0;
478 while (string < end) {
479 unsigned char bytes = utf8_char_length(string, end);
480 size_t ucwidth;
481 unsigned long unicode;
483 if (string + bytes > end)
484 break;
486 /* Change representation to figure out whether
487 * it is a single- or double-width character. */
489 unicode = utf8_to_unicode(string, bytes);
490 /* FIXME: Graceful handling of invalid Unicode character. */
491 if (!unicode)
492 break;
494 ucwidth = unicode_width(unicode, tab_size);
495 if (skip > 0) {
496 skip -= ucwidth <= skip ? ucwidth : skip;
497 *start += bytes;
498 }
499 *width += ucwidth;
500 if (*width > max_width) {
501 *trimmed = 1;
502 *width -= ucwidth;
503 if (reserve && *width == max_width) {
504 string -= last_bytes;
505 *width -= last_ucwidth;
506 }
507 break;
508 }
510 string += bytes;
511 last_bytes = ucwidth ? bytes : 0;
512 last_ucwidth = ucwidth;
513 }
515 return string - *start;
516 }
519 #define DATE_INFO \
520 DATE_(NO), \
521 DATE_(DEFAULT), \
522 DATE_(RELATIVE), \
523 DATE_(SHORT)
525 enum date {
526 #define DATE_(name) DATE_##name
527 DATE_INFO
528 #undef DATE_
529 };
531 static const struct enum_map date_map[] = {
532 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
533 DATE_INFO
534 #undef DATE_
535 };
537 struct time {
538 time_t sec;
539 int tz;
540 };
542 static inline int timecmp(const struct time *t1, const struct time *t2)
543 {
544 return t1->sec - t2->sec;
545 }
547 static const char *
548 mkdate(const struct time *time, enum date date)
549 {
550 static char buf[DATE_COLS + 1];
551 static const struct enum_map reldate[] = {
552 { "second", 1, 60 * 2 },
553 { "minute", 60, 60 * 60 * 2 },
554 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
555 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
556 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
557 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
558 };
559 struct tm tm;
561 if (!date || !time || !time->sec)
562 return "";
564 if (date == DATE_RELATIVE) {
565 struct timeval now;
566 time_t date = time->sec + time->tz;
567 time_t seconds;
568 int i;
570 gettimeofday(&now, NULL);
571 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
572 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
573 if (seconds >= reldate[i].value)
574 continue;
576 seconds /= reldate[i].namelen;
577 if (!string_format(buf, "%ld %s%s %s",
578 seconds, reldate[i].name,
579 seconds > 1 ? "s" : "",
580 now.tv_sec >= date ? "ago" : "ahead"))
581 break;
582 return buf;
583 }
584 }
586 gmtime_r(&time->sec, &tm);
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 }
675 /*
676 * Executing external commands.
677 */
679 enum io_type {
680 IO_FD, /* File descriptor based IO. */
681 IO_BG, /* Execute command in the background. */
682 IO_FG, /* Execute command with same std{in,out,err}. */
683 IO_RD, /* Read only fork+exec IO. */
684 IO_WR, /* Write only fork+exec IO. */
685 IO_AP, /* Append fork+exec output to file. */
686 };
688 struct io {
689 enum io_type type; /* The requested type of pipe. */
690 const char *dir; /* Directory from which to execute. */
691 pid_t pid; /* PID of spawned process. */
692 int pipe; /* Pipe end for reading or writing. */
693 int error; /* Error status. */
694 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
695 char *buf; /* Read buffer. */
696 size_t bufalloc; /* Allocated buffer size. */
697 size_t bufsize; /* Buffer content size. */
698 char *bufpos; /* Current buffer position. */
699 unsigned int eof:1; /* Has end of file been reached. */
700 };
702 static void
703 io_reset(struct io *io)
704 {
705 io->pipe = -1;
706 io->pid = 0;
707 io->buf = io->bufpos = NULL;
708 io->bufalloc = io->bufsize = 0;
709 io->error = 0;
710 io->eof = 0;
711 }
713 static void
714 io_init(struct io *io, const char *dir, enum io_type type)
715 {
716 io_reset(io);
717 io->type = type;
718 io->dir = dir;
719 }
721 static bool
722 io_format(struct io *io, const char *dir, enum io_type type,
723 const char *argv[], enum format_flags flags)
724 {
725 io_init(io, dir, type);
726 return format_argv(io->argv, argv, flags);
727 }
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
731 {
732 char name[SIZEOF_STR] = "";
733 bool fits;
734 va_list args;
736 io_init(io, NULL, IO_FD);
738 va_start(args, fmt);
739 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740 va_end(args);
742 if (!fits) {
743 io->error = ENAMETOOLONG;
744 return FALSE;
745 }
746 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747 if (io->pipe == -1)
748 io->error = errno;
749 return io->pipe != -1;
750 }
752 static bool
753 io_kill(struct io *io)
754 {
755 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
756 }
758 static bool
759 io_done(struct io *io)
760 {
761 pid_t pid = io->pid;
763 if (io->pipe != -1)
764 close(io->pipe);
765 free(io->buf);
766 io_reset(io);
768 while (pid > 0) {
769 int status;
770 pid_t waiting = waitpid(pid, &status, 0);
772 if (waiting < 0) {
773 if (errno == EINTR)
774 continue;
775 io->error = errno;
776 return FALSE;
777 }
779 return waiting == pid &&
780 !WIFSIGNALED(status) &&
781 WIFEXITED(status) &&
782 !WEXITSTATUS(status);
783 }
785 return TRUE;
786 }
788 static bool
789 io_start(struct io *io)
790 {
791 int pipefds[2] = { -1, -1 };
793 if (io->type == IO_FD)
794 return TRUE;
796 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
797 io->error = errno;
798 return FALSE;
799 } else if (io->type == IO_AP) {
800 pipefds[1] = io->pipe;
801 }
803 if ((io->pid = fork())) {
804 if (io->pid == -1)
805 io->error = errno;
806 if (pipefds[!(io->type == IO_WR)] != -1)
807 close(pipefds[!(io->type == IO_WR)]);
808 if (io->pid != -1) {
809 io->pipe = pipefds[!!(io->type == IO_WR)];
810 return TRUE;
811 }
813 } else {
814 if (io->type != IO_FG) {
815 int devnull = open("/dev/null", O_RDWR);
816 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
817 int writefd = (io->type == IO_RD || io->type == IO_AP)
818 ? pipefds[1] : devnull;
820 dup2(readfd, STDIN_FILENO);
821 dup2(writefd, STDOUT_FILENO);
822 dup2(devnull, STDERR_FILENO);
824 close(devnull);
825 if (pipefds[0] != -1)
826 close(pipefds[0]);
827 if (pipefds[1] != -1)
828 close(pipefds[1]);
829 }
831 if (io->dir && *io->dir && chdir(io->dir) == -1)
832 exit(errno);
834 execvp(io->argv[0], (char *const*) io->argv);
835 exit(errno);
836 }
838 if (pipefds[!!(io->type == IO_WR)] != -1)
839 close(pipefds[!!(io->type == IO_WR)]);
840 return FALSE;
841 }
843 static bool
844 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
845 {
846 io_init(io, dir, type);
847 if (!format_argv(io->argv, argv, FORMAT_NONE))
848 return FALSE;
849 return io_start(io);
850 }
852 static int
853 io_complete(struct io *io)
854 {
855 return io_start(io) && io_done(io);
856 }
858 static int
859 io_run_bg(const char **argv)
860 {
861 struct io io = {};
863 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
864 return FALSE;
865 return io_complete(&io);
866 }
868 static bool
869 io_run_fg(const char **argv, const char *dir)
870 {
871 struct io io = {};
873 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
874 return FALSE;
875 return io_complete(&io);
876 }
878 static bool
879 io_run_append(const char **argv, enum format_flags flags, int fd)
880 {
881 struct io io = {};
883 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
884 close(fd);
885 return FALSE;
886 }
888 io.pipe = fd;
889 return io_complete(&io);
890 }
892 static bool
893 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
894 {
895 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
896 }
898 static bool
899 io_eof(struct io *io)
900 {
901 return io->eof;
902 }
904 static int
905 io_error(struct io *io)
906 {
907 return io->error;
908 }
910 static char *
911 io_strerror(struct io *io)
912 {
913 return strerror(io->error);
914 }
916 static bool
917 io_can_read(struct io *io)
918 {
919 struct timeval tv = { 0, 500 };
920 fd_set fds;
922 FD_ZERO(&fds);
923 FD_SET(io->pipe, &fds);
925 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
926 }
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
930 {
931 do {
932 ssize_t readsize = read(io->pipe, buf, bufsize);
934 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935 continue;
936 else if (readsize == -1)
937 io->error = errno;
938 else if (readsize == 0)
939 io->eof = 1;
940 return readsize;
941 } while (1);
942 }
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
948 {
949 char *eol;
950 ssize_t readsize;
952 while (TRUE) {
953 if (io->bufsize > 0) {
954 eol = memchr(io->bufpos, c, io->bufsize);
955 if (eol) {
956 char *line = io->bufpos;
958 *eol = 0;
959 io->bufpos = eol + 1;
960 io->bufsize -= io->bufpos - line;
961 return line;
962 }
963 }
965 if (io_eof(io)) {
966 if (io->bufsize) {
967 io->bufpos[io->bufsize] = 0;
968 io->bufsize = 0;
969 return io->bufpos;
970 }
971 return NULL;
972 }
974 if (!can_read)
975 return NULL;
977 if (io->bufsize > 0 && io->bufpos > io->buf)
978 memmove(io->buf, io->bufpos, io->bufsize);
980 if (io->bufalloc == io->bufsize) {
981 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982 return NULL;
983 io->bufalloc += BUFSIZ;
984 }
986 io->bufpos = io->buf;
987 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988 if (io_error(io))
989 return NULL;
990 io->bufsize += readsize;
991 }
992 }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
996 {
997 size_t written = 0;
999 while (!io_error(io) && written < bufsize) {
1000 ssize_t size;
1002 size = write(io->pipe, buf + written, bufsize - written);
1003 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004 continue;
1005 else if (size == -1)
1006 io->error = errno;
1007 else
1008 written += size;
1009 }
1011 return written == bufsize;
1012 }
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1016 {
1017 char *result = io_get(io, '\n', TRUE);
1019 if (result) {
1020 result = chomp_string(result);
1021 string_ncopy_do(buf, bufsize, result, strlen(result));
1022 }
1024 return io_done(io) && result;
1025 }
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1029 {
1030 struct io io = {};
1032 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1033 && io_read_buf(&io, buf, bufsize);
1034 }
1036 static int
1037 io_load(struct io *io, const char *separators,
1038 int (*read_property)(char *, size_t, char *, size_t))
1039 {
1040 char *name;
1041 int state = OK;
1043 if (!io_start(io))
1044 return ERR;
1046 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047 char *value;
1048 size_t namelen;
1049 size_t valuelen;
1051 name = chomp_string(name);
1052 namelen = strcspn(name, separators);
1054 if (name[namelen]) {
1055 name[namelen] = 0;
1056 value = chomp_string(name + namelen + 1);
1057 valuelen = strlen(value);
1059 } else {
1060 value = "";
1061 valuelen = 0;
1062 }
1064 state = read_property(name, namelen, value, valuelen);
1065 }
1067 if (state != ERR && io_error(io))
1068 state = ERR;
1069 io_done(io);
1071 return state;
1072 }
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076 int (*read_property)(char *, size_t, char *, size_t))
1077 {
1078 struct io io = {};
1080 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1081 ? io_load(&io, separators, read_property) : ERR;
1082 }
1085 /*
1086 * User requests
1087 */
1089 #define REQ_INFO \
1090 /* XXX: Keep the view request first and in sync with views[]. */ \
1091 REQ_GROUP("View switching") \
1092 REQ_(VIEW_MAIN, "Show main view"), \
1093 REQ_(VIEW_DIFF, "Show diff view"), \
1094 REQ_(VIEW_LOG, "Show log view"), \
1095 REQ_(VIEW_TREE, "Show tree view"), \
1096 REQ_(VIEW_BLOB, "Show blob view"), \
1097 REQ_(VIEW_BLAME, "Show blame view"), \
1098 REQ_(VIEW_BRANCH, "Show branch view"), \
1099 REQ_(VIEW_HELP, "Show help page"), \
1100 REQ_(VIEW_PAGER, "Show pager view"), \
1101 REQ_(VIEW_STATUS, "Show status view"), \
1102 REQ_(VIEW_STAGE, "Show stage view"), \
1103 \
1104 REQ_GROUP("View manipulation") \
1105 REQ_(ENTER, "Enter current line and scroll"), \
1106 REQ_(NEXT, "Move to next"), \
1107 REQ_(PREVIOUS, "Move to previous"), \
1108 REQ_(PARENT, "Move to parent"), \
1109 REQ_(VIEW_NEXT, "Move focus to next view"), \
1110 REQ_(REFRESH, "Reload and refresh"), \
1111 REQ_(MAXIMIZE, "Maximize the current view"), \
1112 REQ_(VIEW_CLOSE, "Close the current view"), \
1113 REQ_(QUIT, "Close all views and quit"), \
1114 \
1115 REQ_GROUP("View specific requests") \
1116 REQ_(STATUS_UPDATE, "Update file status"), \
1117 REQ_(STATUS_REVERT, "Revert file changes"), \
1118 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1119 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1120 \
1121 REQ_GROUP("Cursor navigation") \
1122 REQ_(MOVE_UP, "Move cursor one line up"), \
1123 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1124 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1125 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1126 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1127 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1128 \
1129 REQ_GROUP("Scrolling") \
1130 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1131 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1132 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1133 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1134 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1135 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1136 \
1137 REQ_GROUP("Searching") \
1138 REQ_(SEARCH, "Search the view"), \
1139 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1140 REQ_(FIND_NEXT, "Find next search match"), \
1141 REQ_(FIND_PREV, "Find previous search match"), \
1142 \
1143 REQ_GROUP("Option manipulation") \
1144 REQ_(OPTIONS, "Open option menu"), \
1145 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1146 REQ_(TOGGLE_DATE, "Toggle date display"), \
1147 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1149 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1150 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1151 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153 \
1154 REQ_GROUP("Misc") \
1155 REQ_(PROMPT, "Bring up the prompt"), \
1156 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1157 REQ_(SHOW_VERSION, "Show version information"), \
1158 REQ_(STOP_LOADING, "Stop all loading views"), \
1159 REQ_(EDIT, "Open in editor"), \
1160 REQ_(NONE, "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168 /* Offset all requests to avoid conflicts with ncurses getch values. */
1169 REQ_UNKNOWN = KEY_MAX + 1,
1170 REQ_OFFSET,
1171 REQ_INFO
1173 #undef REQ_GROUP
1174 #undef REQ_
1175 };
1177 struct request_info {
1178 enum request request;
1179 const char *name;
1180 int namelen;
1181 const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187 REQ_INFO
1188 #undef REQ_GROUP
1189 #undef REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1194 {
1195 int namelen = strlen(name);
1196 int i;
1198 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199 if (enum_equals(req_info[i], name, namelen))
1200 return req_info[i].request;
1202 return REQ_UNKNOWN;
1203 }
1206 /*
1207 * Options
1208 */
1210 /* Option and state variables. */
1211 static enum date opt_date = DATE_DEFAULT;
1212 static enum author opt_author = AUTHOR_DEFAULT;
1213 static bool opt_line_number = FALSE;
1214 static bool opt_line_graphics = TRUE;
1215 static bool opt_rev_graph = FALSE;
1216 static bool opt_show_refs = TRUE;
1217 static int opt_num_interval = 5;
1218 static double opt_hscroll = 0.50;
1219 static double opt_scale_split_view = 2.0 / 3.0;
1220 static int opt_tab_size = 8;
1221 static int opt_author_cols = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR] = "";
1223 static char opt_file[SIZEOF_STR] = "";
1224 static char opt_ref[SIZEOF_REF] = "";
1225 static char opt_head[SIZEOF_REF] = "";
1226 static char opt_remote[SIZEOF_REF] = "";
1227 static char opt_encoding[20] = "UTF-8";
1228 static iconv_t opt_iconv_in = ICONV_NONE;
1229 static iconv_t opt_iconv_out = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR] = "";
1231 static char opt_cdup[SIZEOF_STR] = "";
1232 static char opt_prefix[SIZEOF_STR] = "";
1233 static char opt_git_dir[SIZEOF_STR] = "";
1234 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR] = "";
1236 static FILE *opt_tty = NULL;
1238 #define is_initial_commit() (!get_ref_head())
1239 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243 * Line-oriented content detection.
1244 */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1261 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1262 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1263 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1268 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1269 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1272 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1273 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1276 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1277 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1278 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1279 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1280 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1281 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1282 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1283 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1284 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1285 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1287 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1289 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1290 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1293 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1294 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1295 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1296 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1299 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1302 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1303 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1305 enum line_type {
1306 #define LINE(type, line, fg, bg, attr) \
1307 LINE_##type
1308 LINE_INFO,
1309 LINE_NONE
1310 #undef LINE
1311 };
1313 struct line_info {
1314 const char *name; /* Option name. */
1315 int namelen; /* Size of option name. */
1316 const char *line; /* The start of line to match. */
1317 int linelen; /* Size of string to match. */
1318 int fg, bg, attr; /* Color and text attributes for the lines. */
1319 };
1321 static struct line_info line_info[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1324 LINE_INFO
1325 #undef LINE
1326 };
1328 static enum line_type
1329 get_line_type(const char *line)
1330 {
1331 int linelen = strlen(line);
1332 enum line_type type;
1334 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1335 /* Case insensitive search matches Signed-off-by lines better. */
1336 if (linelen >= line_info[type].linelen &&
1337 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1338 return type;
1340 return LINE_DEFAULT;
1341 }
1343 static inline int
1344 get_line_attr(enum line_type type)
1345 {
1346 assert(type < ARRAY_SIZE(line_info));
1347 return COLOR_PAIR(type) | line_info[type].attr;
1348 }
1350 static struct line_info *
1351 get_line_info(const char *name)
1352 {
1353 size_t namelen = strlen(name);
1354 enum line_type type;
1356 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1357 if (enum_equals(line_info[type], name, namelen))
1358 return &line_info[type];
1360 return NULL;
1361 }
1363 static void
1364 init_colors(void)
1365 {
1366 int default_bg = line_info[LINE_DEFAULT].bg;
1367 int default_fg = line_info[LINE_DEFAULT].fg;
1368 enum line_type type;
1370 start_color();
1372 if (assume_default_colors(default_fg, default_bg) == ERR) {
1373 default_bg = COLOR_BLACK;
1374 default_fg = COLOR_WHITE;
1375 }
1377 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1378 struct line_info *info = &line_info[type];
1379 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1380 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1382 init_pair(type, fg, bg);
1383 }
1384 }
1386 struct line {
1387 enum line_type type;
1389 /* State flags */
1390 unsigned int selected:1;
1391 unsigned int dirty:1;
1392 unsigned int cleareol:1;
1393 unsigned int other:16;
1395 void *data; /* User data */
1396 };
1399 /*
1400 * Keys
1401 */
1403 struct keybinding {
1404 int alias;
1405 enum request request;
1406 };
1408 static struct keybinding default_keybindings[] = {
1409 /* View switching */
1410 { 'm', REQ_VIEW_MAIN },
1411 { 'd', REQ_VIEW_DIFF },
1412 { 'l', REQ_VIEW_LOG },
1413 { 't', REQ_VIEW_TREE },
1414 { 'f', REQ_VIEW_BLOB },
1415 { 'B', REQ_VIEW_BLAME },
1416 { 'H', REQ_VIEW_BRANCH },
1417 { 'p', REQ_VIEW_PAGER },
1418 { 'h', REQ_VIEW_HELP },
1419 { 'S', REQ_VIEW_STATUS },
1420 { 'c', REQ_VIEW_STAGE },
1422 /* View manipulation */
1423 { 'q', REQ_VIEW_CLOSE },
1424 { KEY_TAB, REQ_VIEW_NEXT },
1425 { KEY_RETURN, REQ_ENTER },
1426 { KEY_UP, REQ_PREVIOUS },
1427 { KEY_DOWN, REQ_NEXT },
1428 { 'R', REQ_REFRESH },
1429 { KEY_F(5), REQ_REFRESH },
1430 { 'O', REQ_MAXIMIZE },
1432 /* Cursor navigation */
1433 { 'k', REQ_MOVE_UP },
1434 { 'j', REQ_MOVE_DOWN },
1435 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1436 { KEY_END, REQ_MOVE_LAST_LINE },
1437 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1438 { ' ', REQ_MOVE_PAGE_DOWN },
1439 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1440 { 'b', REQ_MOVE_PAGE_UP },
1441 { '-', REQ_MOVE_PAGE_UP },
1443 /* Scrolling */
1444 { KEY_LEFT, REQ_SCROLL_LEFT },
1445 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1446 { KEY_IC, REQ_SCROLL_LINE_UP },
1447 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1448 { 'w', REQ_SCROLL_PAGE_UP },
1449 { 's', REQ_SCROLL_PAGE_DOWN },
1451 /* Searching */
1452 { '/', REQ_SEARCH },
1453 { '?', REQ_SEARCH_BACK },
1454 { 'n', REQ_FIND_NEXT },
1455 { 'N', REQ_FIND_PREV },
1457 /* Misc */
1458 { 'Q', REQ_QUIT },
1459 { 'z', REQ_STOP_LOADING },
1460 { 'v', REQ_SHOW_VERSION },
1461 { 'r', REQ_SCREEN_REDRAW },
1462 { 'o', REQ_OPTIONS },
1463 { '.', REQ_TOGGLE_LINENO },
1464 { 'D', REQ_TOGGLE_DATE },
1465 { 'A', REQ_TOGGLE_AUTHOR },
1466 { 'g', REQ_TOGGLE_REV_GRAPH },
1467 { 'F', REQ_TOGGLE_REFS },
1468 { 'I', REQ_TOGGLE_SORT_ORDER },
1469 { 'i', REQ_TOGGLE_SORT_FIELD },
1470 { ':', REQ_PROMPT },
1471 { 'u', REQ_STATUS_UPDATE },
1472 { '!', REQ_STATUS_REVERT },
1473 { 'M', REQ_STATUS_MERGE },
1474 { '@', REQ_STAGE_NEXT },
1475 { ',', REQ_PARENT },
1476 { 'e', REQ_EDIT },
1477 };
1479 #define KEYMAP_INFO \
1480 KEYMAP_(GENERIC), \
1481 KEYMAP_(MAIN), \
1482 KEYMAP_(DIFF), \
1483 KEYMAP_(LOG), \
1484 KEYMAP_(TREE), \
1485 KEYMAP_(BLOB), \
1486 KEYMAP_(BLAME), \
1487 KEYMAP_(BRANCH), \
1488 KEYMAP_(PAGER), \
1489 KEYMAP_(HELP), \
1490 KEYMAP_(STATUS), \
1491 KEYMAP_(STAGE)
1493 enum keymap {
1494 #define KEYMAP_(name) KEYMAP_##name
1495 KEYMAP_INFO
1496 #undef KEYMAP_
1497 };
1499 static const struct enum_map keymap_table[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1501 KEYMAP_INFO
1502 #undef KEYMAP_
1503 };
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table {
1508 struct keybinding *data;
1509 size_t size;
1510 };
1512 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1514 static void
1515 add_keybinding(enum keymap keymap, enum request request, int key)
1516 {
1517 struct keybinding_table *table = &keybindings[keymap];
1519 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1520 if (!table->data)
1521 die("Failed to allocate keybinding");
1522 table->data[table->size].alias = key;
1523 table->data[table->size++].request = request;
1525 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1526 int i;
1528 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1529 if (default_keybindings[i].alias == key)
1530 default_keybindings[i].request = REQ_NONE;
1531 }
1532 }
1534 /* Looks for a key binding first in the given map, then in the generic map, and
1535 * lastly in the default keybindings. */
1536 static enum request
1537 get_keybinding(enum keymap keymap, int key)
1538 {
1539 size_t i;
1541 for (i = 0; i < keybindings[keymap].size; i++)
1542 if (keybindings[keymap].data[i].alias == key)
1543 return keybindings[keymap].data[i].request;
1545 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1546 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1547 return keybindings[KEYMAP_GENERIC].data[i].request;
1549 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1550 if (default_keybindings[i].alias == key)
1551 return default_keybindings[i].request;
1553 return (enum request) key;
1554 }
1557 struct key {
1558 const char *name;
1559 int value;
1560 };
1562 static const struct key key_table[] = {
1563 { "Enter", KEY_RETURN },
1564 { "Space", ' ' },
1565 { "Backspace", KEY_BACKSPACE },
1566 { "Tab", KEY_TAB },
1567 { "Escape", KEY_ESC },
1568 { "Left", KEY_LEFT },
1569 { "Right", KEY_RIGHT },
1570 { "Up", KEY_UP },
1571 { "Down", KEY_DOWN },
1572 { "Insert", KEY_IC },
1573 { "Delete", KEY_DC },
1574 { "Hash", '#' },
1575 { "Home", KEY_HOME },
1576 { "End", KEY_END },
1577 { "PageUp", KEY_PPAGE },
1578 { "PageDown", KEY_NPAGE },
1579 { "F1", KEY_F(1) },
1580 { "F2", KEY_F(2) },
1581 { "F3", KEY_F(3) },
1582 { "F4", KEY_F(4) },
1583 { "F5", KEY_F(5) },
1584 { "F6", KEY_F(6) },
1585 { "F7", KEY_F(7) },
1586 { "F8", KEY_F(8) },
1587 { "F9", KEY_F(9) },
1588 { "F10", KEY_F(10) },
1589 { "F11", KEY_F(11) },
1590 { "F12", KEY_F(12) },
1591 };
1593 static int
1594 get_key_value(const char *name)
1595 {
1596 int i;
1598 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1599 if (!strcasecmp(key_table[i].name, name))
1600 return key_table[i].value;
1602 if (strlen(name) == 1 && isprint(*name))
1603 return (int) *name;
1605 return ERR;
1606 }
1608 static const char *
1609 get_key_name(int key_value)
1610 {
1611 static char key_char[] = "'X'";
1612 const char *seq = NULL;
1613 int key;
1615 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1616 if (key_table[key].value == key_value)
1617 seq = key_table[key].name;
1619 if (seq == NULL &&
1620 key_value < 127 &&
1621 isprint(key_value)) {
1622 key_char[1] = (char) key_value;
1623 seq = key_char;
1624 }
1626 return seq ? seq : "(no key)";
1627 }
1629 static bool
1630 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1631 {
1632 const char *sep = *pos > 0 ? ", " : "";
1633 const char *keyname = get_key_name(keybinding->alias);
1635 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1636 }
1638 static bool
1639 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1640 enum keymap keymap, bool all)
1641 {
1642 int i;
1644 for (i = 0; i < keybindings[keymap].size; i++) {
1645 if (keybindings[keymap].data[i].request == request) {
1646 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1647 return FALSE;
1648 if (!all)
1649 break;
1650 }
1651 }
1653 return TRUE;
1654 }
1656 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1658 static const char *
1659 get_keys(enum keymap keymap, enum request request, bool all)
1660 {
1661 static char buf[BUFSIZ];
1662 size_t pos = 0;
1663 int i;
1665 buf[pos] = 0;
1667 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1668 return "Too many keybindings!";
1669 if (pos > 0 && !all)
1670 return buf;
1672 if (keymap != KEYMAP_GENERIC) {
1673 /* Only the generic keymap includes the default keybindings when
1674 * listing all keys. */
1675 if (all)
1676 return buf;
1678 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1679 return "Too many keybindings!";
1680 if (pos)
1681 return buf;
1682 }
1684 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1685 if (default_keybindings[i].request == request) {
1686 if (!append_key(buf, &pos, &default_keybindings[i]))
1687 return "Too many keybindings!";
1688 if (!all)
1689 return buf;
1690 }
1691 }
1693 return buf;
1694 }
1696 struct run_request {
1697 enum keymap keymap;
1698 int key;
1699 const char *argv[SIZEOF_ARG];
1700 };
1702 static struct run_request *run_request;
1703 static size_t run_requests;
1705 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1707 static enum request
1708 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1709 {
1710 struct run_request *req;
1712 if (argc >= ARRAY_SIZE(req->argv) - 1)
1713 return REQ_NONE;
1715 if (!realloc_run_requests(&run_request, run_requests, 1))
1716 return REQ_NONE;
1718 req = &run_request[run_requests];
1719 req->keymap = keymap;
1720 req->key = key;
1721 req->argv[0] = NULL;
1723 if (!format_argv(req->argv, argv, FORMAT_NONE))
1724 return REQ_NONE;
1726 return REQ_NONE + ++run_requests;
1727 }
1729 static struct run_request *
1730 get_run_request(enum request request)
1731 {
1732 if (request <= REQ_NONE)
1733 return NULL;
1734 return &run_request[request - REQ_NONE - 1];
1735 }
1737 static void
1738 add_builtin_run_requests(void)
1739 {
1740 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1741 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1742 const char *commit[] = { "git", "commit", NULL };
1743 const char *gc[] = { "git", "gc", NULL };
1744 struct {
1745 enum keymap keymap;
1746 int key;
1747 int argc;
1748 const char **argv;
1749 } reqs[] = {
1750 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1751 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1752 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1753 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1754 };
1755 int i;
1757 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1758 enum request req;
1760 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1761 if (req != REQ_NONE)
1762 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1763 }
1764 }
1766 /*
1767 * User config file handling.
1768 */
1770 static int config_lineno;
1771 static bool config_errors;
1772 static const char *config_msg;
1774 static const struct enum_map color_map[] = {
1775 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1776 COLOR_MAP(DEFAULT),
1777 COLOR_MAP(BLACK),
1778 COLOR_MAP(BLUE),
1779 COLOR_MAP(CYAN),
1780 COLOR_MAP(GREEN),
1781 COLOR_MAP(MAGENTA),
1782 COLOR_MAP(RED),
1783 COLOR_MAP(WHITE),
1784 COLOR_MAP(YELLOW),
1785 };
1787 static const struct enum_map attr_map[] = {
1788 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1789 ATTR_MAP(NORMAL),
1790 ATTR_MAP(BLINK),
1791 ATTR_MAP(BOLD),
1792 ATTR_MAP(DIM),
1793 ATTR_MAP(REVERSE),
1794 ATTR_MAP(STANDOUT),
1795 ATTR_MAP(UNDERLINE),
1796 };
1798 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1800 static int parse_step(double *opt, const char *arg)
1801 {
1802 *opt = atoi(arg);
1803 if (!strchr(arg, '%'))
1804 return OK;
1806 /* "Shift down" so 100% and 1 does not conflict. */
1807 *opt = (*opt - 1) / 100;
1808 if (*opt >= 1.0) {
1809 *opt = 0.99;
1810 config_msg = "Step value larger than 100%";
1811 return ERR;
1812 }
1813 if (*opt < 0.0) {
1814 *opt = 1;
1815 config_msg = "Invalid step value";
1816 return ERR;
1817 }
1818 return OK;
1819 }
1821 static int
1822 parse_int(int *opt, const char *arg, int min, int max)
1823 {
1824 int value = atoi(arg);
1826 if (min <= value && value <= max) {
1827 *opt = value;
1828 return OK;
1829 }
1831 config_msg = "Integer value out of bound";
1832 return ERR;
1833 }
1835 static bool
1836 set_color(int *color, const char *name)
1837 {
1838 if (map_enum(color, color_map, name))
1839 return TRUE;
1840 if (!prefixcmp(name, "color"))
1841 return parse_int(color, name + 5, 0, 255) == OK;
1842 return FALSE;
1843 }
1845 /* Wants: object fgcolor bgcolor [attribute] */
1846 static int
1847 option_color_command(int argc, const char *argv[])
1848 {
1849 struct line_info *info;
1851 if (argc < 3) {
1852 config_msg = "Wrong number of arguments given to color command";
1853 return ERR;
1854 }
1856 info = get_line_info(argv[0]);
1857 if (!info) {
1858 static const struct enum_map obsolete[] = {
1859 ENUM_MAP("main-delim", LINE_DELIMITER),
1860 ENUM_MAP("main-date", LINE_DATE),
1861 ENUM_MAP("main-author", LINE_AUTHOR),
1862 };
1863 int index;
1865 if (!map_enum(&index, obsolete, argv[0])) {
1866 config_msg = "Unknown color name";
1867 return ERR;
1868 }
1869 info = &line_info[index];
1870 }
1872 if (!set_color(&info->fg, argv[1]) ||
1873 !set_color(&info->bg, argv[2])) {
1874 config_msg = "Unknown color";
1875 return ERR;
1876 }
1878 info->attr = 0;
1879 while (argc-- > 3) {
1880 int attr;
1882 if (!set_attribute(&attr, argv[argc])) {
1883 config_msg = "Unknown attribute";
1884 return ERR;
1885 }
1886 info->attr |= attr;
1887 }
1889 return OK;
1890 }
1892 static int parse_bool(bool *opt, const char *arg)
1893 {
1894 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1895 ? TRUE : FALSE;
1896 return OK;
1897 }
1899 static int parse_enum_do(unsigned int *opt, const char *arg,
1900 const struct enum_map *map, size_t map_size)
1901 {
1902 bool is_true;
1904 assert(map_size > 1);
1906 if (map_enum_do(map, map_size, (int *) opt, arg))
1907 return OK;
1909 if (parse_bool(&is_true, arg) != OK)
1910 return ERR;
1912 *opt = is_true ? map[1].value : map[0].value;
1913 return OK;
1914 }
1916 #define parse_enum(opt, arg, map) \
1917 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1919 static int
1920 parse_string(char *opt, const char *arg, size_t optsize)
1921 {
1922 int arglen = strlen(arg);
1924 switch (arg[0]) {
1925 case '\"':
1926 case '\'':
1927 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1928 config_msg = "Unmatched quotation";
1929 return ERR;
1930 }
1931 arg += 1; arglen -= 2;
1932 default:
1933 string_ncopy_do(opt, optsize, arg, arglen);
1934 return OK;
1935 }
1936 }
1938 /* Wants: name = value */
1939 static int
1940 option_set_command(int argc, const char *argv[])
1941 {
1942 if (argc != 3) {
1943 config_msg = "Wrong number of arguments given to set command";
1944 return ERR;
1945 }
1947 if (strcmp(argv[1], "=")) {
1948 config_msg = "No value assigned";
1949 return ERR;
1950 }
1952 if (!strcmp(argv[0], "show-author"))
1953 return parse_enum(&opt_author, argv[2], author_map);
1955 if (!strcmp(argv[0], "show-date"))
1956 return parse_enum(&opt_date, argv[2], date_map);
1958 if (!strcmp(argv[0], "show-rev-graph"))
1959 return parse_bool(&opt_rev_graph, argv[2]);
1961 if (!strcmp(argv[0], "show-refs"))
1962 return parse_bool(&opt_show_refs, argv[2]);
1964 if (!strcmp(argv[0], "show-line-numbers"))
1965 return parse_bool(&opt_line_number, argv[2]);
1967 if (!strcmp(argv[0], "line-graphics"))
1968 return parse_bool(&opt_line_graphics, argv[2]);
1970 if (!strcmp(argv[0], "line-number-interval"))
1971 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1973 if (!strcmp(argv[0], "author-width"))
1974 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1976 if (!strcmp(argv[0], "horizontal-scroll"))
1977 return parse_step(&opt_hscroll, argv[2]);
1979 if (!strcmp(argv[0], "split-view-height"))
1980 return parse_step(&opt_scale_split_view, argv[2]);
1982 if (!strcmp(argv[0], "tab-size"))
1983 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1985 if (!strcmp(argv[0], "commit-encoding"))
1986 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1988 config_msg = "Unknown variable name";
1989 return ERR;
1990 }
1992 /* Wants: mode request key */
1993 static int
1994 option_bind_command(int argc, const char *argv[])
1995 {
1996 enum request request;
1997 int keymap = -1;
1998 int key;
2000 if (argc < 3) {
2001 config_msg = "Wrong number of arguments given to bind command";
2002 return ERR;
2003 }
2005 if (set_keymap(&keymap, argv[0]) == ERR) {
2006 config_msg = "Unknown key map";
2007 return ERR;
2008 }
2010 key = get_key_value(argv[1]);
2011 if (key == ERR) {
2012 config_msg = "Unknown key";
2013 return ERR;
2014 }
2016 request = get_request(argv[2]);
2017 if (request == REQ_UNKNOWN) {
2018 static const struct enum_map obsolete[] = {
2019 ENUM_MAP("cherry-pick", REQ_NONE),
2020 ENUM_MAP("screen-resize", REQ_NONE),
2021 ENUM_MAP("tree-parent", REQ_PARENT),
2022 };
2023 int alias;
2025 if (map_enum(&alias, obsolete, argv[2])) {
2026 if (alias != REQ_NONE)
2027 add_keybinding(keymap, alias, key);
2028 config_msg = "Obsolete request name";
2029 return ERR;
2030 }
2031 }
2032 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2033 request = add_run_request(keymap, key, argc - 2, argv + 2);
2034 if (request == REQ_UNKNOWN) {
2035 config_msg = "Unknown request name";
2036 return ERR;
2037 }
2039 add_keybinding(keymap, request, key);
2041 return OK;
2042 }
2044 static int
2045 set_option(const char *opt, char *value)
2046 {
2047 const char *argv[SIZEOF_ARG];
2048 int argc = 0;
2050 if (!argv_from_string(argv, &argc, value)) {
2051 config_msg = "Too many option arguments";
2052 return ERR;
2053 }
2055 if (!strcmp(opt, "color"))
2056 return option_color_command(argc, argv);
2058 if (!strcmp(opt, "set"))
2059 return option_set_command(argc, argv);
2061 if (!strcmp(opt, "bind"))
2062 return option_bind_command(argc, argv);
2064 config_msg = "Unknown option command";
2065 return ERR;
2066 }
2068 static int
2069 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2070 {
2071 int status = OK;
2073 config_lineno++;
2074 config_msg = "Internal error";
2076 /* Check for comment markers, since read_properties() will
2077 * only ensure opt and value are split at first " \t". */
2078 optlen = strcspn(opt, "#");
2079 if (optlen == 0)
2080 return OK;
2082 if (opt[optlen] != 0) {
2083 config_msg = "No option value";
2084 status = ERR;
2086 } else {
2087 /* Look for comment endings in the value. */
2088 size_t len = strcspn(value, "#");
2090 if (len < valuelen) {
2091 valuelen = len;
2092 value[valuelen] = 0;
2093 }
2095 status = set_option(opt, value);
2096 }
2098 if (status == ERR) {
2099 warn("Error on line %d, near '%.*s': %s",
2100 config_lineno, (int) optlen, opt, config_msg);
2101 config_errors = TRUE;
2102 }
2104 /* Always keep going if errors are encountered. */
2105 return OK;
2106 }
2108 static void
2109 load_option_file(const char *path)
2110 {
2111 struct io io = {};
2113 /* It's OK that the file doesn't exist. */
2114 if (!io_open(&io, "%s", path))
2115 return;
2117 config_lineno = 0;
2118 config_errors = FALSE;
2120 if (io_load(&io, " \t", read_option) == ERR ||
2121 config_errors == TRUE)
2122 warn("Errors while loading %s.", path);
2123 }
2125 static int
2126 load_options(void)
2127 {
2128 const char *home = getenv("HOME");
2129 const char *tigrc_user = getenv("TIGRC_USER");
2130 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2131 char buf[SIZEOF_STR];
2133 add_builtin_run_requests();
2135 if (!tigrc_system)
2136 tigrc_system = SYSCONFDIR "/tigrc";
2137 load_option_file(tigrc_system);
2139 if (!tigrc_user) {
2140 if (!home || !string_format(buf, "%s/.tigrc", home))
2141 return ERR;
2142 tigrc_user = buf;
2143 }
2144 load_option_file(tigrc_user);
2146 return OK;
2147 }
2150 /*
2151 * The viewer
2152 */
2154 struct view;
2155 struct view_ops;
2157 /* The display array of active views and the index of the current view. */
2158 static struct view *display[2];
2159 static unsigned int current_view;
2161 #define foreach_displayed_view(view, i) \
2162 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2164 #define displayed_views() (display[1] != NULL ? 2 : 1)
2166 /* Current head and commit ID */
2167 static char ref_blob[SIZEOF_REF] = "";
2168 static char ref_commit[SIZEOF_REF] = "HEAD";
2169 static char ref_head[SIZEOF_REF] = "HEAD";
2170 static char ref_branch[SIZEOF_REF] = "";
2172 enum view_type {
2173 VIEW_MAIN,
2174 VIEW_DIFF,
2175 VIEW_LOG,
2176 VIEW_TREE,
2177 VIEW_BLOB,
2178 VIEW_BLAME,
2179 VIEW_BRANCH,
2180 VIEW_HELP,
2181 VIEW_PAGER,
2182 VIEW_STATUS,
2183 VIEW_STAGE,
2184 };
2186 struct view {
2187 enum view_type type; /* View type */
2188 const char *name; /* View name */
2189 const char *cmd_env; /* Command line set via environment */
2190 const char *id; /* Points to either of ref_{head,commit,blob} */
2192 struct view_ops *ops; /* View operations */
2194 enum keymap keymap; /* What keymap does this view have */
2195 bool git_dir; /* Whether the view requires a git directory. */
2196 bool refresh; /* Whether the view supports refreshing. */
2198 char ref[SIZEOF_REF]; /* Hovered commit reference */
2199 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2201 int height, width; /* The width and height of the main window */
2202 WINDOW *win; /* The main window */
2203 WINDOW *title; /* The title window living below the main window */
2205 /* Navigation */
2206 unsigned long offset; /* Offset of the window top */
2207 unsigned long yoffset; /* Offset from the window side. */
2208 unsigned long lineno; /* Current line number */
2209 unsigned long p_offset; /* Previous offset of the window top */
2210 unsigned long p_yoffset;/* Previous offset from the window side */
2211 unsigned long p_lineno; /* Previous current line number */
2212 bool p_restore; /* Should the previous position be restored. */
2214 /* Searching */
2215 char grep[SIZEOF_STR]; /* Search string */
2216 regex_t *regex; /* Pre-compiled regexp */
2218 /* If non-NULL, points to the view that opened this view. If this view
2219 * is closed tig will switch back to the parent view. */
2220 struct view *parent;
2222 /* Buffering */
2223 size_t lines; /* Total number of lines */
2224 struct line *line; /* Line index */
2225 unsigned int digits; /* Number of digits in the lines member. */
2227 /* Drawing */
2228 struct line *curline; /* Line currently being drawn. */
2229 enum line_type curtype; /* Attribute currently used for drawing. */
2230 unsigned long col; /* Column when drawing. */
2231 bool has_scrolled; /* View was scrolled. */
2233 /* Loading */
2234 struct io io;
2235 struct io *pipe;
2236 time_t start_time;
2237 time_t update_secs;
2238 };
2240 struct view_ops {
2241 /* What type of content being displayed. Used in the title bar. */
2242 const char *type;
2243 /* Default command arguments. */
2244 const char **argv;
2245 /* Open and reads in all view content. */
2246 bool (*open)(struct view *view);
2247 /* Read one line; updates view->line. */
2248 bool (*read)(struct view *view, char *data);
2249 /* Draw one line; @lineno must be < view->height. */
2250 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2251 /* Depending on view handle a special requests. */
2252 enum request (*request)(struct view *view, enum request request, struct line *line);
2253 /* Search for regexp in a line. */
2254 bool (*grep)(struct view *view, struct line *line);
2255 /* Select line */
2256 void (*select)(struct view *view, struct line *line);
2257 /* Prepare view for loading */
2258 bool (*prepare)(struct view *view);
2259 };
2261 static struct view_ops blame_ops;
2262 static struct view_ops blob_ops;
2263 static struct view_ops diff_ops;
2264 static struct view_ops help_ops;
2265 static struct view_ops log_ops;
2266 static struct view_ops main_ops;
2267 static struct view_ops pager_ops;
2268 static struct view_ops stage_ops;
2269 static struct view_ops status_ops;
2270 static struct view_ops tree_ops;
2271 static struct view_ops branch_ops;
2273 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2274 { type, name, #env, ref, ops, map, git, refresh }
2276 #define VIEW_(id, name, ops, git, refresh, ref) \
2277 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2279 static struct view views[] = {
2280 VIEW_(MAIN, "main", &main_ops, TRUE, TRUE, ref_head),
2281 VIEW_(DIFF, "diff", &diff_ops, TRUE, FALSE, ref_commit),
2282 VIEW_(LOG, "log", &log_ops, TRUE, TRUE, ref_head),
2283 VIEW_(TREE, "tree", &tree_ops, TRUE, FALSE, ref_commit),
2284 VIEW_(BLOB, "blob", &blob_ops, TRUE, FALSE, ref_blob),
2285 VIEW_(BLAME, "blame", &blame_ops, TRUE, FALSE, ref_commit),
2286 VIEW_(BRANCH, "branch", &branch_ops, TRUE, TRUE, ref_head),
2287 VIEW_(HELP, "help", &help_ops, FALSE, FALSE, ""),
2288 VIEW_(PAGER, "pager", &pager_ops, FALSE, FALSE, "stdin"),
2289 VIEW_(STATUS, "status", &status_ops, TRUE, TRUE, ""),
2290 VIEW_(STAGE, "stage", &stage_ops, TRUE, TRUE, ""),
2291 };
2293 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2295 #define foreach_view(view, i) \
2296 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2298 #define view_is_displayed(view) \
2299 (view == display[0] || view == display[1])
2301 #define view_has_parent(view, child_type, parent_type) \
2302 (view->type == child_type && view->parent && view->parent->type == parent_type)
2304 static inline void
2305 set_view_attr(struct view *view, enum line_type type)
2306 {
2307 if (!view->curline->selected && view->curtype != type) {
2308 (void) wattrset(view->win, get_line_attr(type));
2309 wchgat(view->win, -1, 0, type, NULL);
2310 view->curtype = type;
2311 }
2312 }
2314 static int
2315 draw_chars(struct view *view, enum line_type type, const char *string,
2316 int max_len, bool use_tilde)
2317 {
2318 static char out_buffer[BUFSIZ * 2];
2319 int len = 0;
2320 int col = 0;
2321 int trimmed = FALSE;
2322 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2324 if (max_len <= 0)
2325 return 0;
2327 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2329 set_view_attr(view, type);
2330 if (len > 0) {
2331 if (opt_iconv_out != ICONV_NONE) {
2332 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2333 size_t inlen = len + 1;
2335 char *outbuf = out_buffer;
2336 size_t outlen = sizeof(out_buffer);
2338 size_t ret;
2340 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2341 if (ret != (size_t) -1) {
2342 string = out_buffer;
2343 len = sizeof(out_buffer) - outlen;
2344 }
2345 }
2347 waddnstr(view->win, string, len);
2348 }
2349 if (trimmed && use_tilde) {
2350 set_view_attr(view, LINE_DELIMITER);
2351 waddch(view->win, '~');
2352 col++;
2353 }
2355 return col;
2356 }
2358 static int
2359 draw_space(struct view *view, enum line_type type, int max, int spaces)
2360 {
2361 static char space[] = " ";
2362 int col = 0;
2364 spaces = MIN(max, spaces);
2366 while (spaces > 0) {
2367 int len = MIN(spaces, sizeof(space) - 1);
2369 col += draw_chars(view, type, space, len, FALSE);
2370 spaces -= len;
2371 }
2373 return col;
2374 }
2376 static bool
2377 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2378 {
2379 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2380 return view->width + view->yoffset <= view->col;
2381 }
2383 static bool
2384 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2385 {
2386 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2387 int max = view->width + view->yoffset - view->col;
2388 int i;
2390 if (max < size)
2391 size = max;
2393 set_view_attr(view, type);
2394 /* Using waddch() instead of waddnstr() ensures that
2395 * they'll be rendered correctly for the cursor line. */
2396 for (i = skip; i < size; i++)
2397 waddch(view->win, graphic[i]);
2399 view->col += size;
2400 if (size < max && skip <= size)
2401 waddch(view->win, ' ');
2402 view->col++;
2404 return view->width + view->yoffset <= view->col;
2405 }
2407 static bool
2408 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2409 {
2410 int max = MIN(view->width + view->yoffset - view->col, len);
2411 int col;
2413 if (text)
2414 col = draw_chars(view, type, text, max - 1, trim);
2415 else
2416 col = draw_space(view, type, max - 1, max - 1);
2418 view->col += col;
2419 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2420 return view->width + view->yoffset <= view->col;
2421 }
2423 static bool
2424 draw_date(struct view *view, struct time *time)
2425 {
2426 const char *date = mkdate(time, opt_date);
2427 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2429 return draw_field(view, LINE_DATE, date, cols, FALSE);
2430 }
2432 static bool
2433 draw_author(struct view *view, const char *author)
2434 {
2435 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2436 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2438 if (abbreviate && author)
2439 author = get_author_initials(author);
2441 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2442 }
2444 static bool
2445 draw_mode(struct view *view, mode_t mode)
2446 {
2447 const char *str;
2449 if (S_ISDIR(mode))
2450 str = "drwxr-xr-x";
2451 else if (S_ISLNK(mode))
2452 str = "lrwxrwxrwx";
2453 else if (S_ISGITLINK(mode))
2454 str = "m---------";
2455 else if (S_ISREG(mode) && mode & S_IXUSR)
2456 str = "-rwxr-xr-x";
2457 else if (S_ISREG(mode))
2458 str = "-rw-r--r--";
2459 else
2460 str = "----------";
2462 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2463 }
2465 static bool
2466 draw_lineno(struct view *view, unsigned int lineno)
2467 {
2468 char number[10];
2469 int digits3 = view->digits < 3 ? 3 : view->digits;
2470 int max = MIN(view->width + view->yoffset - view->col, digits3);
2471 char *text = NULL;
2472 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2474 lineno += view->offset + 1;
2475 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2476 static char fmt[] = "%1ld";
2478 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2479 if (string_format(number, fmt, lineno))
2480 text = number;
2481 }
2482 if (text)
2483 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2484 else
2485 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2486 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2487 }
2489 static bool
2490 draw_view_line(struct view *view, unsigned int lineno)
2491 {
2492 struct line *line;
2493 bool selected = (view->offset + lineno == view->lineno);
2495 assert(view_is_displayed(view));
2497 if (view->offset + lineno >= view->lines)
2498 return FALSE;
2500 line = &view->line[view->offset + lineno];
2502 wmove(view->win, lineno, 0);
2503 if (line->cleareol)
2504 wclrtoeol(view->win);
2505 view->col = 0;
2506 view->curline = line;
2507 view->curtype = LINE_NONE;
2508 line->selected = FALSE;
2509 line->dirty = line->cleareol = 0;
2511 if (selected) {
2512 set_view_attr(view, LINE_CURSOR);
2513 line->selected = TRUE;
2514 view->ops->select(view, line);
2515 }
2517 return view->ops->draw(view, line, lineno);
2518 }
2520 static void
2521 redraw_view_dirty(struct view *view)
2522 {
2523 bool dirty = FALSE;
2524 int lineno;
2526 for (lineno = 0; lineno < view->height; lineno++) {
2527 if (view->offset + lineno >= view->lines)
2528 break;
2529 if (!view->line[view->offset + lineno].dirty)
2530 continue;
2531 dirty = TRUE;
2532 if (!draw_view_line(view, lineno))
2533 break;
2534 }
2536 if (!dirty)
2537 return;
2538 wnoutrefresh(view->win);
2539 }
2541 static void
2542 redraw_view_from(struct view *view, int lineno)
2543 {
2544 assert(0 <= lineno && lineno < view->height);
2546 for (; lineno < view->height; lineno++) {
2547 if (!draw_view_line(view, lineno))
2548 break;
2549 }
2551 wnoutrefresh(view->win);
2552 }
2554 static void
2555 redraw_view(struct view *view)
2556 {
2557 werase(view->win);
2558 redraw_view_from(view, 0);
2559 }
2562 static void
2563 update_view_title(struct view *view)
2564 {
2565 char buf[SIZEOF_STR];
2566 char state[SIZEOF_STR];
2567 size_t bufpos = 0, statelen = 0;
2569 assert(view_is_displayed(view));
2571 if (view->type != VIEW_STATUS && view->lines) {
2572 unsigned int view_lines = view->offset + view->height;
2573 unsigned int lines = view->lines
2574 ? MIN(view_lines, view->lines) * 100 / view->lines
2575 : 0;
2577 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2578 view->ops->type,
2579 view->lineno + 1,
2580 view->lines,
2581 lines);
2583 }
2585 if (view->pipe) {
2586 time_t secs = time(NULL) - view->start_time;
2588 /* Three git seconds are a long time ... */
2589 if (secs > 2)
2590 string_format_from(state, &statelen, " loading %lds", secs);
2591 }
2593 string_format_from(buf, &bufpos, "[%s]", view->name);
2594 if (*view->ref && bufpos < view->width) {
2595 size_t refsize = strlen(view->ref);
2596 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2598 if (minsize < view->width)
2599 refsize = view->width - minsize + 7;
2600 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2601 }
2603 if (statelen && bufpos < view->width) {
2604 string_format_from(buf, &bufpos, "%s", state);
2605 }
2607 if (view == display[current_view])
2608 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2609 else
2610 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2612 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2613 wclrtoeol(view->title);
2614 wnoutrefresh(view->title);
2615 }
2617 static int
2618 apply_step(double step, int value)
2619 {
2620 if (step >= 1)
2621 return (int) step;
2622 value *= step + 0.01;
2623 return value ? value : 1;
2624 }
2626 static void
2627 resize_display(void)
2628 {
2629 int offset, i;
2630 struct view *base = display[0];
2631 struct view *view = display[1] ? display[1] : display[0];
2633 /* Setup window dimensions */
2635 getmaxyx(stdscr, base->height, base->width);
2637 /* Make room for the status window. */
2638 base->height -= 1;
2640 if (view != base) {
2641 /* Horizontal split. */
2642 view->width = base->width;
2643 view->height = apply_step(opt_scale_split_view, base->height);
2644 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2645 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2646 base->height -= view->height;
2648 /* Make room for the title bar. */
2649 view->height -= 1;
2650 }
2652 /* Make room for the title bar. */
2653 base->height -= 1;
2655 offset = 0;
2657 foreach_displayed_view (view, i) {
2658 if (!view->win) {
2659 view->win = newwin(view->height, 0, offset, 0);
2660 if (!view->win)
2661 die("Failed to create %s view", view->name);
2663 scrollok(view->win, FALSE);
2665 view->title = newwin(1, 0, offset + view->height, 0);
2666 if (!view->title)
2667 die("Failed to create title window");
2669 } else {
2670 wresize(view->win, view->height, view->width);
2671 mvwin(view->win, offset, 0);
2672 mvwin(view->title, offset + view->height, 0);
2673 }
2675 offset += view->height + 1;
2676 }
2677 }
2679 static void
2680 redraw_display(bool clear)
2681 {
2682 struct view *view;
2683 int i;
2685 foreach_displayed_view (view, i) {
2686 if (clear)
2687 wclear(view->win);
2688 redraw_view(view);
2689 update_view_title(view);
2690 }
2691 }
2693 static void
2694 toggle_enum_option_do(unsigned int *opt, const char *help,
2695 const struct enum_map *map, size_t size)
2696 {
2697 *opt = (*opt + 1) % size;
2698 redraw_display(FALSE);
2699 report("Displaying %s %s", enum_name(map[*opt]), help);
2700 }
2702 #define toggle_enum_option(opt, help, map) \
2703 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2705 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2706 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2708 static void
2709 toggle_view_option(bool *option, const char *help)
2710 {
2711 *option = !*option;
2712 redraw_display(FALSE);
2713 report("%sabling %s", *option ? "En" : "Dis", help);
2714 }
2716 static void
2717 open_option_menu(void)
2718 {
2719 const struct menu_item menu[] = {
2720 { '.', "line numbers", &opt_line_number },
2721 { 'D', "date display", &opt_date },
2722 { 'A', "author display", &opt_author },
2723 { 'g', "revision graph display", &opt_rev_graph },
2724 { 'F', "reference display", &opt_show_refs },
2725 { 0 }
2726 };
2727 int selected = 0;
2729 if (prompt_menu("Toggle option", menu, &selected)) {
2730 if (menu[selected].data == &opt_date)
2731 toggle_date();
2732 else if (menu[selected].data == &opt_author)
2733 toggle_author();
2734 else
2735 toggle_view_option(menu[selected].data, menu[selected].text);
2736 }
2737 }
2739 static void
2740 maximize_view(struct view *view)
2741 {
2742 memset(display, 0, sizeof(display));
2743 current_view = 0;
2744 display[current_view] = view;
2745 resize_display();
2746 redraw_display(FALSE);
2747 report("");
2748 }
2751 /*
2752 * Navigation
2753 */
2755 static bool
2756 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2757 {
2758 if (lineno >= view->lines)
2759 lineno = view->lines > 0 ? view->lines - 1 : 0;
2761 if (offset > lineno || offset + view->height <= lineno) {
2762 unsigned long half = view->height / 2;
2764 if (lineno > half)
2765 offset = lineno - half;
2766 else
2767 offset = 0;
2768 }
2770 if (offset != view->offset || lineno != view->lineno) {
2771 view->offset = offset;
2772 view->lineno = lineno;
2773 return TRUE;
2774 }
2776 return FALSE;
2777 }
2779 /* Scrolling backend */
2780 static void
2781 do_scroll_view(struct view *view, int lines)
2782 {
2783 bool redraw_current_line = FALSE;
2785 /* The rendering expects the new offset. */
2786 view->offset += lines;
2788 assert(0 <= view->offset && view->offset < view->lines);
2789 assert(lines);
2791 /* Move current line into the view. */
2792 if (view->lineno < view->offset) {
2793 view->lineno = view->offset;
2794 redraw_current_line = TRUE;
2795 } else if (view->lineno >= view->offset + view->height) {
2796 view->lineno = view->offset + view->height - 1;
2797 redraw_current_line = TRUE;
2798 }
2800 assert(view->offset <= view->lineno && view->lineno < view->lines);
2802 /* Redraw the whole screen if scrolling is pointless. */
2803 if (view->height < ABS(lines)) {
2804 redraw_view(view);
2806 } else {
2807 int line = lines > 0 ? view->height - lines : 0;
2808 int end = line + ABS(lines);
2810 scrollok(view->win, TRUE);
2811 wscrl(view->win, lines);
2812 scrollok(view->win, FALSE);
2814 while (line < end && draw_view_line(view, line))
2815 line++;
2817 if (redraw_current_line)
2818 draw_view_line(view, view->lineno - view->offset);
2819 wnoutrefresh(view->win);
2820 }
2822 view->has_scrolled = TRUE;
2823 report("");
2824 }
2826 /* Scroll frontend */
2827 static void
2828 scroll_view(struct view *view, enum request request)
2829 {
2830 int lines = 1;
2832 assert(view_is_displayed(view));
2834 switch (request) {
2835 case REQ_SCROLL_LEFT:
2836 if (view->yoffset == 0) {
2837 report("Cannot scroll beyond the first column");
2838 return;
2839 }
2840 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2841 view->yoffset = 0;
2842 else
2843 view->yoffset -= apply_step(opt_hscroll, view->width);
2844 redraw_view_from(view, 0);
2845 report("");
2846 return;
2847 case REQ_SCROLL_RIGHT:
2848 view->yoffset += apply_step(opt_hscroll, view->width);
2849 redraw_view(view);
2850 report("");
2851 return;
2852 case REQ_SCROLL_PAGE_DOWN:
2853 lines = view->height;
2854 case REQ_SCROLL_LINE_DOWN:
2855 if (view->offset + lines > view->lines)
2856 lines = view->lines - view->offset;
2858 if (lines == 0 || view->offset + view->height >= view->lines) {
2859 report("Cannot scroll beyond the last line");
2860 return;
2861 }
2862 break;
2864 case REQ_SCROLL_PAGE_UP:
2865 lines = view->height;
2866 case REQ_SCROLL_LINE_UP:
2867 if (lines > view->offset)
2868 lines = view->offset;
2870 if (lines == 0) {
2871 report("Cannot scroll beyond the first line");
2872 return;
2873 }
2875 lines = -lines;
2876 break;
2878 default:
2879 die("request %d not handled in switch", request);
2880 }
2882 do_scroll_view(view, lines);
2883 }
2885 /* Cursor moving */
2886 static void
2887 move_view(struct view *view, enum request request)
2888 {
2889 int scroll_steps = 0;
2890 int steps;
2892 switch (request) {
2893 case REQ_MOVE_FIRST_LINE:
2894 steps = -view->lineno;
2895 break;
2897 case REQ_MOVE_LAST_LINE:
2898 steps = view->lines - view->lineno - 1;
2899 break;
2901 case REQ_MOVE_PAGE_UP:
2902 steps = view->height > view->lineno
2903 ? -view->lineno : -view->height;
2904 break;
2906 case REQ_MOVE_PAGE_DOWN:
2907 steps = view->lineno + view->height >= view->lines
2908 ? view->lines - view->lineno - 1 : view->height;
2909 break;
2911 case REQ_MOVE_UP:
2912 steps = -1;
2913 break;
2915 case REQ_MOVE_DOWN:
2916 steps = 1;
2917 break;
2919 default:
2920 die("request %d not handled in switch", request);
2921 }
2923 if (steps <= 0 && view->lineno == 0) {
2924 report("Cannot move beyond the first line");
2925 return;
2927 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2928 report("Cannot move beyond the last line");
2929 return;
2930 }
2932 /* Move the current line */
2933 view->lineno += steps;
2934 assert(0 <= view->lineno && view->lineno < view->lines);
2936 /* Check whether the view needs to be scrolled */
2937 if (view->lineno < view->offset ||
2938 view->lineno >= view->offset + view->height) {
2939 scroll_steps = steps;
2940 if (steps < 0 && -steps > view->offset) {
2941 scroll_steps = -view->offset;
2943 } else if (steps > 0) {
2944 if (view->lineno == view->lines - 1 &&
2945 view->lines > view->height) {
2946 scroll_steps = view->lines - view->offset - 1;
2947 if (scroll_steps >= view->height)
2948 scroll_steps -= view->height - 1;
2949 }
2950 }
2951 }
2953 if (!view_is_displayed(view)) {
2954 view->offset += scroll_steps;
2955 assert(0 <= view->offset && view->offset < view->lines);
2956 view->ops->select(view, &view->line[view->lineno]);
2957 return;
2958 }
2960 /* Repaint the old "current" line if we be scrolling */
2961 if (ABS(steps) < view->height)
2962 draw_view_line(view, view->lineno - steps - view->offset);
2964 if (scroll_steps) {
2965 do_scroll_view(view, scroll_steps);
2966 return;
2967 }
2969 /* Draw the current line */
2970 draw_view_line(view, view->lineno - view->offset);
2972 wnoutrefresh(view->win);
2973 report("");
2974 }
2977 /*
2978 * Searching
2979 */
2981 static void search_view(struct view *view, enum request request);
2983 static bool
2984 grep_text(struct view *view, const char *text[])
2985 {
2986 regmatch_t pmatch;
2987 size_t i;
2989 for (i = 0; text[i]; i++)
2990 if (*text[i] &&
2991 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2992 return TRUE;
2993 return FALSE;
2994 }
2996 static void
2997 select_view_line(struct view *view, unsigned long lineno)
2998 {
2999 unsigned long old_lineno = view->lineno;
3000 unsigned long old_offset = view->offset;
3002 if (goto_view_line(view, view->offset, lineno)) {
3003 if (view_is_displayed(view)) {
3004 if (old_offset != view->offset) {
3005 redraw_view(view);
3006 } else {
3007 draw_view_line(view, old_lineno - view->offset);
3008 draw_view_line(view, view->lineno - view->offset);
3009 wnoutrefresh(view->win);
3010 }
3011 } else {
3012 view->ops->select(view, &view->line[view->lineno]);
3013 }
3014 }
3015 }
3017 static void
3018 find_next(struct view *view, enum request request)
3019 {
3020 unsigned long lineno = view->lineno;
3021 int direction;
3023 if (!*view->grep) {
3024 if (!*opt_search)
3025 report("No previous search");
3026 else
3027 search_view(view, request);
3028 return;
3029 }
3031 switch (request) {
3032 case REQ_SEARCH:
3033 case REQ_FIND_NEXT:
3034 direction = 1;
3035 break;
3037 case REQ_SEARCH_BACK:
3038 case REQ_FIND_PREV:
3039 direction = -1;
3040 break;
3042 default:
3043 return;
3044 }
3046 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3047 lineno += direction;
3049 /* Note, lineno is unsigned long so will wrap around in which case it
3050 * will become bigger than view->lines. */
3051 for (; lineno < view->lines; lineno += direction) {
3052 if (view->ops->grep(view, &view->line[lineno])) {
3053 select_view_line(view, lineno);
3054 report("Line %ld matches '%s'", lineno + 1, view->grep);
3055 return;
3056 }
3057 }
3059 report("No match found for '%s'", view->grep);
3060 }
3062 static void
3063 search_view(struct view *view, enum request request)
3064 {
3065 int regex_err;
3067 if (view->regex) {
3068 regfree(view->regex);
3069 *view->grep = 0;
3070 } else {
3071 view->regex = calloc(1, sizeof(*view->regex));
3072 if (!view->regex)
3073 return;
3074 }
3076 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3077 if (regex_err != 0) {
3078 char buf[SIZEOF_STR] = "unknown error";
3080 regerror(regex_err, view->regex, buf, sizeof(buf));
3081 report("Search failed: %s", buf);
3082 return;
3083 }
3085 string_copy(view->grep, opt_search);
3087 find_next(view, request);
3088 }
3090 /*
3091 * Incremental updating
3092 */
3094 static void
3095 reset_view(struct view *view)
3096 {
3097 int i;
3099 for (i = 0; i < view->lines; i++)
3100 free(view->line[i].data);
3101 free(view->line);
3103 view->p_offset = view->offset;
3104 view->p_yoffset = view->yoffset;
3105 view->p_lineno = view->lineno;
3107 view->line = NULL;
3108 view->offset = 0;
3109 view->yoffset = 0;
3110 view->lines = 0;
3111 view->lineno = 0;
3112 view->vid[0] = 0;
3113 view->update_secs = 0;
3114 }
3116 static void
3117 free_argv(const char *argv[])
3118 {
3119 int argc;
3121 for (argc = 0; argv[argc]; argc++)
3122 free((void *) argv[argc]);
3123 }
3125 static const char *
3126 format_arg(const char *name)
3127 {
3128 static struct {
3129 const char *name;
3130 size_t namelen;
3131 const char *value;
3132 const char *value_if_empty;
3133 } vars[] = {
3134 #define FORMAT_VAR(name, value, value_if_empty) \
3135 { name, STRING_SIZE(name), value, value_if_empty }
3136 FORMAT_VAR("%(directory)", opt_path, ""),
3137 FORMAT_VAR("%(file)", opt_file, ""),
3138 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3139 FORMAT_VAR("%(head)", ref_head, ""),
3140 FORMAT_VAR("%(commit)", ref_commit, ""),
3141 FORMAT_VAR("%(blob)", ref_blob, ""),
3142 FORMAT_VAR("%(branch)", ref_branch, ""),
3143 };
3144 int i;
3146 for (i = 0; i < ARRAY_SIZE(vars); i++)
3147 if (!strncmp(name, vars[i].name, vars[i].namelen))
3148 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3150 report("Unknown replacement: `%s`", name);
3151 return NULL;
3152 }
3154 static bool
3155 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3156 {
3157 char buf[SIZEOF_STR];
3158 int argc;
3159 bool noreplace = flags == FORMAT_NONE;
3161 free_argv(dst_argv);
3163 for (argc = 0; src_argv[argc]; argc++) {
3164 const char *arg = src_argv[argc];
3165 size_t bufpos = 0;
3167 while (arg) {
3168 char *next = strstr(arg, "%(");
3169 int len = next - arg;
3170 const char *value;
3172 if (!next || noreplace) {
3173 len = strlen(arg);
3174 value = "";
3176 } else {
3177 value = format_arg(next);
3179 if (!value) {
3180 return FALSE;
3181 }
3182 }
3184 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3185 return FALSE;
3187 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3188 }
3190 dst_argv[argc] = strdup(buf);
3191 if (!dst_argv[argc])
3192 break;
3193 }
3195 dst_argv[argc] = NULL;
3197 return src_argv[argc] == NULL;
3198 }
3200 static bool
3201 restore_view_position(struct view *view)
3202 {
3203 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3204 return FALSE;
3206 /* Changing the view position cancels the restoring. */
3207 /* FIXME: Changing back to the first line is not detected. */
3208 if (view->offset != 0 || view->lineno != 0) {
3209 view->p_restore = FALSE;
3210 return FALSE;
3211 }
3213 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3214 view_is_displayed(view))
3215 werase(view->win);
3217 view->yoffset = view->p_yoffset;
3218 view->p_restore = FALSE;
3220 return TRUE;
3221 }
3223 static void
3224 end_update(struct view *view, bool force)
3225 {
3226 if (!view->pipe)
3227 return;
3228 while (!view->ops->read(view, NULL))
3229 if (!force)
3230 return;
3231 if (force)
3232 io_kill(view->pipe);
3233 io_done(view->pipe);
3234 view->pipe = NULL;
3235 }
3237 static void
3238 setup_update(struct view *view, const char *vid)
3239 {
3240 reset_view(view);
3241 string_copy_rev(view->vid, vid);
3242 view->pipe = &view->io;
3243 view->start_time = time(NULL);
3244 }
3246 static bool
3247 prepare_update(struct view *view, const char *argv[], const char *dir)
3248 {
3249 if (view->pipe)
3250 end_update(view, TRUE);
3251 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3252 }
3254 static bool
3255 prepare_update_file(struct view *view, const char *name)
3256 {
3257 if (view->pipe)
3258 end_update(view, TRUE);
3259 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3260 }
3262 static bool
3263 begin_update(struct view *view, bool refresh)
3264 {
3265 if (view->pipe)
3266 end_update(view, TRUE);
3268 if (!refresh) {
3269 if (view->ops->prepare) {
3270 if (!view->ops->prepare(view))
3271 return FALSE;
3272 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3273 return FALSE;
3274 }
3276 /* Put the current ref_* value to the view title ref
3277 * member. This is needed by the blob view. Most other
3278 * views sets it automatically after loading because the
3279 * first line is a commit line. */
3280 string_copy_rev(view->ref, view->id);
3281 }
3283 if (!io_start(&view->io))
3284 return FALSE;
3286 setup_update(view, view->id);
3288 return TRUE;
3289 }
3291 static bool
3292 update_view(struct view *view)
3293 {
3294 char out_buffer[BUFSIZ * 2];
3295 char *line;
3296 /* Clear the view and redraw everything since the tree sorting
3297 * might have rearranged things. */
3298 bool redraw = view->lines == 0;
3299 bool can_read = TRUE;
3301 if (!view->pipe)
3302 return TRUE;
3304 if (!io_can_read(view->pipe)) {
3305 if (view->lines == 0 && view_is_displayed(view)) {
3306 time_t secs = time(NULL) - view->start_time;
3308 if (secs > 1 && secs > view->update_secs) {
3309 if (view->update_secs == 0)
3310 redraw_view(view);
3311 update_view_title(view);
3312 view->update_secs = secs;
3313 }
3314 }
3315 return TRUE;
3316 }
3318 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3319 if (opt_iconv_in != ICONV_NONE) {
3320 ICONV_CONST char *inbuf = line;
3321 size_t inlen = strlen(line) + 1;
3323 char *outbuf = out_buffer;
3324 size_t outlen = sizeof(out_buffer);
3326 size_t ret;
3328 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3329 if (ret != (size_t) -1)
3330 line = out_buffer;
3331 }
3333 if (!view->ops->read(view, line)) {
3334 report("Allocation failure");
3335 end_update(view, TRUE);
3336 return FALSE;
3337 }
3338 }
3340 {
3341 unsigned long lines = view->lines;
3342 int digits;
3344 for (digits = 0; lines; digits++)
3345 lines /= 10;
3347 /* Keep the displayed view in sync with line number scaling. */
3348 if (digits != view->digits) {
3349 view->digits = digits;
3350 if (opt_line_number || view->type == VIEW_BLAME)
3351 redraw = TRUE;
3352 }
3353 }
3355 if (io_error(view->pipe)) {
3356 report("Failed to read: %s", io_strerror(view->pipe));
3357 end_update(view, TRUE);
3359 } else if (io_eof(view->pipe)) {
3360 report("");
3361 end_update(view, FALSE);
3362 }
3364 if (restore_view_position(view))
3365 redraw = TRUE;
3367 if (!view_is_displayed(view))
3368 return TRUE;
3370 if (redraw)
3371 redraw_view_from(view, 0);
3372 else
3373 redraw_view_dirty(view);
3375 /* Update the title _after_ the redraw so that if the redraw picks up a
3376 * commit reference in view->ref it'll be available here. */
3377 update_view_title(view);
3378 return TRUE;
3379 }
3381 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3383 static struct line *
3384 add_line_data(struct view *view, void *data, enum line_type type)
3385 {
3386 struct line *line;
3388 if (!realloc_lines(&view->line, view->lines, 1))
3389 return NULL;
3391 line = &view->line[view->lines++];
3392 memset(line, 0, sizeof(*line));
3393 line->type = type;
3394 line->data = data;
3395 line->dirty = 1;
3397 return line;
3398 }
3400 static struct line *
3401 add_line_text(struct view *view, const char *text, enum line_type type)
3402 {
3403 char *data = text ? strdup(text) : NULL;
3405 return data ? add_line_data(view, data, type) : NULL;
3406 }
3408 static struct line *
3409 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3410 {
3411 char buf[SIZEOF_STR];
3412 va_list args;
3414 va_start(args, fmt);
3415 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3416 buf[0] = 0;
3417 va_end(args);
3419 return buf[0] ? add_line_text(view, buf, type) : NULL;
3420 }
3422 /*
3423 * View opening
3424 */
3426 enum open_flags {
3427 OPEN_DEFAULT = 0, /* Use default view switching. */
3428 OPEN_SPLIT = 1, /* Split current view. */
3429 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3430 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3431 OPEN_PREPARED = 32, /* Open already prepared command. */
3432 };
3434 static void
3435 open_view(struct view *prev, enum request request, enum open_flags flags)
3436 {
3437 bool split = !!(flags & OPEN_SPLIT);
3438 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3439 bool nomaximize = !!(flags & OPEN_REFRESH);
3440 struct view *view = VIEW(request);
3441 int nviews = displayed_views();
3442 struct view *base_view = display[0];
3444 if (view == prev && nviews == 1 && !reload) {
3445 report("Already in %s view", view->name);
3446 return;
3447 }
3449 if (view->git_dir && !opt_git_dir[0]) {
3450 report("The %s view is disabled in pager view", view->name);
3451 return;
3452 }
3454 if (split) {
3455 display[1] = view;
3456 current_view = 1;
3457 } else if (!nomaximize) {
3458 /* Maximize the current view. */
3459 memset(display, 0, sizeof(display));
3460 current_view = 0;
3461 display[current_view] = view;
3462 }
3464 /* No parent signals that this is the first loaded view. */
3465 if (prev && view != prev) {
3466 view->parent = prev;
3467 }
3469 /* Resize the view when switching between split- and full-screen,
3470 * or when switching between two different full-screen views. */
3471 if (nviews != displayed_views() ||
3472 (nviews == 1 && base_view != display[0]))
3473 resize_display();
3475 if (view->ops->open) {
3476 if (view->pipe)
3477 end_update(view, TRUE);
3478 if (!view->ops->open(view)) {
3479 report("Failed to load %s view", view->name);
3480 return;
3481 }
3482 restore_view_position(view);
3484 } else if ((reload || strcmp(view->vid, view->id)) &&
3485 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3486 report("Failed to load %s view", view->name);
3487 return;
3488 }
3490 if (split && prev->lineno - prev->offset >= prev->height) {
3491 /* Take the title line into account. */
3492 int lines = prev->lineno - prev->offset - prev->height + 1;
3494 /* Scroll the view that was split if the current line is
3495 * outside the new limited view. */
3496 do_scroll_view(prev, lines);
3497 }
3499 if (prev && view != prev && split && view_is_displayed(prev)) {
3500 /* "Blur" the previous view. */
3501 update_view_title(prev);
3502 }
3504 if (view->pipe && view->lines == 0) {
3505 /* Clear the old view and let the incremental updating refill
3506 * the screen. */
3507 werase(view->win);
3508 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3509 report("");
3510 } else if (view_is_displayed(view)) {
3511 redraw_view(view);
3512 report("");
3513 }
3514 }
3516 static void
3517 open_external_viewer(const char *argv[], const char *dir)
3518 {
3519 def_prog_mode(); /* save current tty modes */
3520 endwin(); /* restore original tty modes */
3521 io_run_fg(argv, dir);
3522 fprintf(stderr, "Press Enter to continue");
3523 getc(opt_tty);
3524 reset_prog_mode();
3525 redraw_display(TRUE);
3526 }
3528 static void
3529 open_mergetool(const char *file)
3530 {
3531 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3533 open_external_viewer(mergetool_argv, opt_cdup);
3534 }
3536 static void
3537 open_editor(const char *file)
3538 {
3539 const char *editor_argv[] = { "vi", file, NULL };
3540 const char *editor;
3542 editor = getenv("GIT_EDITOR");
3543 if (!editor && *opt_editor)
3544 editor = opt_editor;
3545 if (!editor)
3546 editor = getenv("VISUAL");
3547 if (!editor)
3548 editor = getenv("EDITOR");
3549 if (!editor)
3550 editor = "vi";
3552 editor_argv[0] = editor;
3553 open_external_viewer(editor_argv, opt_cdup);
3554 }
3556 static void
3557 open_run_request(enum request request)
3558 {
3559 struct run_request *req = get_run_request(request);
3560 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3562 if (!req) {
3563 report("Unknown run request");
3564 return;
3565 }
3567 if (format_argv(argv, req->argv, FORMAT_ALL))
3568 open_external_viewer(argv, NULL);
3569 free_argv(argv);
3570 }
3572 /*
3573 * User request switch noodle
3574 */
3576 static int
3577 view_driver(struct view *view, enum request request)
3578 {
3579 int i;
3581 if (request == REQ_NONE)
3582 return TRUE;
3584 if (request > REQ_NONE) {
3585 open_run_request(request);
3586 /* FIXME: When all views can refresh always do this. */
3587 if (view->refresh)
3588 request = REQ_REFRESH;
3589 else
3590 return TRUE;
3591 }
3593 if (view && view->lines) {
3594 request = view->ops->request(view, request, &view->line[view->lineno]);
3595 if (request == REQ_NONE)
3596 return TRUE;
3597 }
3599 switch (request) {
3600 case REQ_MOVE_UP:
3601 case REQ_MOVE_DOWN:
3602 case REQ_MOVE_PAGE_UP:
3603 case REQ_MOVE_PAGE_DOWN:
3604 case REQ_MOVE_FIRST_LINE:
3605 case REQ_MOVE_LAST_LINE:
3606 move_view(view, request);
3607 break;
3609 case REQ_SCROLL_LEFT:
3610 case REQ_SCROLL_RIGHT:
3611 case REQ_SCROLL_LINE_DOWN:
3612 case REQ_SCROLL_LINE_UP:
3613 case REQ_SCROLL_PAGE_DOWN:
3614 case REQ_SCROLL_PAGE_UP:
3615 scroll_view(view, request);
3616 break;
3618 case REQ_VIEW_BLAME:
3619 if (!opt_file[0]) {
3620 report("No file chosen, press %s to open tree view",
3621 get_key(view->keymap, REQ_VIEW_TREE));
3622 break;
3623 }
3624 open_view(view, request, OPEN_DEFAULT);
3625 break;
3627 case REQ_VIEW_BLOB:
3628 if (!ref_blob[0]) {
3629 report("No file chosen, press %s to open tree view",
3630 get_key(view->keymap, REQ_VIEW_TREE));
3631 break;
3632 }
3633 open_view(view, request, OPEN_DEFAULT);
3634 break;
3636 case REQ_VIEW_PAGER:
3637 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3638 report("No pager content, press %s to run command from prompt",
3639 get_key(view->keymap, REQ_PROMPT));
3640 break;
3641 }
3642 open_view(view, request, OPEN_DEFAULT);
3643 break;
3645 case REQ_VIEW_STAGE:
3646 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3647 report("No stage content, press %s to open the status view and choose file",
3648 get_key(view->keymap, REQ_VIEW_STATUS));
3649 break;
3650 }
3651 open_view(view, request, OPEN_DEFAULT);
3652 break;
3654 case REQ_VIEW_STATUS:
3655 if (opt_is_inside_work_tree == FALSE) {
3656 report("The status view requires a working tree");
3657 break;
3658 }
3659 open_view(view, request, OPEN_DEFAULT);
3660 break;
3662 case REQ_VIEW_MAIN:
3663 case REQ_VIEW_DIFF:
3664 case REQ_VIEW_LOG:
3665 case REQ_VIEW_TREE:
3666 case REQ_VIEW_HELP:
3667 case REQ_VIEW_BRANCH:
3668 open_view(view, request, OPEN_DEFAULT);
3669 break;
3671 case REQ_NEXT:
3672 case REQ_PREVIOUS:
3673 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3675 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3676 view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3677 view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3678 view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3679 view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3680 int line;
3682 view = view->parent;
3683 line = view->lineno;
3684 move_view(view, request);
3685 if (view_is_displayed(view))
3686 update_view_title(view);
3687 if (line != view->lineno)
3688 view->ops->request(view, REQ_ENTER,
3689 &view->line[view->lineno]);
3691 } else {
3692 move_view(view, request);
3693 }
3694 break;
3696 case REQ_VIEW_NEXT:
3697 {
3698 int nviews = displayed_views();
3699 int next_view = (current_view + 1) % nviews;
3701 if (next_view == current_view) {
3702 report("Only one view is displayed");
3703 break;
3704 }
3706 current_view = next_view;
3707 /* Blur out the title of the previous view. */
3708 update_view_title(view);
3709 report("");
3710 break;
3711 }
3712 case REQ_REFRESH:
3713 report("Refreshing is not yet supported for the %s view", view->name);
3714 break;
3716 case REQ_MAXIMIZE:
3717 if (displayed_views() == 2)
3718 maximize_view(view);
3719 break;
3721 case REQ_OPTIONS:
3722 open_option_menu();
3723 break;
3725 case REQ_TOGGLE_LINENO:
3726 toggle_view_option(&opt_line_number, "line numbers");
3727 break;
3729 case REQ_TOGGLE_DATE:
3730 toggle_date();
3731 break;
3733 case REQ_TOGGLE_AUTHOR:
3734 toggle_author();
3735 break;
3737 case REQ_TOGGLE_REV_GRAPH:
3738 toggle_view_option(&opt_rev_graph, "revision graph display");
3739 break;
3741 case REQ_TOGGLE_REFS:
3742 toggle_view_option(&opt_show_refs, "reference display");
3743 break;
3745 case REQ_TOGGLE_SORT_FIELD:
3746 case REQ_TOGGLE_SORT_ORDER:
3747 report("Sorting is not yet supported for the %s view", view->name);
3748 break;
3750 case REQ_SEARCH:
3751 case REQ_SEARCH_BACK:
3752 search_view(view, request);
3753 break;
3755 case REQ_FIND_NEXT:
3756 case REQ_FIND_PREV:
3757 find_next(view, request);
3758 break;
3760 case REQ_STOP_LOADING:
3761 foreach_view(view, i) {
3762 if (view->pipe)
3763 report("Stopped loading the %s view", view->name),
3764 end_update(view, TRUE);
3765 }
3766 break;
3768 case REQ_SHOW_VERSION:
3769 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3770 return TRUE;
3772 case REQ_SCREEN_REDRAW:
3773 redraw_display(TRUE);
3774 break;
3776 case REQ_EDIT:
3777 report("Nothing to edit");
3778 break;
3780 case REQ_ENTER:
3781 report("Nothing to enter");
3782 break;
3784 case REQ_VIEW_CLOSE:
3785 /* XXX: Mark closed views by letting view->parent point to the
3786 * view itself. Parents to closed view should never be
3787 * followed. */
3788 if (view->parent &&
3789 view->parent->parent != view->parent) {
3790 maximize_view(view->parent);
3791 view->parent = view;
3792 break;
3793 }
3794 /* Fall-through */
3795 case REQ_QUIT:
3796 return FALSE;
3798 default:
3799 report("Unknown key, press %s for help",
3800 get_key(view->keymap, REQ_VIEW_HELP));
3801 return TRUE;
3802 }
3804 return TRUE;
3805 }
3808 /*
3809 * View backend utilities
3810 */
3812 enum sort_field {
3813 ORDERBY_NAME,
3814 ORDERBY_DATE,
3815 ORDERBY_AUTHOR,
3816 };
3818 struct sort_state {
3819 const enum sort_field *fields;
3820 size_t size, current;
3821 bool reverse;
3822 };
3824 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3825 #define get_sort_field(state) ((state).fields[(state).current])
3826 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3828 static void
3829 sort_view(struct view *view, enum request request, struct sort_state *state,
3830 int (*compare)(const void *, const void *))
3831 {
3832 switch (request) {
3833 case REQ_TOGGLE_SORT_FIELD:
3834 state->current = (state->current + 1) % state->size;
3835 break;
3837 case REQ_TOGGLE_SORT_ORDER:
3838 state->reverse = !state->reverse;
3839 break;
3840 default:
3841 die("Not a sort request");
3842 }
3844 qsort(view->line, view->lines, sizeof(*view->line), compare);
3845 redraw_view(view);
3846 }
3848 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3850 /* Small author cache to reduce memory consumption. It uses binary
3851 * search to lookup or find place to position new entries. No entries
3852 * are ever freed. */
3853 static const char *
3854 get_author(const char *name)
3855 {
3856 static const char **authors;
3857 static size_t authors_size;
3858 int from = 0, to = authors_size - 1;
3860 while (from <= to) {
3861 size_t pos = (to + from) / 2;
3862 int cmp = strcmp(name, authors[pos]);
3864 if (!cmp)
3865 return authors[pos];
3867 if (cmp < 0)
3868 to = pos - 1;
3869 else
3870 from = pos + 1;
3871 }
3873 if (!realloc_authors(&authors, authors_size, 1))
3874 return NULL;
3875 name = strdup(name);
3876 if (!name)
3877 return NULL;
3879 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3880 authors[from] = name;
3881 authors_size++;
3883 return name;
3884 }
3886 static void
3887 parse_timesec(struct time *time, const char *sec)
3888 {
3889 time->sec = (time_t) atol(sec);
3890 }
3892 static void
3893 parse_timezone(struct time *time, const char *zone)
3894 {
3895 long tz;
3897 tz = ('0' - zone[1]) * 60 * 60 * 10;
3898 tz += ('0' - zone[2]) * 60 * 60;
3899 tz += ('0' - zone[3]) * 60;
3900 tz += ('0' - zone[4]);
3902 if (zone[0] == '-')
3903 tz = -tz;
3905 time->tz = tz;
3906 time->sec -= tz;
3907 }
3909 /* Parse author lines where the name may be empty:
3910 * author <email@address.tld> 1138474660 +0100
3911 */
3912 static void
3913 parse_author_line(char *ident, const char **author, struct time *time)
3914 {
3915 char *nameend = strchr(ident, '<');
3916 char *emailend = strchr(ident, '>');
3918 if (nameend && emailend)
3919 *nameend = *emailend = 0;
3920 ident = chomp_string(ident);
3921 if (!*ident) {
3922 if (nameend)
3923 ident = chomp_string(nameend + 1);
3924 if (!*ident)
3925 ident = "Unknown";
3926 }
3928 *author = get_author(ident);
3930 /* Parse epoch and timezone */
3931 if (emailend && emailend[1] == ' ') {
3932 char *secs = emailend + 2;
3933 char *zone = strchr(secs, ' ');
3935 parse_timesec(time, secs);
3937 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3938 parse_timezone(time, zone + 1);
3939 }
3940 }
3942 static bool
3943 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3944 {
3945 char rev[SIZEOF_REV];
3946 const char *revlist_argv[] = {
3947 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3948 };
3949 struct menu_item *items;
3950 char text[SIZEOF_STR];
3951 bool ok = TRUE;
3952 int i;
3954 items = calloc(*parents + 1, sizeof(*items));
3955 if (!items)
3956 return FALSE;
3958 for (i = 0; i < *parents; i++) {
3959 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3960 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3961 !(items[i].text = strdup(text))) {
3962 ok = FALSE;
3963 break;
3964 }
3965 }
3967 if (ok) {
3968 *parents = 0;
3969 ok = prompt_menu("Select parent", items, parents);
3970 }
3971 for (i = 0; items[i].text; i++)
3972 free((char *) items[i].text);
3973 free(items);
3974 return ok;
3975 }
3977 static bool
3978 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3979 {
3980 char buf[SIZEOF_STR * 4];
3981 const char *revlist_argv[] = {
3982 "git", "log", "--no-color", "-1",
3983 "--pretty=format:%P", id, "--", path, NULL
3984 };
3985 int parents;
3987 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3988 (parents = strlen(buf) / 40) < 0) {
3989 report("Failed to get parent information");
3990 return FALSE;
3992 } else if (parents == 0) {
3993 if (path)
3994 report("Path '%s' does not exist in the parent", path);
3995 else
3996 report("The selected commit has no parents");
3997 return FALSE;
3998 }
4000 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4001 return FALSE;
4003 string_copy_rev(rev, &buf[41 * parents]);
4004 return TRUE;
4005 }
4007 /*
4008 * Pager backend
4009 */
4011 static bool
4012 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4013 {
4014 char text[SIZEOF_STR];
4016 if (opt_line_number && draw_lineno(view, lineno))
4017 return TRUE;
4019 string_expand(text, sizeof(text), line->data, opt_tab_size);
4020 draw_text(view, line->type, text, TRUE);
4021 return TRUE;
4022 }
4024 static bool
4025 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4026 {
4027 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4028 char ref[SIZEOF_STR];
4030 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4031 return TRUE;
4033 /* This is the only fatal call, since it can "corrupt" the buffer. */
4034 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4035 return FALSE;
4037 return TRUE;
4038 }
4040 static void
4041 add_pager_refs(struct view *view, struct line *line)
4042 {
4043 char buf[SIZEOF_STR];
4044 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4045 struct ref_list *list;
4046 size_t bufpos = 0, i;
4047 const char *sep = "Refs: ";
4048 bool is_tag = FALSE;
4050 assert(line->type == LINE_COMMIT);
4052 list = get_ref_list(commit_id);
4053 if (!list) {
4054 if (view->type == VIEW_DIFF)
4055 goto try_add_describe_ref;
4056 return;
4057 }
4059 for (i = 0; i < list->size; i++) {
4060 struct ref *ref = list->refs[i];
4061 const char *fmt = ref->tag ? "%s[%s]" :
4062 ref->remote ? "%s<%s>" : "%s%s";
4064 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4065 return;
4066 sep = ", ";
4067 if (ref->tag)
4068 is_tag = TRUE;
4069 }
4071 if (!is_tag && view->type == VIEW_DIFF) {
4072 try_add_describe_ref:
4073 /* Add <tag>-g<commit_id> "fake" reference. */
4074 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4075 return;
4076 }
4078 if (bufpos == 0)
4079 return;
4081 add_line_text(view, buf, LINE_PP_REFS);
4082 }
4084 static bool
4085 pager_read(struct view *view, char *data)
4086 {
4087 struct line *line;
4089 if (!data)
4090 return TRUE;
4092 line = add_line_text(view, data, get_line_type(data));
4093 if (!line)
4094 return FALSE;
4096 if (line->type == LINE_COMMIT &&
4097 (view->type == VIEW_DIFF ||
4098 view->type == VIEW_LOG))
4099 add_pager_refs(view, line);
4101 return TRUE;
4102 }
4104 static enum request
4105 pager_request(struct view *view, enum request request, struct line *line)
4106 {
4107 int split = 0;
4109 if (request != REQ_ENTER)
4110 return request;
4112 if (line->type == LINE_COMMIT &&
4113 (view->type == VIEW_LOG ||
4114 view->type == VIEW_PAGER)) {
4115 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4116 split = 1;
4117 }
4119 /* Always scroll the view even if it was split. That way
4120 * you can use Enter to scroll through the log view and
4121 * split open each commit diff. */
4122 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4124 /* FIXME: A minor workaround. Scrolling the view will call report("")
4125 * but if we are scrolling a non-current view this won't properly
4126 * update the view title. */
4127 if (split)
4128 update_view_title(view);
4130 return REQ_NONE;
4131 }
4133 static bool
4134 pager_grep(struct view *view, struct line *line)
4135 {
4136 const char *text[] = { line->data, NULL };
4138 return grep_text(view, text);
4139 }
4141 static void
4142 pager_select(struct view *view, struct line *line)
4143 {
4144 if (line->type == LINE_COMMIT) {
4145 char *text = (char *)line->data + STRING_SIZE("commit ");
4147 if (view->type != VIEW_PAGER)
4148 string_copy_rev(view->ref, text);
4149 string_copy_rev(ref_commit, text);
4150 }
4151 }
4153 static struct view_ops pager_ops = {
4154 "line",
4155 NULL,
4156 NULL,
4157 pager_read,
4158 pager_draw,
4159 pager_request,
4160 pager_grep,
4161 pager_select,
4162 };
4164 static const char *log_argv[SIZEOF_ARG] = {
4165 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4166 };
4168 static enum request
4169 log_request(struct view *view, enum request request, struct line *line)
4170 {
4171 switch (request) {
4172 case REQ_REFRESH:
4173 load_refs();
4174 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4175 return REQ_NONE;
4176 default:
4177 return pager_request(view, request, line);
4178 }
4179 }
4181 static struct view_ops log_ops = {
4182 "line",
4183 log_argv,
4184 NULL,
4185 pager_read,
4186 pager_draw,
4187 log_request,
4188 pager_grep,
4189 pager_select,
4190 };
4192 static const char *diff_argv[SIZEOF_ARG] = {
4193 "git", "show", "--pretty=fuller", "--no-color", "--root",
4194 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4195 };
4197 static struct view_ops diff_ops = {
4198 "line",
4199 diff_argv,
4200 NULL,
4201 pager_read,
4202 pager_draw,
4203 pager_request,
4204 pager_grep,
4205 pager_select,
4206 };
4208 /*
4209 * Help backend
4210 */
4212 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4214 static bool
4215 help_open_keymap_title(struct view *view, enum keymap keymap)
4216 {
4217 struct line *line;
4219 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4220 help_keymap_hidden[keymap] ? '+' : '-',
4221 enum_name(keymap_table[keymap]));
4222 if (line)
4223 line->other = keymap;
4225 return help_keymap_hidden[keymap];
4226 }
4228 static void
4229 help_open_keymap(struct view *view, enum keymap keymap)
4230 {
4231 const char *group = NULL;
4232 char buf[SIZEOF_STR];
4233 size_t bufpos;
4234 bool add_title = TRUE;
4235 int i;
4237 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4238 const char *key = NULL;
4240 if (req_info[i].request == REQ_NONE)
4241 continue;
4243 if (!req_info[i].request) {
4244 group = req_info[i].help;
4245 continue;
4246 }
4248 key = get_keys(keymap, req_info[i].request, TRUE);
4249 if (!key || !*key)
4250 continue;
4252 if (add_title && help_open_keymap_title(view, keymap))
4253 return;
4254 add_title = FALSE;
4256 if (group) {
4257 add_line_text(view, group, LINE_HELP_GROUP);
4258 group = NULL;
4259 }
4261 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4262 enum_name(req_info[i]), req_info[i].help);
4263 }
4265 group = "External commands:";
4267 for (i = 0; i < run_requests; i++) {
4268 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4269 const char *key;
4270 int argc;
4272 if (!req || req->keymap != keymap)
4273 continue;
4275 key = get_key_name(req->key);
4276 if (!*key)
4277 key = "(no key defined)";
4279 if (add_title && help_open_keymap_title(view, keymap))
4280 return;
4281 if (group) {
4282 add_line_text(view, group, LINE_HELP_GROUP);
4283 group = NULL;
4284 }
4286 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4287 if (!string_format_from(buf, &bufpos, "%s%s",
4288 argc ? " " : "", req->argv[argc]))
4289 return;
4291 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4292 }
4293 }
4295 static bool
4296 help_open(struct view *view)
4297 {
4298 enum keymap keymap;
4300 reset_view(view);
4301 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4302 add_line_text(view, "", LINE_DEFAULT);
4304 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4305 help_open_keymap(view, keymap);
4307 return TRUE;
4308 }
4310 static enum request
4311 help_request(struct view *view, enum request request, struct line *line)
4312 {
4313 switch (request) {
4314 case REQ_ENTER:
4315 if (line->type == LINE_HELP_KEYMAP) {
4316 help_keymap_hidden[line->other] =
4317 !help_keymap_hidden[line->other];
4318 view->p_restore = TRUE;
4319 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4320 }
4322 return REQ_NONE;
4323 default:
4324 return pager_request(view, request, line);
4325 }
4326 }
4328 static struct view_ops help_ops = {
4329 "line",
4330 NULL,
4331 help_open,
4332 NULL,
4333 pager_draw,
4334 help_request,
4335 pager_grep,
4336 pager_select,
4337 };
4340 /*
4341 * Tree backend
4342 */
4344 struct tree_stack_entry {
4345 struct tree_stack_entry *prev; /* Entry below this in the stack */
4346 unsigned long lineno; /* Line number to restore */
4347 char *name; /* Position of name in opt_path */
4348 };
4350 /* The top of the path stack. */
4351 static struct tree_stack_entry *tree_stack = NULL;
4352 unsigned long tree_lineno = 0;
4354 static void
4355 pop_tree_stack_entry(void)
4356 {
4357 struct tree_stack_entry *entry = tree_stack;
4359 tree_lineno = entry->lineno;
4360 entry->name[0] = 0;
4361 tree_stack = entry->prev;
4362 free(entry);
4363 }
4365 static void
4366 push_tree_stack_entry(const char *name, unsigned long lineno)
4367 {
4368 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4369 size_t pathlen = strlen(opt_path);
4371 if (!entry)
4372 return;
4374 entry->prev = tree_stack;
4375 entry->name = opt_path + pathlen;
4376 tree_stack = entry;
4378 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4379 pop_tree_stack_entry();
4380 return;
4381 }
4383 /* Move the current line to the first tree entry. */
4384 tree_lineno = 1;
4385 entry->lineno = lineno;
4386 }
4388 /* Parse output from git-ls-tree(1):
4389 *
4390 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4391 */
4393 #define SIZEOF_TREE_ATTR \
4394 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4396 #define SIZEOF_TREE_MODE \
4397 STRING_SIZE("100644 ")
4399 #define TREE_ID_OFFSET \
4400 STRING_SIZE("100644 blob ")
4402 struct tree_entry {
4403 char id[SIZEOF_REV];
4404 mode_t mode;
4405 struct time time; /* Date from the author ident. */
4406 const char *author; /* Author of the commit. */
4407 char name[1];
4408 };
4410 static const char *
4411 tree_path(const struct line *line)
4412 {
4413 return ((struct tree_entry *) line->data)->name;
4414 }
4416 static int
4417 tree_compare_entry(const struct line *line1, const struct line *line2)
4418 {
4419 if (line1->type != line2->type)
4420 return line1->type == LINE_TREE_DIR ? -1 : 1;
4421 return strcmp(tree_path(line1), tree_path(line2));
4422 }
4424 static const enum sort_field tree_sort_fields[] = {
4425 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4426 };
4427 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4429 static int
4430 tree_compare(const void *l1, const void *l2)
4431 {
4432 const struct line *line1 = (const struct line *) l1;
4433 const struct line *line2 = (const struct line *) l2;
4434 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4435 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4437 if (line1->type == LINE_TREE_HEAD)
4438 return -1;
4439 if (line2->type == LINE_TREE_HEAD)
4440 return 1;
4442 switch (get_sort_field(tree_sort_state)) {
4443 case ORDERBY_DATE:
4444 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4446 case ORDERBY_AUTHOR:
4447 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4449 case ORDERBY_NAME:
4450 default:
4451 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4452 }
4453 }
4456 static struct line *
4457 tree_entry(struct view *view, enum line_type type, const char *path,
4458 const char *mode, const char *id)
4459 {
4460 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4461 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4463 if (!entry || !line) {
4464 free(entry);
4465 return NULL;
4466 }
4468 strncpy(entry->name, path, strlen(path));
4469 if (mode)
4470 entry->mode = strtoul(mode, NULL, 8);
4471 if (id)
4472 string_copy_rev(entry->id, id);
4474 return line;
4475 }
4477 static bool
4478 tree_read_date(struct view *view, char *text, bool *read_date)
4479 {
4480 static const char *author_name;
4481 static struct time author_time;
4483 if (!text && *read_date) {
4484 *read_date = FALSE;
4485 return TRUE;
4487 } else if (!text) {
4488 char *path = *opt_path ? opt_path : ".";
4489 /* Find next entry to process */
4490 const char *log_file[] = {
4491 "git", "log", "--no-color", "--pretty=raw",
4492 "--cc", "--raw", view->id, "--", path, NULL
4493 };
4494 struct io io = {};
4496 if (!view->lines) {
4497 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4498 report("Tree is empty");
4499 return TRUE;
4500 }
4502 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4503 report("Failed to load tree data");
4504 return TRUE;
4505 }
4507 io_done(view->pipe);
4508 view->io = io;
4509 *read_date = TRUE;
4510 return FALSE;
4512 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4513 parse_author_line(text + STRING_SIZE("author "),
4514 &author_name, &author_time);
4516 } else if (*text == ':') {
4517 char *pos;
4518 size_t annotated = 1;
4519 size_t i;
4521 pos = strchr(text, '\t');
4522 if (!pos)
4523 return TRUE;
4524 text = pos + 1;
4525 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4526 text += strlen(opt_path);
4527 pos = strchr(text, '/');
4528 if (pos)
4529 *pos = 0;
4531 for (i = 1; i < view->lines; i++) {
4532 struct line *line = &view->line[i];
4533 struct tree_entry *entry = line->data;
4535 annotated += !!entry->author;
4536 if (entry->author || strcmp(entry->name, text))
4537 continue;
4539 entry->author = author_name;
4540 entry->time = author_time;
4541 line->dirty = 1;
4542 break;
4543 }
4545 if (annotated == view->lines)
4546 io_kill(view->pipe);
4547 }
4548 return TRUE;
4549 }
4551 static bool
4552 tree_read(struct view *view, char *text)
4553 {
4554 static bool read_date = FALSE;
4555 struct tree_entry *data;
4556 struct line *entry, *line;
4557 enum line_type type;
4558 size_t textlen = text ? strlen(text) : 0;
4559 char *path = text + SIZEOF_TREE_ATTR;
4561 if (read_date || !text)
4562 return tree_read_date(view, text, &read_date);
4564 if (textlen <= SIZEOF_TREE_ATTR)
4565 return FALSE;
4566 if (view->lines == 0 &&
4567 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4568 return FALSE;
4570 /* Strip the path part ... */
4571 if (*opt_path) {
4572 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4573 size_t striplen = strlen(opt_path);
4575 if (pathlen > striplen)
4576 memmove(path, path + striplen,
4577 pathlen - striplen + 1);
4579 /* Insert "link" to parent directory. */
4580 if (view->lines == 1 &&
4581 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4582 return FALSE;
4583 }
4585 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4586 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4587 if (!entry)
4588 return FALSE;
4589 data = entry->data;
4591 /* Skip "Directory ..." and ".." line. */
4592 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4593 if (tree_compare_entry(line, entry) <= 0)
4594 continue;
4596 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4598 line->data = data;
4599 line->type = type;
4600 for (; line <= entry; line++)
4601 line->dirty = line->cleareol = 1;
4602 return TRUE;
4603 }
4605 if (tree_lineno > view->lineno) {
4606 view->lineno = tree_lineno;
4607 tree_lineno = 0;
4608 }
4610 return TRUE;
4611 }
4613 static bool
4614 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4615 {
4616 struct tree_entry *entry = line->data;
4618 if (line->type == LINE_TREE_HEAD) {
4619 if (draw_text(view, line->type, "Directory path /", TRUE))
4620 return TRUE;
4621 } else {
4622 if (draw_mode(view, entry->mode))
4623 return TRUE;
4625 if (opt_author && draw_author(view, entry->author))
4626 return TRUE;
4628 if (opt_date && draw_date(view, &entry->time))
4629 return TRUE;
4630 }
4631 if (draw_text(view, line->type, entry->name, TRUE))
4632 return TRUE;
4633 return TRUE;
4634 }
4636 static void
4637 open_blob_editor()
4638 {
4639 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4640 int fd = mkstemp(file);
4642 if (fd == -1)
4643 report("Failed to create temporary file");
4644 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4645 report("Failed to save blob data to file");
4646 else
4647 open_editor(file);
4648 if (fd != -1)
4649 unlink(file);
4650 }
4652 static enum request
4653 tree_request(struct view *view, enum request request, struct line *line)
4654 {
4655 enum open_flags flags;
4657 switch (request) {
4658 case REQ_VIEW_BLAME:
4659 if (line->type != LINE_TREE_FILE) {
4660 report("Blame only supported for files");
4661 return REQ_NONE;
4662 }
4664 string_copy(opt_ref, view->vid);
4665 return request;
4667 case REQ_EDIT:
4668 if (line->type != LINE_TREE_FILE) {
4669 report("Edit only supported for files");
4670 } else if (!is_head_commit(view->vid)) {
4671 open_blob_editor();
4672 } else {
4673 open_editor(opt_file);
4674 }
4675 return REQ_NONE;
4677 case REQ_TOGGLE_SORT_FIELD:
4678 case REQ_TOGGLE_SORT_ORDER:
4679 sort_view(view, request, &tree_sort_state, tree_compare);
4680 return REQ_NONE;
4682 case REQ_PARENT:
4683 if (!*opt_path) {
4684 /* quit view if at top of tree */
4685 return REQ_VIEW_CLOSE;
4686 }
4687 /* fake 'cd ..' */
4688 line = &view->line[1];
4689 break;
4691 case REQ_ENTER:
4692 break;
4694 default:
4695 return request;
4696 }
4698 /* Cleanup the stack if the tree view is at a different tree. */
4699 while (!*opt_path && tree_stack)
4700 pop_tree_stack_entry();
4702 switch (line->type) {
4703 case LINE_TREE_DIR:
4704 /* Depending on whether it is a subdirectory or parent link
4705 * mangle the path buffer. */
4706 if (line == &view->line[1] && *opt_path) {
4707 pop_tree_stack_entry();
4709 } else {
4710 const char *basename = tree_path(line);
4712 push_tree_stack_entry(basename, view->lineno);
4713 }
4715 /* Trees and subtrees share the same ID, so they are not not
4716 * unique like blobs. */
4717 flags = OPEN_RELOAD;
4718 request = REQ_VIEW_TREE;
4719 break;
4721 case LINE_TREE_FILE:
4722 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4723 request = REQ_VIEW_BLOB;
4724 break;
4726 default:
4727 return REQ_NONE;
4728 }
4730 open_view(view, request, flags);
4731 if (request == REQ_VIEW_TREE)
4732 view->lineno = tree_lineno;
4734 return REQ_NONE;
4735 }
4737 static bool
4738 tree_grep(struct view *view, struct line *line)
4739 {
4740 struct tree_entry *entry = line->data;
4741 const char *text[] = {
4742 entry->name,
4743 opt_author ? entry->author : "",
4744 mkdate(&entry->time, opt_date),
4745 NULL
4746 };
4748 return grep_text(view, text);
4749 }
4751 static void
4752 tree_select(struct view *view, struct line *line)
4753 {
4754 struct tree_entry *entry = line->data;
4756 if (line->type == LINE_TREE_FILE) {
4757 string_copy_rev(ref_blob, entry->id);
4758 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4760 } else if (line->type != LINE_TREE_DIR) {
4761 return;
4762 }
4764 string_copy_rev(view->ref, entry->id);
4765 }
4767 static bool
4768 tree_prepare(struct view *view)
4769 {
4770 if (view->lines == 0 && opt_prefix[0]) {
4771 char *pos = opt_prefix;
4773 while (pos && *pos) {
4774 char *end = strchr(pos, '/');
4776 if (end)
4777 *end = 0;
4778 push_tree_stack_entry(pos, 0);
4779 pos = end;
4780 if (end) {
4781 *end = '/';
4782 pos++;
4783 }
4784 }
4786 } else if (strcmp(view->vid, view->id)) {
4787 opt_path[0] = 0;
4788 }
4790 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4791 }
4793 static const char *tree_argv[SIZEOF_ARG] = {
4794 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4795 };
4797 static struct view_ops tree_ops = {
4798 "file",
4799 tree_argv,
4800 NULL,
4801 tree_read,
4802 tree_draw,
4803 tree_request,
4804 tree_grep,
4805 tree_select,
4806 tree_prepare,
4807 };
4809 static bool
4810 blob_read(struct view *view, char *line)
4811 {
4812 if (!line)
4813 return TRUE;
4814 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4815 }
4817 static enum request
4818 blob_request(struct view *view, enum request request, struct line *line)
4819 {
4820 switch (request) {
4821 case REQ_EDIT:
4822 open_blob_editor();
4823 return REQ_NONE;
4824 default:
4825 return pager_request(view, request, line);
4826 }
4827 }
4829 static const char *blob_argv[SIZEOF_ARG] = {
4830 "git", "cat-file", "blob", "%(blob)", NULL
4831 };
4833 static struct view_ops blob_ops = {
4834 "line",
4835 blob_argv,
4836 NULL,
4837 blob_read,
4838 pager_draw,
4839 blob_request,
4840 pager_grep,
4841 pager_select,
4842 };
4844 /*
4845 * Blame backend
4846 *
4847 * Loading the blame view is a two phase job:
4848 *
4849 * 1. File content is read either using opt_file from the
4850 * filesystem or using git-cat-file.
4851 * 2. Then blame information is incrementally added by
4852 * reading output from git-blame.
4853 */
4855 static const char *blame_head_argv[] = {
4856 "git", "blame", "--incremental", "--", "%(file)", NULL
4857 };
4859 static const char *blame_ref_argv[] = {
4860 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4861 };
4863 static const char *blame_cat_file_argv[] = {
4864 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4865 };
4867 struct blame_commit {
4868 char id[SIZEOF_REV]; /* SHA1 ID. */
4869 char title[128]; /* First line of the commit message. */
4870 const char *author; /* Author of the commit. */
4871 struct time time; /* Date from the author ident. */
4872 char filename[128]; /* Name of file. */
4873 bool has_previous; /* Was a "previous" line detected. */
4874 };
4876 struct blame {
4877 struct blame_commit *commit;
4878 unsigned long lineno;
4879 char text[1];
4880 };
4882 static bool
4883 blame_open(struct view *view)
4884 {
4885 char path[SIZEOF_STR];
4887 if (!view->parent && *opt_prefix) {
4888 string_copy(path, opt_file);
4889 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4890 return FALSE;
4891 }
4893 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4894 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4895 return FALSE;
4896 }
4898 setup_update(view, opt_file);
4899 string_format(view->ref, "%s ...", opt_file);
4901 return TRUE;
4902 }
4904 static struct blame_commit *
4905 get_blame_commit(struct view *view, const char *id)
4906 {
4907 size_t i;
4909 for (i = 0; i < view->lines; i++) {
4910 struct blame *blame = view->line[i].data;
4912 if (!blame->commit)
4913 continue;
4915 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4916 return blame->commit;
4917 }
4919 {
4920 struct blame_commit *commit = calloc(1, sizeof(*commit));
4922 if (commit)
4923 string_ncopy(commit->id, id, SIZEOF_REV);
4924 return commit;
4925 }
4926 }
4928 static bool
4929 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4930 {
4931 const char *pos = *posref;
4933 *posref = NULL;
4934 pos = strchr(pos + 1, ' ');
4935 if (!pos || !isdigit(pos[1]))
4936 return FALSE;
4937 *number = atoi(pos + 1);
4938 if (*number < min || *number > max)
4939 return FALSE;
4941 *posref = pos;
4942 return TRUE;
4943 }
4945 static struct blame_commit *
4946 parse_blame_commit(struct view *view, const char *text, int *blamed)
4947 {
4948 struct blame_commit *commit;
4949 struct blame *blame;
4950 const char *pos = text + SIZEOF_REV - 2;
4951 size_t orig_lineno = 0;
4952 size_t lineno;
4953 size_t group;
4955 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4956 return NULL;
4958 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4959 !parse_number(&pos, &lineno, 1, view->lines) ||
4960 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4961 return NULL;
4963 commit = get_blame_commit(view, text);
4964 if (!commit)
4965 return NULL;
4967 *blamed += group;
4968 while (group--) {
4969 struct line *line = &view->line[lineno + group - 1];
4971 blame = line->data;
4972 blame->commit = commit;
4973 blame->lineno = orig_lineno + group - 1;
4974 line->dirty = 1;
4975 }
4977 return commit;
4978 }
4980 static bool
4981 blame_read_file(struct view *view, const char *line, bool *read_file)
4982 {
4983 if (!line) {
4984 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4985 struct io io = {};
4987 if (view->lines == 0 && !view->parent)
4988 die("No blame exist for %s", view->vid);
4990 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4991 report("Failed to load blame data");
4992 return TRUE;
4993 }
4995 io_done(view->pipe);
4996 view->io = io;
4997 *read_file = FALSE;
4998 return FALSE;
5000 } else {
5001 size_t linelen = strlen(line);
5002 struct blame *blame = malloc(sizeof(*blame) + linelen);
5004 if (!blame)
5005 return FALSE;
5007 blame->commit = NULL;
5008 strncpy(blame->text, line, linelen);
5009 blame->text[linelen] = 0;
5010 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5011 }
5012 }
5014 static bool
5015 match_blame_header(const char *name, char **line)
5016 {
5017 size_t namelen = strlen(name);
5018 bool matched = !strncmp(name, *line, namelen);
5020 if (matched)
5021 *line += namelen;
5023 return matched;
5024 }
5026 static bool
5027 blame_read(struct view *view, char *line)
5028 {
5029 static struct blame_commit *commit = NULL;
5030 static int blamed = 0;
5031 static bool read_file = TRUE;
5033 if (read_file)
5034 return blame_read_file(view, line, &read_file);
5036 if (!line) {
5037 /* Reset all! */
5038 commit = NULL;
5039 blamed = 0;
5040 read_file = TRUE;
5041 string_format(view->ref, "%s", view->vid);
5042 if (view_is_displayed(view)) {
5043 update_view_title(view);
5044 redraw_view_from(view, 0);
5045 }
5046 return TRUE;
5047 }
5049 if (!commit) {
5050 commit = parse_blame_commit(view, line, &blamed);
5051 string_format(view->ref, "%s %2d%%", view->vid,
5052 view->lines ? blamed * 100 / view->lines : 0);
5054 } else if (match_blame_header("author ", &line)) {
5055 commit->author = get_author(line);
5057 } else if (match_blame_header("author-time ", &line)) {
5058 parse_timesec(&commit->time, line);
5060 } else if (match_blame_header("author-tz ", &line)) {
5061 parse_timezone(&commit->time, line);
5063 } else if (match_blame_header("summary ", &line)) {
5064 string_ncopy(commit->title, line, strlen(line));
5066 } else if (match_blame_header("previous ", &line)) {
5067 commit->has_previous = TRUE;
5069 } else if (match_blame_header("filename ", &line)) {
5070 string_ncopy(commit->filename, line, strlen(line));
5071 commit = NULL;
5072 }
5074 return TRUE;
5075 }
5077 static bool
5078 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5079 {
5080 struct blame *blame = line->data;
5081 struct time *time = NULL;
5082 const char *id = NULL, *author = NULL;
5083 char text[SIZEOF_STR];
5085 if (blame->commit && *blame->commit->filename) {
5086 id = blame->commit->id;
5087 author = blame->commit->author;
5088 time = &blame->commit->time;
5089 }
5091 if (opt_date && draw_date(view, time))
5092 return TRUE;
5094 if (opt_author && draw_author(view, author))
5095 return TRUE;
5097 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5098 return TRUE;
5100 if (draw_lineno(view, lineno))
5101 return TRUE;
5103 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5104 draw_text(view, LINE_DEFAULT, text, TRUE);
5105 return TRUE;
5106 }
5108 static bool
5109 check_blame_commit(struct blame *blame, bool check_null_id)
5110 {
5111 if (!blame->commit)
5112 report("Commit data not loaded yet");
5113 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5114 report("No commit exist for the selected line");
5115 else
5116 return TRUE;
5117 return FALSE;
5118 }
5120 static void
5121 setup_blame_parent_line(struct view *view, struct blame *blame)
5122 {
5123 const char *diff_tree_argv[] = {
5124 "git", "diff-tree", "-U0", blame->commit->id,
5125 "--", blame->commit->filename, NULL
5126 };
5127 struct io io = {};
5128 int parent_lineno = -1;
5129 int blamed_lineno = -1;
5130 char *line;
5132 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5133 return;
5135 while ((line = io_get(&io, '\n', TRUE))) {
5136 if (*line == '@') {
5137 char *pos = strchr(line, '+');
5139 parent_lineno = atoi(line + 4);
5140 if (pos)
5141 blamed_lineno = atoi(pos + 1);
5143 } else if (*line == '+' && parent_lineno != -1) {
5144 if (blame->lineno == blamed_lineno - 1 &&
5145 !strcmp(blame->text, line + 1)) {
5146 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5147 break;
5148 }
5149 blamed_lineno++;
5150 }
5151 }
5153 io_done(&io);
5154 }
5156 static enum request
5157 blame_request(struct view *view, enum request request, struct line *line)
5158 {
5159 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5160 struct blame *blame = line->data;
5162 switch (request) {
5163 case REQ_VIEW_BLAME:
5164 if (check_blame_commit(blame, TRUE)) {
5165 string_copy(opt_ref, blame->commit->id);
5166 string_copy(opt_file, blame->commit->filename);
5167 if (blame->lineno)
5168 view->lineno = blame->lineno;
5169 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5170 }
5171 break;
5173 case REQ_PARENT:
5174 if (check_blame_commit(blame, TRUE) &&
5175 select_commit_parent(blame->commit->id, opt_ref,
5176 blame->commit->filename)) {
5177 string_copy(opt_file, blame->commit->filename);
5178 setup_blame_parent_line(view, blame);
5179 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5180 }
5181 break;
5183 case REQ_ENTER:
5184 if (!check_blame_commit(blame, FALSE))
5185 break;
5187 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5188 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5189 break;
5191 if (!strcmp(blame->commit->id, NULL_ID)) {
5192 struct view *diff = VIEW(REQ_VIEW_DIFF);
5193 const char *diff_index_argv[] = {
5194 "git", "diff-index", "--root", "--patch-with-stat",
5195 "-C", "-M", "HEAD", "--", view->vid, NULL
5196 };
5198 if (!blame->commit->has_previous) {
5199 diff_index_argv[1] = "diff";
5200 diff_index_argv[2] = "--no-color";
5201 diff_index_argv[6] = "--";
5202 diff_index_argv[7] = "/dev/null";
5203 }
5205 if (!prepare_update(diff, diff_index_argv, NULL)) {
5206 report("Failed to allocate diff command");
5207 break;
5208 }
5209 flags |= OPEN_PREPARED;
5210 }
5212 open_view(view, REQ_VIEW_DIFF, flags);
5213 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5214 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5215 break;
5217 default:
5218 return request;
5219 }
5221 return REQ_NONE;
5222 }
5224 static bool
5225 blame_grep(struct view *view, struct line *line)
5226 {
5227 struct blame *blame = line->data;
5228 struct blame_commit *commit = blame->commit;
5229 const char *text[] = {
5230 blame->text,
5231 commit ? commit->title : "",
5232 commit ? commit->id : "",
5233 commit && opt_author ? commit->author : "",
5234 commit ? mkdate(&commit->time, opt_date) : "",
5235 NULL
5236 };
5238 return grep_text(view, text);
5239 }
5241 static void
5242 blame_select(struct view *view, struct line *line)
5243 {
5244 struct blame *blame = line->data;
5245 struct blame_commit *commit = blame->commit;
5247 if (!commit)
5248 return;
5250 if (!strcmp(commit->id, NULL_ID))
5251 string_ncopy(ref_commit, "HEAD", 4);
5252 else
5253 string_copy_rev(ref_commit, commit->id);
5254 }
5256 static struct view_ops blame_ops = {
5257 "line",
5258 NULL,
5259 blame_open,
5260 blame_read,
5261 blame_draw,
5262 blame_request,
5263 blame_grep,
5264 blame_select,
5265 };
5267 /*
5268 * Branch backend
5269 */
5271 struct branch {
5272 const char *author; /* Author of the last commit. */
5273 struct time time; /* Date of the last activity. */
5274 const struct ref *ref; /* Name and commit ID information. */
5275 };
5277 static const struct ref branch_all;
5279 static const enum sort_field branch_sort_fields[] = {
5280 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5281 };
5282 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5284 static int
5285 branch_compare(const void *l1, const void *l2)
5286 {
5287 const struct branch *branch1 = ((const struct line *) l1)->data;
5288 const struct branch *branch2 = ((const struct line *) l2)->data;
5290 switch (get_sort_field(branch_sort_state)) {
5291 case ORDERBY_DATE:
5292 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5294 case ORDERBY_AUTHOR:
5295 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5297 case ORDERBY_NAME:
5298 default:
5299 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5300 }
5301 }
5303 static bool
5304 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5305 {
5306 struct branch *branch = line->data;
5307 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5309 if (opt_date && draw_date(view, &branch->time))
5310 return TRUE;
5312 if (opt_author && draw_author(view, branch->author))
5313 return TRUE;
5315 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5316 return TRUE;
5317 }
5319 static enum request
5320 branch_request(struct view *view, enum request request, struct line *line)
5321 {
5322 struct branch *branch = line->data;
5324 switch (request) {
5325 case REQ_REFRESH:
5326 load_refs();
5327 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5328 return REQ_NONE;
5330 case REQ_TOGGLE_SORT_FIELD:
5331 case REQ_TOGGLE_SORT_ORDER:
5332 sort_view(view, request, &branch_sort_state, branch_compare);
5333 return REQ_NONE;
5335 case REQ_ENTER:
5336 if (branch->ref == &branch_all) {
5337 const char *all_branches_argv[] = {
5338 "git", "log", "--no-color", "--pretty=raw", "--parents",
5339 "--topo-order", "--all", NULL
5340 };
5341 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5343 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5344 report("Failed to load view of all branches");
5345 return REQ_NONE;
5346 }
5347 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5348 } else {
5349 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5350 }
5351 return REQ_NONE;
5353 default:
5354 return request;
5355 }
5356 }
5358 static bool
5359 branch_read(struct view *view, char *line)
5360 {
5361 static char id[SIZEOF_REV];
5362 struct branch *reference;
5363 size_t i;
5365 if (!line)
5366 return TRUE;
5368 switch (get_line_type(line)) {
5369 case LINE_COMMIT:
5370 string_copy_rev(id, line + STRING_SIZE("commit "));
5371 return TRUE;
5373 case LINE_AUTHOR:
5374 for (i = 0, reference = NULL; i < view->lines; i++) {
5375 struct branch *branch = view->line[i].data;
5377 if (strcmp(branch->ref->id, id))
5378 continue;
5380 view->line[i].dirty = TRUE;
5381 if (reference) {
5382 branch->author = reference->author;
5383 branch->time = reference->time;
5384 continue;
5385 }
5387 parse_author_line(line + STRING_SIZE("author "),
5388 &branch->author, &branch->time);
5389 reference = branch;
5390 }
5391 return TRUE;
5393 default:
5394 return TRUE;
5395 }
5397 }
5399 static bool
5400 branch_open_visitor(void *data, const struct ref *ref)
5401 {
5402 struct view *view = data;
5403 struct branch *branch;
5405 if (ref->tag || ref->ltag || ref->remote)
5406 return TRUE;
5408 branch = calloc(1, sizeof(*branch));
5409 if (!branch)
5410 return FALSE;
5412 branch->ref = ref;
5413 return !!add_line_data(view, branch, LINE_DEFAULT);
5414 }
5416 static bool
5417 branch_open(struct view *view)
5418 {
5419 const char *branch_log[] = {
5420 "git", "log", "--no-color", "--pretty=raw",
5421 "--simplify-by-decoration", "--all", NULL
5422 };
5424 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5425 report("Failed to load branch data");
5426 return TRUE;
5427 }
5429 setup_update(view, view->id);
5430 branch_open_visitor(view, &branch_all);
5431 foreach_ref(branch_open_visitor, view);
5432 view->p_restore = TRUE;
5434 return TRUE;
5435 }
5437 static bool
5438 branch_grep(struct view *view, struct line *line)
5439 {
5440 struct branch *branch = line->data;
5441 const char *text[] = {
5442 branch->ref->name,
5443 branch->author,
5444 NULL
5445 };
5447 return grep_text(view, text);
5448 }
5450 static void
5451 branch_select(struct view *view, struct line *line)
5452 {
5453 struct branch *branch = line->data;
5455 string_copy_rev(view->ref, branch->ref->id);
5456 string_copy_rev(ref_commit, branch->ref->id);
5457 string_copy_rev(ref_head, branch->ref->id);
5458 string_copy_rev(ref_branch, branch->ref->name);
5459 }
5461 static struct view_ops branch_ops = {
5462 "branch",
5463 NULL,
5464 branch_open,
5465 branch_read,
5466 branch_draw,
5467 branch_request,
5468 branch_grep,
5469 branch_select,
5470 };
5472 /*
5473 * Status backend
5474 */
5476 struct status {
5477 char status;
5478 struct {
5479 mode_t mode;
5480 char rev[SIZEOF_REV];
5481 char name[SIZEOF_STR];
5482 } old;
5483 struct {
5484 mode_t mode;
5485 char rev[SIZEOF_REV];
5486 char name[SIZEOF_STR];
5487 } new;
5488 };
5490 static char status_onbranch[SIZEOF_STR];
5491 static struct status stage_status;
5492 static enum line_type stage_line_type;
5493 static size_t stage_chunks;
5494 static int *stage_chunk;
5496 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5498 /* This should work even for the "On branch" line. */
5499 static inline bool
5500 status_has_none(struct view *view, struct line *line)
5501 {
5502 return line < view->line + view->lines && !line[1].data;
5503 }
5505 /* Get fields from the diff line:
5506 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5507 */
5508 static inline bool
5509 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5510 {
5511 const char *old_mode = buf + 1;
5512 const char *new_mode = buf + 8;
5513 const char *old_rev = buf + 15;
5514 const char *new_rev = buf + 56;
5515 const char *status = buf + 97;
5517 if (bufsize < 98 ||
5518 old_mode[-1] != ':' ||
5519 new_mode[-1] != ' ' ||
5520 old_rev[-1] != ' ' ||
5521 new_rev[-1] != ' ' ||
5522 status[-1] != ' ')
5523 return FALSE;
5525 file->status = *status;
5527 string_copy_rev(file->old.rev, old_rev);
5528 string_copy_rev(file->new.rev, new_rev);
5530 file->old.mode = strtoul(old_mode, NULL, 8);
5531 file->new.mode = strtoul(new_mode, NULL, 8);
5533 file->old.name[0] = file->new.name[0] = 0;
5535 return TRUE;
5536 }
5538 static bool
5539 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5540 {
5541 struct status *unmerged = NULL;
5542 char *buf;
5543 struct io io = {};
5545 if (!io_run(&io, argv, opt_cdup, IO_RD))
5546 return FALSE;
5548 add_line_data(view, NULL, type);
5550 while ((buf = io_get(&io, 0, TRUE))) {
5551 struct status *file = unmerged;
5553 if (!file) {
5554 file = calloc(1, sizeof(*file));
5555 if (!file || !add_line_data(view, file, type))
5556 goto error_out;
5557 }
5559 /* Parse diff info part. */
5560 if (status) {
5561 file->status = status;
5562 if (status == 'A')
5563 string_copy(file->old.rev, NULL_ID);
5565 } else if (!file->status || file == unmerged) {
5566 if (!status_get_diff(file, buf, strlen(buf)))
5567 goto error_out;
5569 buf = io_get(&io, 0, TRUE);
5570 if (!buf)
5571 break;
5573 /* Collapse all modified entries that follow an
5574 * associated unmerged entry. */
5575 if (unmerged == file) {
5576 unmerged->status = 'U';
5577 unmerged = NULL;
5578 } else if (file->status == 'U') {
5579 unmerged = file;
5580 }
5581 }
5583 /* Grab the old name for rename/copy. */
5584 if (!*file->old.name &&
5585 (file->status == 'R' || file->status == 'C')) {
5586 string_ncopy(file->old.name, buf, strlen(buf));
5588 buf = io_get(&io, 0, TRUE);
5589 if (!buf)
5590 break;
5591 }
5593 /* git-ls-files just delivers a NUL separated list of
5594 * file names similar to the second half of the
5595 * git-diff-* output. */
5596 string_ncopy(file->new.name, buf, strlen(buf));
5597 if (!*file->old.name)
5598 string_copy(file->old.name, file->new.name);
5599 file = NULL;
5600 }
5602 if (io_error(&io)) {
5603 error_out:
5604 io_done(&io);
5605 return FALSE;
5606 }
5608 if (!view->line[view->lines - 1].data)
5609 add_line_data(view, NULL, LINE_STAT_NONE);
5611 io_done(&io);
5612 return TRUE;
5613 }
5615 /* Don't show unmerged entries in the staged section. */
5616 static const char *status_diff_index_argv[] = {
5617 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5618 "--cached", "-M", "HEAD", NULL
5619 };
5621 static const char *status_diff_files_argv[] = {
5622 "git", "diff-files", "-z", NULL
5623 };
5625 static const char *status_list_other_argv[] = {
5626 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5627 };
5629 static const char *status_list_no_head_argv[] = {
5630 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5631 };
5633 static const char *update_index_argv[] = {
5634 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5635 };
5637 /* Restore the previous line number to stay in the context or select a
5638 * line with something that can be updated. */
5639 static void
5640 status_restore(struct view *view)
5641 {
5642 if (view->p_lineno >= view->lines)
5643 view->p_lineno = view->lines - 1;
5644 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5645 view->p_lineno++;
5646 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5647 view->p_lineno--;
5649 /* If the above fails, always skip the "On branch" line. */
5650 if (view->p_lineno < view->lines)
5651 view->lineno = view->p_lineno;
5652 else
5653 view->lineno = 1;
5655 if (view->lineno < view->offset)
5656 view->offset = view->lineno;
5657 else if (view->offset + view->height <= view->lineno)
5658 view->offset = view->lineno - view->height + 1;
5660 view->p_restore = FALSE;
5661 }
5663 static void
5664 status_update_onbranch(void)
5665 {
5666 static const char *paths[][2] = {
5667 { "rebase-apply/rebasing", "Rebasing" },
5668 { "rebase-apply/applying", "Applying mailbox" },
5669 { "rebase-apply/", "Rebasing mailbox" },
5670 { "rebase-merge/interactive", "Interactive rebase" },
5671 { "rebase-merge/", "Rebase merge" },
5672 { "MERGE_HEAD", "Merging" },
5673 { "BISECT_LOG", "Bisecting" },
5674 { "HEAD", "On branch" },
5675 };
5676 char buf[SIZEOF_STR];
5677 struct stat stat;
5678 int i;
5680 if (is_initial_commit()) {
5681 string_copy(status_onbranch, "Initial commit");
5682 return;
5683 }
5685 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5686 char *head = opt_head;
5688 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5689 lstat(buf, &stat) < 0)
5690 continue;
5692 if (!*opt_head) {
5693 struct io io = {};
5695 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5696 io_read_buf(&io, buf, sizeof(buf))) {
5697 head = buf;
5698 if (!prefixcmp(head, "refs/heads/"))
5699 head += STRING_SIZE("refs/heads/");
5700 }
5701 }
5703 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5704 string_copy(status_onbranch, opt_head);
5705 return;
5706 }
5708 string_copy(status_onbranch, "Not currently on any branch");
5709 }
5711 /* First parse staged info using git-diff-index(1), then parse unstaged
5712 * info using git-diff-files(1), and finally untracked files using
5713 * git-ls-files(1). */
5714 static bool
5715 status_open(struct view *view)
5716 {
5717 reset_view(view);
5719 add_line_data(view, NULL, LINE_STAT_HEAD);
5720 status_update_onbranch();
5722 io_run_bg(update_index_argv);
5724 if (is_initial_commit()) {
5725 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5726 return FALSE;
5727 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5728 return FALSE;
5729 }
5731 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5732 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5733 return FALSE;
5735 /* Restore the exact position or use the specialized restore
5736 * mode? */
5737 if (!view->p_restore)
5738 status_restore(view);
5739 return TRUE;
5740 }
5742 static bool
5743 status_draw(struct view *view, struct line *line, unsigned int lineno)
5744 {
5745 struct status *status = line->data;
5746 enum line_type type;
5747 const char *text;
5749 if (!status) {
5750 switch (line->type) {
5751 case LINE_STAT_STAGED:
5752 type = LINE_STAT_SECTION;
5753 text = "Changes to be committed:";
5754 break;
5756 case LINE_STAT_UNSTAGED:
5757 type = LINE_STAT_SECTION;
5758 text = "Changed but not updated:";
5759 break;
5761 case LINE_STAT_UNTRACKED:
5762 type = LINE_STAT_SECTION;
5763 text = "Untracked files:";
5764 break;
5766 case LINE_STAT_NONE:
5767 type = LINE_DEFAULT;
5768 text = " (no files)";
5769 break;
5771 case LINE_STAT_HEAD:
5772 type = LINE_STAT_HEAD;
5773 text = status_onbranch;
5774 break;
5776 default:
5777 return FALSE;
5778 }
5779 } else {
5780 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5782 buf[0] = status->status;
5783 if (draw_text(view, line->type, buf, TRUE))
5784 return TRUE;
5785 type = LINE_DEFAULT;
5786 text = status->new.name;
5787 }
5789 draw_text(view, type, text, TRUE);
5790 return TRUE;
5791 }
5793 static enum request
5794 status_load_error(struct view *view, struct view *stage, const char *path)
5795 {
5796 if (displayed_views() == 2 || display[current_view] != view)
5797 maximize_view(view);
5798 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5799 return REQ_NONE;
5800 }
5802 static enum request
5803 status_enter(struct view *view, struct line *line)
5804 {
5805 struct status *status = line->data;
5806 const char *oldpath = status ? status->old.name : NULL;
5807 /* Diffs for unmerged entries are empty when passing the new
5808 * path, so leave it empty. */
5809 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5810 const char *info;
5811 enum open_flags split;
5812 struct view *stage = VIEW(REQ_VIEW_STAGE);
5814 if (line->type == LINE_STAT_NONE ||
5815 (!status && line[1].type == LINE_STAT_NONE)) {
5816 report("No file to diff");
5817 return REQ_NONE;
5818 }
5820 switch (line->type) {
5821 case LINE_STAT_STAGED:
5822 if (is_initial_commit()) {
5823 const char *no_head_diff_argv[] = {
5824 "git", "diff", "--no-color", "--patch-with-stat",
5825 "--", "/dev/null", newpath, NULL
5826 };
5828 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5829 return status_load_error(view, stage, newpath);
5830 } else {
5831 const char *index_show_argv[] = {
5832 "git", "diff-index", "--root", "--patch-with-stat",
5833 "-C", "-M", "--cached", "HEAD", "--",
5834 oldpath, newpath, NULL
5835 };
5837 if (!prepare_update(stage, index_show_argv, opt_cdup))
5838 return status_load_error(view, stage, newpath);
5839 }
5841 if (status)
5842 info = "Staged changes to %s";
5843 else
5844 info = "Staged changes";
5845 break;
5847 case LINE_STAT_UNSTAGED:
5848 {
5849 const char *files_show_argv[] = {
5850 "git", "diff-files", "--root", "--patch-with-stat",
5851 "-C", "-M", "--", oldpath, newpath, NULL
5852 };
5854 if (!prepare_update(stage, files_show_argv, opt_cdup))
5855 return status_load_error(view, stage, newpath);
5856 if (status)
5857 info = "Unstaged changes to %s";
5858 else
5859 info = "Unstaged changes";
5860 break;
5861 }
5862 case LINE_STAT_UNTRACKED:
5863 if (!newpath) {
5864 report("No file to show");
5865 return REQ_NONE;
5866 }
5868 if (!suffixcmp(status->new.name, -1, "/")) {
5869 report("Cannot display a directory");
5870 return REQ_NONE;
5871 }
5873 if (!prepare_update_file(stage, newpath))
5874 return status_load_error(view, stage, newpath);
5875 info = "Untracked file %s";
5876 break;
5878 case LINE_STAT_HEAD:
5879 return REQ_NONE;
5881 default:
5882 die("line type %d not handled in switch", line->type);
5883 }
5885 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5886 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5887 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5888 if (status) {
5889 stage_status = *status;
5890 } else {
5891 memset(&stage_status, 0, sizeof(stage_status));
5892 }
5894 stage_line_type = line->type;
5895 stage_chunks = 0;
5896 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5897 }
5899 return REQ_NONE;
5900 }
5902 static bool
5903 status_exists(struct status *status, enum line_type type)
5904 {
5905 struct view *view = VIEW(REQ_VIEW_STATUS);
5906 unsigned long lineno;
5908 for (lineno = 0; lineno < view->lines; lineno++) {
5909 struct line *line = &view->line[lineno];
5910 struct status *pos = line->data;
5912 if (line->type != type)
5913 continue;
5914 if (!pos && (!status || !status->status) && line[1].data) {
5915 select_view_line(view, lineno);
5916 return TRUE;
5917 }
5918 if (pos && !strcmp(status->new.name, pos->new.name)) {
5919 select_view_line(view, lineno);
5920 return TRUE;
5921 }
5922 }
5924 return FALSE;
5925 }
5928 static bool
5929 status_update_prepare(struct io *io, enum line_type type)
5930 {
5931 const char *staged_argv[] = {
5932 "git", "update-index", "-z", "--index-info", NULL
5933 };
5934 const char *others_argv[] = {
5935 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5936 };
5938 switch (type) {
5939 case LINE_STAT_STAGED:
5940 return io_run(io, staged_argv, opt_cdup, IO_WR);
5942 case LINE_STAT_UNSTAGED:
5943 case LINE_STAT_UNTRACKED:
5944 return io_run(io, others_argv, opt_cdup, IO_WR);
5946 default:
5947 die("line type %d not handled in switch", type);
5948 return FALSE;
5949 }
5950 }
5952 static bool
5953 status_update_write(struct io *io, struct status *status, enum line_type type)
5954 {
5955 char buf[SIZEOF_STR];
5956 size_t bufsize = 0;
5958 switch (type) {
5959 case LINE_STAT_STAGED:
5960 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5961 status->old.mode,
5962 status->old.rev,
5963 status->old.name, 0))
5964 return FALSE;
5965 break;
5967 case LINE_STAT_UNSTAGED:
5968 case LINE_STAT_UNTRACKED:
5969 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5970 return FALSE;
5971 break;
5973 default:
5974 die("line type %d not handled in switch", type);
5975 }
5977 return io_write(io, buf, bufsize);
5978 }
5980 static bool
5981 status_update_file(struct status *status, enum line_type type)
5982 {
5983 struct io io = {};
5984 bool result;
5986 if (!status_update_prepare(&io, type))
5987 return FALSE;
5989 result = status_update_write(&io, status, type);
5990 return io_done(&io) && result;
5991 }
5993 static bool
5994 status_update_files(struct view *view, struct line *line)
5995 {
5996 char buf[sizeof(view->ref)];
5997 struct io io = {};
5998 bool result = TRUE;
5999 struct line *pos = view->line + view->lines;
6000 int files = 0;
6001 int file, done;
6002 int cursor_y = -1, cursor_x = -1;
6004 if (!status_update_prepare(&io, line->type))
6005 return FALSE;
6007 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6008 files++;
6010 string_copy(buf, view->ref);
6011 getsyx(cursor_y, cursor_x);
6012 for (file = 0, done = 5; result && file < files; line++, file++) {
6013 int almost_done = file * 100 / files;
6015 if (almost_done > done) {
6016 done = almost_done;
6017 string_format(view->ref, "updating file %u of %u (%d%% done)",
6018 file, files, done);
6019 update_view_title(view);
6020 setsyx(cursor_y, cursor_x);
6021 doupdate();
6022 }
6023 result = status_update_write(&io, line->data, line->type);
6024 }
6025 string_copy(view->ref, buf);
6027 return io_done(&io) && result;
6028 }
6030 static bool
6031 status_update(struct view *view)
6032 {
6033 struct line *line = &view->line[view->lineno];
6035 assert(view->lines);
6037 if (!line->data) {
6038 /* This should work even for the "On branch" line. */
6039 if (line < view->line + view->lines && !line[1].data) {
6040 report("Nothing to update");
6041 return FALSE;
6042 }
6044 if (!status_update_files(view, line + 1)) {
6045 report("Failed to update file status");
6046 return FALSE;
6047 }
6049 } else if (!status_update_file(line->data, line->type)) {
6050 report("Failed to update file status");
6051 return FALSE;
6052 }
6054 return TRUE;
6055 }
6057 static bool
6058 status_revert(struct status *status, enum line_type type, bool has_none)
6059 {
6060 if (!status || type != LINE_STAT_UNSTAGED) {
6061 if (type == LINE_STAT_STAGED) {
6062 report("Cannot revert changes to staged files");
6063 } else if (type == LINE_STAT_UNTRACKED) {
6064 report("Cannot revert changes to untracked files");
6065 } else if (has_none) {
6066 report("Nothing to revert");
6067 } else {
6068 report("Cannot revert changes to multiple files");
6069 }
6071 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6072 char mode[10] = "100644";
6073 const char *reset_argv[] = {
6074 "git", "update-index", "--cacheinfo", mode,
6075 status->old.rev, status->old.name, NULL
6076 };
6077 const char *checkout_argv[] = {
6078 "git", "checkout", "--", status->old.name, NULL
6079 };
6081 if (status->status == 'U') {
6082 string_format(mode, "%5o", status->old.mode);
6084 if (status->old.mode == 0 && status->new.mode == 0) {
6085 reset_argv[2] = "--force-remove";
6086 reset_argv[3] = status->old.name;
6087 reset_argv[4] = NULL;
6088 }
6090 if (!io_run_fg(reset_argv, opt_cdup))
6091 return FALSE;
6092 if (status->old.mode == 0 && status->new.mode == 0)
6093 return TRUE;
6094 }
6096 return io_run_fg(checkout_argv, opt_cdup);
6097 }
6099 return FALSE;
6100 }
6102 static enum request
6103 status_request(struct view *view, enum request request, struct line *line)
6104 {
6105 struct status *status = line->data;
6107 switch (request) {
6108 case REQ_STATUS_UPDATE:
6109 if (!status_update(view))
6110 return REQ_NONE;
6111 break;
6113 case REQ_STATUS_REVERT:
6114 if (!status_revert(status, line->type, status_has_none(view, line)))
6115 return REQ_NONE;
6116 break;
6118 case REQ_STATUS_MERGE:
6119 if (!status || status->status != 'U') {
6120 report("Merging only possible for files with unmerged status ('U').");
6121 return REQ_NONE;
6122 }
6123 open_mergetool(status->new.name);
6124 break;
6126 case REQ_EDIT:
6127 if (!status)
6128 return request;
6129 if (status->status == 'D') {
6130 report("File has been deleted.");
6131 return REQ_NONE;
6132 }
6134 open_editor(status->new.name);
6135 break;
6137 case REQ_VIEW_BLAME:
6138 if (status)
6139 opt_ref[0] = 0;
6140 return request;
6142 case REQ_ENTER:
6143 /* After returning the status view has been split to
6144 * show the stage view. No further reloading is
6145 * necessary. */
6146 return status_enter(view, line);
6148 case REQ_REFRESH:
6149 /* Simply reload the view. */
6150 break;
6152 default:
6153 return request;
6154 }
6156 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6158 return REQ_NONE;
6159 }
6161 static void
6162 status_select(struct view *view, struct line *line)
6163 {
6164 struct status *status = line->data;
6165 char file[SIZEOF_STR] = "all files";
6166 const char *text;
6167 const char *key;
6169 if (status && !string_format(file, "'%s'", status->new.name))
6170 return;
6172 if (!status && line[1].type == LINE_STAT_NONE)
6173 line++;
6175 switch (line->type) {
6176 case LINE_STAT_STAGED:
6177 text = "Press %s to unstage %s for commit";
6178 break;
6180 case LINE_STAT_UNSTAGED:
6181 text = "Press %s to stage %s for commit";
6182 break;
6184 case LINE_STAT_UNTRACKED:
6185 text = "Press %s to stage %s for addition";
6186 break;
6188 case LINE_STAT_HEAD:
6189 case LINE_STAT_NONE:
6190 text = "Nothing to update";
6191 break;
6193 default:
6194 die("line type %d not handled in switch", line->type);
6195 }
6197 if (status && status->status == 'U') {
6198 text = "Press %s to resolve conflict in %s";
6199 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6201 } else {
6202 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6203 }
6205 string_format(view->ref, text, key, file);
6206 if (status)
6207 string_copy(opt_file, status->new.name);
6208 }
6210 static bool
6211 status_grep(struct view *view, struct line *line)
6212 {
6213 struct status *status = line->data;
6215 if (status) {
6216 const char buf[2] = { status->status, 0 };
6217 const char *text[] = { status->new.name, buf, NULL };
6219 return grep_text(view, text);
6220 }
6222 return FALSE;
6223 }
6225 static struct view_ops status_ops = {
6226 "file",
6227 NULL,
6228 status_open,
6229 NULL,
6230 status_draw,
6231 status_request,
6232 status_grep,
6233 status_select,
6234 };
6237 static bool
6238 stage_diff_write(struct io *io, struct line *line, struct line *end)
6239 {
6240 while (line < end) {
6241 if (!io_write(io, line->data, strlen(line->data)) ||
6242 !io_write(io, "\n", 1))
6243 return FALSE;
6244 line++;
6245 if (line->type == LINE_DIFF_CHUNK ||
6246 line->type == LINE_DIFF_HEADER)
6247 break;
6248 }
6250 return TRUE;
6251 }
6253 static struct line *
6254 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6255 {
6256 for (; view->line < line; line--)
6257 if (line->type == type)
6258 return line;
6260 return NULL;
6261 }
6263 static bool
6264 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6265 {
6266 const char *apply_argv[SIZEOF_ARG] = {
6267 "git", "apply", "--whitespace=nowarn", NULL
6268 };
6269 struct line *diff_hdr;
6270 struct io io = {};
6271 int argc = 3;
6273 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6274 if (!diff_hdr)
6275 return FALSE;
6277 if (!revert)
6278 apply_argv[argc++] = "--cached";
6279 if (revert || stage_line_type == LINE_STAT_STAGED)
6280 apply_argv[argc++] = "-R";
6281 apply_argv[argc++] = "-";
6282 apply_argv[argc++] = NULL;
6283 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6284 return FALSE;
6286 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6287 !stage_diff_write(&io, chunk, view->line + view->lines))
6288 chunk = NULL;
6290 io_done(&io);
6291 io_run_bg(update_index_argv);
6293 return chunk ? TRUE : FALSE;
6294 }
6296 static bool
6297 stage_update(struct view *view, struct line *line)
6298 {
6299 struct line *chunk = NULL;
6301 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6302 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6304 if (chunk) {
6305 if (!stage_apply_chunk(view, chunk, FALSE)) {
6306 report("Failed to apply chunk");
6307 return FALSE;
6308 }
6310 } else if (!stage_status.status) {
6311 view = VIEW(REQ_VIEW_STATUS);
6313 for (line = view->line; line < view->line + view->lines; line++)
6314 if (line->type == stage_line_type)
6315 break;
6317 if (!status_update_files(view, line + 1)) {
6318 report("Failed to update files");
6319 return FALSE;
6320 }
6322 } else if (!status_update_file(&stage_status, stage_line_type)) {
6323 report("Failed to update file");
6324 return FALSE;
6325 }
6327 return TRUE;
6328 }
6330 static bool
6331 stage_revert(struct view *view, struct line *line)
6332 {
6333 struct line *chunk = NULL;
6335 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6336 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6338 if (chunk) {
6339 if (!prompt_yesno("Are you sure you want to revert changes?"))
6340 return FALSE;
6342 if (!stage_apply_chunk(view, chunk, TRUE)) {
6343 report("Failed to revert chunk");
6344 return FALSE;
6345 }
6346 return TRUE;
6348 } else {
6349 return status_revert(stage_status.status ? &stage_status : NULL,
6350 stage_line_type, FALSE);
6351 }
6352 }
6355 static void
6356 stage_next(struct view *view, struct line *line)
6357 {
6358 int i;
6360 if (!stage_chunks) {
6361 for (line = view->line; line < view->line + view->lines; line++) {
6362 if (line->type != LINE_DIFF_CHUNK)
6363 continue;
6365 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6366 report("Allocation failure");
6367 return;
6368 }
6370 stage_chunk[stage_chunks++] = line - view->line;
6371 }
6372 }
6374 for (i = 0; i < stage_chunks; i++) {
6375 if (stage_chunk[i] > view->lineno) {
6376 do_scroll_view(view, stage_chunk[i] - view->lineno);
6377 report("Chunk %d of %d", i + 1, stage_chunks);
6378 return;
6379 }
6380 }
6382 report("No next chunk found");
6383 }
6385 static enum request
6386 stage_request(struct view *view, enum request request, struct line *line)
6387 {
6388 switch (request) {
6389 case REQ_STATUS_UPDATE:
6390 if (!stage_update(view, line))
6391 return REQ_NONE;
6392 break;
6394 case REQ_STATUS_REVERT:
6395 if (!stage_revert(view, line))
6396 return REQ_NONE;
6397 break;
6399 case REQ_STAGE_NEXT:
6400 if (stage_line_type == LINE_STAT_UNTRACKED) {
6401 report("File is untracked; press %s to add",
6402 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6403 return REQ_NONE;
6404 }
6405 stage_next(view, line);
6406 return REQ_NONE;
6408 case REQ_EDIT:
6409 if (!stage_status.new.name[0])
6410 return request;
6411 if (stage_status.status == 'D') {
6412 report("File has been deleted.");
6413 return REQ_NONE;
6414 }
6416 open_editor(stage_status.new.name);
6417 break;
6419 case REQ_REFRESH:
6420 /* Reload everything ... */
6421 break;
6423 case REQ_VIEW_BLAME:
6424 if (stage_status.new.name[0]) {
6425 string_copy(opt_file, stage_status.new.name);
6426 opt_ref[0] = 0;
6427 }
6428 return request;
6430 case REQ_ENTER:
6431 return pager_request(view, request, line);
6433 default:
6434 return request;
6435 }
6437 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6438 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6440 /* Check whether the staged entry still exists, and close the
6441 * stage view if it doesn't. */
6442 if (!status_exists(&stage_status, stage_line_type)) {
6443 status_restore(VIEW(REQ_VIEW_STATUS));
6444 return REQ_VIEW_CLOSE;
6445 }
6447 if (stage_line_type == LINE_STAT_UNTRACKED) {
6448 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6449 report("Cannot display a directory");
6450 return REQ_NONE;
6451 }
6453 if (!prepare_update_file(view, stage_status.new.name)) {
6454 report("Failed to open file: %s", strerror(errno));
6455 return REQ_NONE;
6456 }
6457 }
6458 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6460 return REQ_NONE;
6461 }
6463 static struct view_ops stage_ops = {
6464 "line",
6465 NULL,
6466 NULL,
6467 pager_read,
6468 pager_draw,
6469 stage_request,
6470 pager_grep,
6471 pager_select,
6472 };
6475 /*
6476 * Revision graph
6477 */
6479 struct commit {
6480 char id[SIZEOF_REV]; /* SHA1 ID. */
6481 char title[128]; /* First line of the commit message. */
6482 const char *author; /* Author of the commit. */
6483 struct time time; /* Date from the author ident. */
6484 struct ref_list *refs; /* Repository references. */
6485 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6486 size_t graph_size; /* The width of the graph array. */
6487 bool has_parents; /* Rewritten --parents seen. */
6488 };
6490 /* Size of rev graph with no "padding" columns */
6491 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6493 struct rev_graph {
6494 struct rev_graph *prev, *next, *parents;
6495 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6496 size_t size;
6497 struct commit *commit;
6498 size_t pos;
6499 unsigned int boundary:1;
6500 };
6502 /* Parents of the commit being visualized. */
6503 static struct rev_graph graph_parents[4];
6505 /* The current stack of revisions on the graph. */
6506 static struct rev_graph graph_stacks[4] = {
6507 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6508 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6509 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6510 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6511 };
6513 static inline bool
6514 graph_parent_is_merge(struct rev_graph *graph)
6515 {
6516 return graph->parents->size > 1;
6517 }
6519 static inline void
6520 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6521 {
6522 struct commit *commit = graph->commit;
6524 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6525 commit->graph[commit->graph_size++] = symbol;
6526 }
6528 static void
6529 clear_rev_graph(struct rev_graph *graph)
6530 {
6531 graph->boundary = 0;
6532 graph->size = graph->pos = 0;
6533 graph->commit = NULL;
6534 memset(graph->parents, 0, sizeof(*graph->parents));
6535 }
6537 static void
6538 done_rev_graph(struct rev_graph *graph)
6539 {
6540 if (graph_parent_is_merge(graph) &&
6541 graph->pos < graph->size - 1 &&
6542 graph->next->size == graph->size + graph->parents->size - 1) {
6543 size_t i = graph->pos + graph->parents->size - 1;
6545 graph->commit->graph_size = i * 2;
6546 while (i < graph->next->size - 1) {
6547 append_to_rev_graph(graph, ' ');
6548 append_to_rev_graph(graph, '\\');
6549 i++;
6550 }
6551 }
6553 clear_rev_graph(graph);
6554 }
6556 static void
6557 push_rev_graph(struct rev_graph *graph, const char *parent)
6558 {
6559 int i;
6561 /* "Collapse" duplicate parents lines.
6562 *
6563 * FIXME: This needs to also update update the drawn graph but
6564 * for now it just serves as a method for pruning graph lines. */
6565 for (i = 0; i < graph->size; i++)
6566 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6567 return;
6569 if (graph->size < SIZEOF_REVITEMS) {
6570 string_copy_rev(graph->rev[graph->size++], parent);
6571 }
6572 }
6574 static chtype
6575 get_rev_graph_symbol(struct rev_graph *graph)
6576 {
6577 chtype symbol;
6579 if (graph->boundary)
6580 symbol = REVGRAPH_BOUND;
6581 else if (graph->parents->size == 0)
6582 symbol = REVGRAPH_INIT;
6583 else if (graph_parent_is_merge(graph))
6584 symbol = REVGRAPH_MERGE;
6585 else if (graph->pos >= graph->size)
6586 symbol = REVGRAPH_BRANCH;
6587 else
6588 symbol = REVGRAPH_COMMIT;
6590 return symbol;
6591 }
6593 static void
6594 draw_rev_graph(struct rev_graph *graph)
6595 {
6596 struct rev_filler {
6597 chtype separator, line;
6598 };
6599 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6600 static struct rev_filler fillers[] = {
6601 { ' ', '|' },
6602 { '`', '.' },
6603 { '\'', ' ' },
6604 { '/', ' ' },
6605 };
6606 chtype symbol = get_rev_graph_symbol(graph);
6607 struct rev_filler *filler;
6608 size_t i;
6610 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6611 filler = &fillers[DEFAULT];
6613 for (i = 0; i < graph->pos; i++) {
6614 append_to_rev_graph(graph, filler->line);
6615 if (graph_parent_is_merge(graph->prev) &&
6616 graph->prev->pos == i)
6617 filler = &fillers[RSHARP];
6619 append_to_rev_graph(graph, filler->separator);
6620 }
6622 /* Place the symbol for this revision. */
6623 append_to_rev_graph(graph, symbol);
6625 if (graph->prev->size > graph->size)
6626 filler = &fillers[RDIAG];
6627 else
6628 filler = &fillers[DEFAULT];
6630 i++;
6632 for (; i < graph->size; i++) {
6633 append_to_rev_graph(graph, filler->separator);
6634 append_to_rev_graph(graph, filler->line);
6635 if (graph_parent_is_merge(graph->prev) &&
6636 i < graph->prev->pos + graph->parents->size)
6637 filler = &fillers[RSHARP];
6638 if (graph->prev->size > graph->size)
6639 filler = &fillers[LDIAG];
6640 }
6642 if (graph->prev->size > graph->size) {
6643 append_to_rev_graph(graph, filler->separator);
6644 if (filler->line != ' ')
6645 append_to_rev_graph(graph, filler->line);
6646 }
6647 }
6649 /* Prepare the next rev graph */
6650 static void
6651 prepare_rev_graph(struct rev_graph *graph)
6652 {
6653 size_t i;
6655 /* First, traverse all lines of revisions up to the active one. */
6656 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6657 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6658 break;
6660 push_rev_graph(graph->next, graph->rev[graph->pos]);
6661 }
6663 /* Interleave the new revision parent(s). */
6664 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6665 push_rev_graph(graph->next, graph->parents->rev[i]);
6667 /* Lastly, put any remaining revisions. */
6668 for (i = graph->pos + 1; i < graph->size; i++)
6669 push_rev_graph(graph->next, graph->rev[i]);
6670 }
6672 static void
6673 update_rev_graph(struct view *view, struct rev_graph *graph)
6674 {
6675 /* If this is the finalizing update ... */
6676 if (graph->commit)
6677 prepare_rev_graph(graph);
6679 /* Graph visualization needs a one rev look-ahead,
6680 * so the first update doesn't visualize anything. */
6681 if (!graph->prev->commit)
6682 return;
6684 if (view->lines > 2)
6685 view->line[view->lines - 3].dirty = 1;
6686 if (view->lines > 1)
6687 view->line[view->lines - 2].dirty = 1;
6688 draw_rev_graph(graph->prev);
6689 done_rev_graph(graph->prev->prev);
6690 }
6693 /*
6694 * Main view backend
6695 */
6697 static const char *main_argv[SIZEOF_ARG] = {
6698 "git", "log", "--no-color", "--pretty=raw", "--parents",
6699 "--topo-order", "%(head)", NULL
6700 };
6702 static bool
6703 main_draw(struct view *view, struct line *line, unsigned int lineno)
6704 {
6705 struct commit *commit = line->data;
6707 if (!commit->author)
6708 return FALSE;
6710 if (opt_date && draw_date(view, &commit->time))
6711 return TRUE;
6713 if (opt_author && draw_author(view, commit->author))
6714 return TRUE;
6716 if (opt_rev_graph && commit->graph_size &&
6717 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6718 return TRUE;
6720 if (opt_show_refs && commit->refs) {
6721 size_t i;
6723 for (i = 0; i < commit->refs->size; i++) {
6724 struct ref *ref = commit->refs->refs[i];
6725 enum line_type type;
6727 if (ref->head)
6728 type = LINE_MAIN_HEAD;
6729 else if (ref->ltag)
6730 type = LINE_MAIN_LOCAL_TAG;
6731 else if (ref->tag)
6732 type = LINE_MAIN_TAG;
6733 else if (ref->tracked)
6734 type = LINE_MAIN_TRACKED;
6735 else if (ref->remote)
6736 type = LINE_MAIN_REMOTE;
6737 else
6738 type = LINE_MAIN_REF;
6740 if (draw_text(view, type, "[", TRUE) ||
6741 draw_text(view, type, ref->name, TRUE) ||
6742 draw_text(view, type, "]", TRUE))
6743 return TRUE;
6745 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6746 return TRUE;
6747 }
6748 }
6750 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6751 return TRUE;
6752 }
6754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6755 static bool
6756 main_read(struct view *view, char *line)
6757 {
6758 static struct rev_graph *graph = graph_stacks;
6759 enum line_type type;
6760 struct commit *commit;
6762 if (!line) {
6763 int i;
6765 if (!view->lines && !view->parent)
6766 die("No revisions match the given arguments.");
6767 if (view->lines > 0) {
6768 commit = view->line[view->lines - 1].data;
6769 view->line[view->lines - 1].dirty = 1;
6770 if (!commit->author) {
6771 view->lines--;
6772 free(commit);
6773 graph->commit = NULL;
6774 }
6775 }
6776 update_rev_graph(view, graph);
6778 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6779 clear_rev_graph(&graph_stacks[i]);
6780 return TRUE;
6781 }
6783 type = get_line_type(line);
6784 if (type == LINE_COMMIT) {
6785 commit = calloc(1, sizeof(struct commit));
6786 if (!commit)
6787 return FALSE;
6789 line += STRING_SIZE("commit ");
6790 if (*line == '-') {
6791 graph->boundary = 1;
6792 line++;
6793 }
6795 string_copy_rev(commit->id, line);
6796 commit->refs = get_ref_list(commit->id);
6797 graph->commit = commit;
6798 add_line_data(view, commit, LINE_MAIN_COMMIT);
6800 while ((line = strchr(line, ' '))) {
6801 line++;
6802 push_rev_graph(graph->parents, line);
6803 commit->has_parents = TRUE;
6804 }
6805 return TRUE;
6806 }
6808 if (!view->lines)
6809 return TRUE;
6810 commit = view->line[view->lines - 1].data;
6812 switch (type) {
6813 case LINE_PARENT:
6814 if (commit->has_parents)
6815 break;
6816 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6817 break;
6819 case LINE_AUTHOR:
6820 parse_author_line(line + STRING_SIZE("author "),
6821 &commit->author, &commit->time);
6822 update_rev_graph(view, graph);
6823 graph = graph->next;
6824 break;
6826 default:
6827 /* Fill in the commit title if it has not already been set. */
6828 if (commit->title[0])
6829 break;
6831 /* Require titles to start with a non-space character at the
6832 * offset used by git log. */
6833 if (strncmp(line, " ", 4))
6834 break;
6835 line += 4;
6836 /* Well, if the title starts with a whitespace character,
6837 * try to be forgiving. Otherwise we end up with no title. */
6838 while (isspace(*line))
6839 line++;
6840 if (*line == '\0')
6841 break;
6842 /* FIXME: More graceful handling of titles; append "..." to
6843 * shortened titles, etc. */
6845 string_expand(commit->title, sizeof(commit->title), line, 1);
6846 view->line[view->lines - 1].dirty = 1;
6847 }
6849 return TRUE;
6850 }
6852 static enum request
6853 main_request(struct view *view, enum request request, struct line *line)
6854 {
6855 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6857 switch (request) {
6858 case REQ_ENTER:
6859 open_view(view, REQ_VIEW_DIFF, flags);
6860 break;
6861 case REQ_REFRESH:
6862 load_refs();
6863 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6864 break;
6865 default:
6866 return request;
6867 }
6869 return REQ_NONE;
6870 }
6872 static bool
6873 grep_refs(struct ref_list *list, regex_t *regex)
6874 {
6875 regmatch_t pmatch;
6876 size_t i;
6878 if (!opt_show_refs || !list)
6879 return FALSE;
6881 for (i = 0; i < list->size; i++) {
6882 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6883 return TRUE;
6884 }
6886 return FALSE;
6887 }
6889 static bool
6890 main_grep(struct view *view, struct line *line)
6891 {
6892 struct commit *commit = line->data;
6893 const char *text[] = {
6894 commit->title,
6895 opt_author ? commit->author : "",
6896 mkdate(&commit->time, opt_date),
6897 NULL
6898 };
6900 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6901 }
6903 static void
6904 main_select(struct view *view, struct line *line)
6905 {
6906 struct commit *commit = line->data;
6908 string_copy_rev(view->ref, commit->id);
6909 string_copy_rev(ref_commit, view->ref);
6910 }
6912 static struct view_ops main_ops = {
6913 "commit",
6914 main_argv,
6915 NULL,
6916 main_read,
6917 main_draw,
6918 main_request,
6919 main_grep,
6920 main_select,
6921 };
6924 /*
6925 * Status management
6926 */
6928 /* Whether or not the curses interface has been initialized. */
6929 static bool cursed = FALSE;
6931 /* Terminal hacks and workarounds. */
6932 static bool use_scroll_redrawwin;
6933 static bool use_scroll_status_wclear;
6935 /* The status window is used for polling keystrokes. */
6936 static WINDOW *status_win;
6938 /* Reading from the prompt? */
6939 static bool input_mode = FALSE;
6941 static bool status_empty = FALSE;
6943 /* Update status and title window. */
6944 static void
6945 report(const char *msg, ...)
6946 {
6947 struct view *view = display[current_view];
6949 if (input_mode)
6950 return;
6952 if (!view) {
6953 char buf[SIZEOF_STR];
6954 va_list args;
6956 va_start(args, msg);
6957 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6958 buf[sizeof(buf) - 1] = 0;
6959 buf[sizeof(buf) - 2] = '.';
6960 buf[sizeof(buf) - 3] = '.';
6961 buf[sizeof(buf) - 4] = '.';
6962 }
6963 va_end(args);
6964 die("%s", buf);
6965 }
6967 if (!status_empty || *msg) {
6968 va_list args;
6970 va_start(args, msg);
6972 wmove(status_win, 0, 0);
6973 if (view->has_scrolled && use_scroll_status_wclear)
6974 wclear(status_win);
6975 if (*msg) {
6976 vwprintw(status_win, msg, args);
6977 status_empty = FALSE;
6978 } else {
6979 status_empty = TRUE;
6980 }
6981 wclrtoeol(status_win);
6982 wnoutrefresh(status_win);
6984 va_end(args);
6985 }
6987 update_view_title(view);
6988 }
6990 static void
6991 init_display(void)
6992 {
6993 const char *term;
6994 int x, y;
6996 /* Initialize the curses library */
6997 if (isatty(STDIN_FILENO)) {
6998 cursed = !!initscr();
6999 opt_tty = stdin;
7000 } else {
7001 /* Leave stdin and stdout alone when acting as a pager. */
7002 opt_tty = fopen("/dev/tty", "r+");
7003 if (!opt_tty)
7004 die("Failed to open /dev/tty");
7005 cursed = !!newterm(NULL, opt_tty, opt_tty);
7006 }
7008 if (!cursed)
7009 die("Failed to initialize curses");
7011 nonl(); /* Disable conversion and detect newlines from input. */
7012 cbreak(); /* Take input chars one at a time, no wait for \n */
7013 noecho(); /* Don't echo input */
7014 leaveok(stdscr, FALSE);
7016 if (has_colors())
7017 init_colors();
7019 getmaxyx(stdscr, y, x);
7020 status_win = newwin(1, 0, y - 1, 0);
7021 if (!status_win)
7022 die("Failed to create status window");
7024 /* Enable keyboard mapping */
7025 keypad(status_win, TRUE);
7026 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7028 TABSIZE = opt_tab_size;
7030 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7031 if (term && !strcmp(term, "gnome-terminal")) {
7032 /* In the gnome-terminal-emulator, the message from
7033 * scrolling up one line when impossible followed by
7034 * scrolling down one line causes corruption of the
7035 * status line. This is fixed by calling wclear. */
7036 use_scroll_status_wclear = TRUE;
7037 use_scroll_redrawwin = FALSE;
7039 } else if (term && !strcmp(term, "xrvt-xpm")) {
7040 /* No problems with full optimizations in xrvt-(unicode)
7041 * and aterm. */
7042 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7044 } else {
7045 /* When scrolling in (u)xterm the last line in the
7046 * scrolling direction will update slowly. */
7047 use_scroll_redrawwin = TRUE;
7048 use_scroll_status_wclear = FALSE;
7049 }
7050 }
7052 static int
7053 get_input(int prompt_position)
7054 {
7055 struct view *view;
7056 int i, key, cursor_y, cursor_x;
7057 bool loading = FALSE;
7059 if (prompt_position)
7060 input_mode = TRUE;
7062 while (TRUE) {
7063 foreach_view (view, i) {
7064 update_view(view);
7065 if (view_is_displayed(view) && view->has_scrolled &&
7066 use_scroll_redrawwin)
7067 redrawwin(view->win);
7068 view->has_scrolled = FALSE;
7069 if (view->pipe)
7070 loading = TRUE;
7071 }
7073 /* Update the cursor position. */
7074 if (prompt_position) {
7075 getbegyx(status_win, cursor_y, cursor_x);
7076 cursor_x = prompt_position;
7077 } else {
7078 view = display[current_view];
7079 getbegyx(view->win, cursor_y, cursor_x);
7080 cursor_x = view->width - 1;
7081 cursor_y += view->lineno - view->offset;
7082 }
7083 setsyx(cursor_y, cursor_x);
7085 /* Refresh, accept single keystroke of input */
7086 doupdate();
7087 nodelay(status_win, loading);
7088 key = wgetch(status_win);
7090 /* wgetch() with nodelay() enabled returns ERR when
7091 * there's no input. */
7092 if (key == ERR) {
7094 } else if (key == KEY_RESIZE) {
7095 int height, width;
7097 getmaxyx(stdscr, height, width);
7099 wresize(status_win, 1, width);
7100 mvwin(status_win, height - 1, 0);
7101 wnoutrefresh(status_win);
7102 resize_display();
7103 redraw_display(TRUE);
7105 } else {
7106 input_mode = FALSE;
7107 return key;
7108 }
7109 }
7110 }
7112 static char *
7113 prompt_input(const char *prompt, input_handler handler, void *data)
7114 {
7115 enum input_status status = INPUT_OK;
7116 static char buf[SIZEOF_STR];
7117 size_t pos = 0;
7119 buf[pos] = 0;
7121 while (status == INPUT_OK || status == INPUT_SKIP) {
7122 int key;
7124 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7125 wclrtoeol(status_win);
7127 key = get_input(pos + 1);
7128 switch (key) {
7129 case KEY_RETURN:
7130 case KEY_ENTER:
7131 case '\n':
7132 status = pos ? INPUT_STOP : INPUT_CANCEL;
7133 break;
7135 case KEY_BACKSPACE:
7136 if (pos > 0)
7137 buf[--pos] = 0;
7138 else
7139 status = INPUT_CANCEL;
7140 break;
7142 case KEY_ESC:
7143 status = INPUT_CANCEL;
7144 break;
7146 default:
7147 if (pos >= sizeof(buf)) {
7148 report("Input string too long");
7149 return NULL;
7150 }
7152 status = handler(data, buf, key);
7153 if (status == INPUT_OK)
7154 buf[pos++] = (char) key;
7155 }
7156 }
7158 /* Clear the status window */
7159 status_empty = FALSE;
7160 report("");
7162 if (status == INPUT_CANCEL)
7163 return NULL;
7165 buf[pos++] = 0;
7167 return buf;
7168 }
7170 static enum input_status
7171 prompt_yesno_handler(void *data, char *buf, int c)
7172 {
7173 if (c == 'y' || c == 'Y')
7174 return INPUT_STOP;
7175 if (c == 'n' || c == 'N')
7176 return INPUT_CANCEL;
7177 return INPUT_SKIP;
7178 }
7180 static bool
7181 prompt_yesno(const char *prompt)
7182 {
7183 char prompt2[SIZEOF_STR];
7185 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7186 return FALSE;
7188 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7189 }
7191 static enum input_status
7192 read_prompt_handler(void *data, char *buf, int c)
7193 {
7194 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7195 }
7197 static char *
7198 read_prompt(const char *prompt)
7199 {
7200 return prompt_input(prompt, read_prompt_handler, NULL);
7201 }
7203 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7204 {
7205 enum input_status status = INPUT_OK;
7206 int size = 0;
7208 while (items[size].text)
7209 size++;
7211 while (status == INPUT_OK) {
7212 const struct menu_item *item = &items[*selected];
7213 int key;
7214 int i;
7216 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7217 prompt, *selected + 1, size);
7218 if (item->hotkey)
7219 wprintw(status_win, "[%c] ", (char) item->hotkey);
7220 wprintw(status_win, "%s", item->text);
7221 wclrtoeol(status_win);
7223 key = get_input(COLS - 1);
7224 switch (key) {
7225 case KEY_RETURN:
7226 case KEY_ENTER:
7227 case '\n':
7228 status = INPUT_STOP;
7229 break;
7231 case KEY_LEFT:
7232 case KEY_UP:
7233 *selected = *selected - 1;
7234 if (*selected < 0)
7235 *selected = size - 1;
7236 break;
7238 case KEY_RIGHT:
7239 case KEY_DOWN:
7240 *selected = (*selected + 1) % size;
7241 break;
7243 case KEY_ESC:
7244 status = INPUT_CANCEL;
7245 break;
7247 default:
7248 for (i = 0; items[i].text; i++)
7249 if (items[i].hotkey == key) {
7250 *selected = i;
7251 status = INPUT_STOP;
7252 break;
7253 }
7254 }
7255 }
7257 /* Clear the status window */
7258 status_empty = FALSE;
7259 report("");
7261 return status != INPUT_CANCEL;
7262 }
7264 /*
7265 * Repository properties
7266 */
7268 static struct ref **refs = NULL;
7269 static size_t refs_size = 0;
7270 static struct ref *refs_head = NULL;
7272 static struct ref_list **ref_lists = NULL;
7273 static size_t ref_lists_size = 0;
7275 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7276 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7277 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7279 static int
7280 compare_refs(const void *ref1_, const void *ref2_)
7281 {
7282 const struct ref *ref1 = *(const struct ref **)ref1_;
7283 const struct ref *ref2 = *(const struct ref **)ref2_;
7285 if (ref1->tag != ref2->tag)
7286 return ref2->tag - ref1->tag;
7287 if (ref1->ltag != ref2->ltag)
7288 return ref2->ltag - ref2->ltag;
7289 if (ref1->head != ref2->head)
7290 return ref2->head - ref1->head;
7291 if (ref1->tracked != ref2->tracked)
7292 return ref2->tracked - ref1->tracked;
7293 if (ref1->remote != ref2->remote)
7294 return ref2->remote - ref1->remote;
7295 return strcmp(ref1->name, ref2->name);
7296 }
7298 static void
7299 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7300 {
7301 size_t i;
7303 for (i = 0; i < refs_size; i++)
7304 if (!visitor(data, refs[i]))
7305 break;
7306 }
7308 static struct ref *
7309 get_ref_head()
7310 {
7311 return refs_head;
7312 }
7314 static struct ref_list *
7315 get_ref_list(const char *id)
7316 {
7317 struct ref_list *list;
7318 size_t i;
7320 for (i = 0; i < ref_lists_size; i++)
7321 if (!strcmp(id, ref_lists[i]->id))
7322 return ref_lists[i];
7324 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7325 return NULL;
7326 list = calloc(1, sizeof(*list));
7327 if (!list)
7328 return NULL;
7330 for (i = 0; i < refs_size; i++) {
7331 if (!strcmp(id, refs[i]->id) &&
7332 realloc_refs_list(&list->refs, list->size, 1))
7333 list->refs[list->size++] = refs[i];
7334 }
7336 if (!list->refs) {
7337 free(list);
7338 return NULL;
7339 }
7341 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7342 ref_lists[ref_lists_size++] = list;
7343 return list;
7344 }
7346 static int
7347 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7348 {
7349 struct ref *ref = NULL;
7350 bool tag = FALSE;
7351 bool ltag = FALSE;
7352 bool remote = FALSE;
7353 bool tracked = FALSE;
7354 bool head = FALSE;
7355 int from = 0, to = refs_size - 1;
7357 if (!prefixcmp(name, "refs/tags/")) {
7358 if (!suffixcmp(name, namelen, "^{}")) {
7359 namelen -= 3;
7360 name[namelen] = 0;
7361 } else {
7362 ltag = TRUE;
7363 }
7365 tag = TRUE;
7366 namelen -= STRING_SIZE("refs/tags/");
7367 name += STRING_SIZE("refs/tags/");
7369 } else if (!prefixcmp(name, "refs/remotes/")) {
7370 remote = TRUE;
7371 namelen -= STRING_SIZE("refs/remotes/");
7372 name += STRING_SIZE("refs/remotes/");
7373 tracked = !strcmp(opt_remote, name);
7375 } else if (!prefixcmp(name, "refs/heads/")) {
7376 namelen -= STRING_SIZE("refs/heads/");
7377 name += STRING_SIZE("refs/heads/");
7378 if (!strncmp(opt_head, name, namelen))
7379 return OK;
7381 } else if (!strcmp(name, "HEAD")) {
7382 head = TRUE;
7383 if (*opt_head) {
7384 namelen = strlen(opt_head);
7385 name = opt_head;
7386 }
7387 }
7389 /* If we are reloading or it's an annotated tag, replace the
7390 * previous SHA1 with the resolved commit id; relies on the fact
7391 * git-ls-remote lists the commit id of an annotated tag right
7392 * before the commit id it points to. */
7393 while (from <= to) {
7394 size_t pos = (to + from) / 2;
7395 int cmp = strcmp(name, refs[pos]->name);
7397 if (!cmp) {
7398 ref = refs[pos];
7399 break;
7400 }
7402 if (cmp < 0)
7403 to = pos - 1;
7404 else
7405 from = pos + 1;
7406 }
7408 if (!ref) {
7409 if (!realloc_refs(&refs, refs_size, 1))
7410 return ERR;
7411 ref = calloc(1, sizeof(*ref) + namelen);
7412 if (!ref)
7413 return ERR;
7414 memmove(refs + from + 1, refs + from,
7415 (refs_size - from) * sizeof(*refs));
7416 refs[from] = ref;
7417 strncpy(ref->name, name, namelen);
7418 refs_size++;
7419 }
7421 ref->head = head;
7422 ref->tag = tag;
7423 ref->ltag = ltag;
7424 ref->remote = remote;
7425 ref->tracked = tracked;
7426 string_copy_rev(ref->id, id);
7428 if (head)
7429 refs_head = ref;
7430 return OK;
7431 }
7433 static int
7434 load_refs(void)
7435 {
7436 const char *head_argv[] = {
7437 "git", "symbolic-ref", "HEAD", NULL
7438 };
7439 static const char *ls_remote_argv[SIZEOF_ARG] = {
7440 "git", "ls-remote", opt_git_dir, NULL
7441 };
7442 static bool init = FALSE;
7443 size_t i;
7445 if (!init) {
7446 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7447 die("TIG_LS_REMOTE contains too many arguments");
7448 init = TRUE;
7449 }
7451 if (!*opt_git_dir)
7452 return OK;
7454 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7455 !prefixcmp(opt_head, "refs/heads/")) {
7456 char *offset = opt_head + STRING_SIZE("refs/heads/");
7458 memmove(opt_head, offset, strlen(offset) + 1);
7459 }
7461 refs_head = NULL;
7462 for (i = 0; i < refs_size; i++)
7463 refs[i]->id[0] = 0;
7465 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7466 return ERR;
7468 /* Update the ref lists to reflect changes. */
7469 for (i = 0; i < ref_lists_size; i++) {
7470 struct ref_list *list = ref_lists[i];
7471 size_t old, new;
7473 for (old = new = 0; old < list->size; old++)
7474 if (!strcmp(list->id, list->refs[old]->id))
7475 list->refs[new++] = list->refs[old];
7476 list->size = new;
7477 }
7479 return OK;
7480 }
7482 static void
7483 set_remote_branch(const char *name, const char *value, size_t valuelen)
7484 {
7485 if (!strcmp(name, ".remote")) {
7486 string_ncopy(opt_remote, value, valuelen);
7488 } else if (*opt_remote && !strcmp(name, ".merge")) {
7489 size_t from = strlen(opt_remote);
7491 if (!prefixcmp(value, "refs/heads/"))
7492 value += STRING_SIZE("refs/heads/");
7494 if (!string_format_from(opt_remote, &from, "/%s", value))
7495 opt_remote[0] = 0;
7496 }
7497 }
7499 static void
7500 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7501 {
7502 const char *argv[SIZEOF_ARG] = { name, "=" };
7503 int argc = 1 + (cmd == option_set_command);
7504 int error = ERR;
7506 if (!argv_from_string(argv, &argc, value))
7507 config_msg = "Too many option arguments";
7508 else
7509 error = cmd(argc, argv);
7511 if (error == ERR)
7512 warn("Option 'tig.%s': %s", name, config_msg);
7513 }
7515 static bool
7516 set_environment_variable(const char *name, const char *value)
7517 {
7518 size_t len = strlen(name) + 1 + strlen(value) + 1;
7519 char *env = malloc(len);
7521 if (env &&
7522 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7523 putenv(env) == 0)
7524 return TRUE;
7525 free(env);
7526 return FALSE;
7527 }
7529 static void
7530 set_work_tree(const char *value)
7531 {
7532 char cwd[SIZEOF_STR];
7534 if (!getcwd(cwd, sizeof(cwd)))
7535 die("Failed to get cwd path: %s", strerror(errno));
7536 if (chdir(opt_git_dir) < 0)
7537 die("Failed to chdir(%s): %s", strerror(errno));
7538 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7539 die("Failed to get git path: %s", strerror(errno));
7540 if (chdir(cwd) < 0)
7541 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7542 if (chdir(value) < 0)
7543 die("Failed to chdir(%s): %s", value, strerror(errno));
7544 if (!getcwd(cwd, sizeof(cwd)))
7545 die("Failed to get cwd path: %s", strerror(errno));
7546 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7547 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7548 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7549 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7550 opt_is_inside_work_tree = TRUE;
7551 }
7553 static int
7554 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7555 {
7556 if (!strcmp(name, "i18n.commitencoding"))
7557 string_ncopy(opt_encoding, value, valuelen);
7559 else if (!strcmp(name, "core.editor"))
7560 string_ncopy(opt_editor, value, valuelen);
7562 else if (!strcmp(name, "core.worktree"))
7563 set_work_tree(value);
7565 else if (!prefixcmp(name, "tig.color."))
7566 set_repo_config_option(name + 10, value, option_color_command);
7568 else if (!prefixcmp(name, "tig.bind."))
7569 set_repo_config_option(name + 9, value, option_bind_command);
7571 else if (!prefixcmp(name, "tig."))
7572 set_repo_config_option(name + 4, value, option_set_command);
7574 else if (*opt_head && !prefixcmp(name, "branch.") &&
7575 !strncmp(name + 7, opt_head, strlen(opt_head)))
7576 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7578 return OK;
7579 }
7581 static int
7582 load_git_config(void)
7583 {
7584 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7586 return io_run_load(config_list_argv, "=", read_repo_config_option);
7587 }
7589 static int
7590 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7591 {
7592 if (!opt_git_dir[0]) {
7593 string_ncopy(opt_git_dir, name, namelen);
7595 } else if (opt_is_inside_work_tree == -1) {
7596 /* This can be 3 different values depending on the
7597 * version of git being used. If git-rev-parse does not
7598 * understand --is-inside-work-tree it will simply echo
7599 * the option else either "true" or "false" is printed.
7600 * Default to true for the unknown case. */
7601 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7603 } else if (*name == '.') {
7604 string_ncopy(opt_cdup, name, namelen);
7606 } else {
7607 string_ncopy(opt_prefix, name, namelen);
7608 }
7610 return OK;
7611 }
7613 static int
7614 load_repo_info(void)
7615 {
7616 const char *rev_parse_argv[] = {
7617 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7618 "--show-cdup", "--show-prefix", NULL
7619 };
7621 return io_run_load(rev_parse_argv, "=", read_repo_info);
7622 }
7625 /*
7626 * Main
7627 */
7629 static const char usage[] =
7630 "tig " TIG_VERSION " (" __DATE__ ")\n"
7631 "\n"
7632 "Usage: tig [options] [revs] [--] [paths]\n"
7633 " or: tig show [options] [revs] [--] [paths]\n"
7634 " or: tig blame [rev] path\n"
7635 " or: tig status\n"
7636 " or: tig < [git command output]\n"
7637 "\n"
7638 "Options:\n"
7639 " -v, --version Show version and exit\n"
7640 " -h, --help Show help message and exit";
7642 static void __NORETURN
7643 quit(int sig)
7644 {
7645 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7646 if (cursed)
7647 endwin();
7648 exit(0);
7649 }
7651 static void __NORETURN
7652 die(const char *err, ...)
7653 {
7654 va_list args;
7656 endwin();
7658 va_start(args, err);
7659 fputs("tig: ", stderr);
7660 vfprintf(stderr, err, args);
7661 fputs("\n", stderr);
7662 va_end(args);
7664 exit(1);
7665 }
7667 static void
7668 warn(const char *msg, ...)
7669 {
7670 va_list args;
7672 va_start(args, msg);
7673 fputs("tig warning: ", stderr);
7674 vfprintf(stderr, msg, args);
7675 fputs("\n", stderr);
7676 va_end(args);
7677 }
7679 static enum request
7680 parse_options(int argc, const char *argv[])
7681 {
7682 enum request request = REQ_VIEW_MAIN;
7683 const char *subcommand;
7684 bool seen_dashdash = FALSE;
7685 /* XXX: This is vulnerable to the user overriding options
7686 * required for the main view parser. */
7687 const char *custom_argv[SIZEOF_ARG] = {
7688 "git", "log", "--no-color", "--pretty=raw", "--parents",
7689 "--topo-order", NULL
7690 };
7691 int i, j = 6;
7693 if (!isatty(STDIN_FILENO)) {
7694 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7695 return REQ_VIEW_PAGER;
7696 }
7698 if (argc <= 1)
7699 return REQ_NONE;
7701 subcommand = argv[1];
7702 if (!strcmp(subcommand, "status")) {
7703 if (argc > 2)
7704 warn("ignoring arguments after `%s'", subcommand);
7705 return REQ_VIEW_STATUS;
7707 } else if (!strcmp(subcommand, "blame")) {
7708 if (argc <= 2 || argc > 4)
7709 die("invalid number of options to blame\n\n%s", usage);
7711 i = 2;
7712 if (argc == 4) {
7713 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7714 i++;
7715 }
7717 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7718 return REQ_VIEW_BLAME;
7720 } else if (!strcmp(subcommand, "show")) {
7721 request = REQ_VIEW_DIFF;
7723 } else {
7724 subcommand = NULL;
7725 }
7727 if (subcommand) {
7728 custom_argv[1] = subcommand;
7729 j = 2;
7730 }
7732 for (i = 1 + !!subcommand; i < argc; i++) {
7733 const char *opt = argv[i];
7735 if (seen_dashdash || !strcmp(opt, "--")) {
7736 seen_dashdash = TRUE;
7738 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7739 printf("tig version %s\n", TIG_VERSION);
7740 quit(0);
7742 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7743 printf("%s\n", usage);
7744 quit(0);
7745 }
7747 custom_argv[j++] = opt;
7748 if (j >= ARRAY_SIZE(custom_argv))
7749 die("command too long");
7750 }
7752 if (!prepare_update(VIEW(request), custom_argv, NULL))
7753 die("Failed to format arguments");
7755 return request;
7756 }
7758 int
7759 main(int argc, const char *argv[])
7760 {
7761 const char *codeset = "UTF-8";
7762 enum request request = parse_options(argc, argv);
7763 struct view *view;
7764 size_t i;
7766 signal(SIGINT, quit);
7767 signal(SIGPIPE, SIG_IGN);
7769 if (setlocale(LC_ALL, "")) {
7770 codeset = nl_langinfo(CODESET);
7771 }
7773 if (load_repo_info() == ERR)
7774 die("Failed to load repo info.");
7776 if (load_options() == ERR)
7777 die("Failed to load user config.");
7779 if (load_git_config() == ERR)
7780 die("Failed to load repo config.");
7782 /* Require a git repository unless when running in pager mode. */
7783 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7784 die("Not a git repository");
7786 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7787 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7788 if (opt_iconv_in == ICONV_NONE)
7789 die("Failed to initialize character set conversion");
7790 }
7792 if (codeset && strcmp(codeset, "UTF-8")) {
7793 opt_iconv_out = iconv_open(codeset, "UTF-8");
7794 if (opt_iconv_out == ICONV_NONE)
7795 die("Failed to initialize character set conversion");
7796 }
7798 if (load_refs() == ERR)
7799 die("Failed to load refs.");
7801 foreach_view (view, i)
7802 if (!argv_from_env(view->ops->argv, view->cmd_env))
7803 die("Too many arguments in the `%s` environment variable",
7804 view->cmd_env);
7806 init_display();
7808 if (request != REQ_NONE)
7809 open_view(NULL, request, OPEN_PREPARED);
7810 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7812 while (view_driver(display[current_view], request)) {
7813 int key = get_input(0);
7815 view = display[current_view];
7816 request = get_keybinding(view->keymap, key);
7818 /* Some low-level request handling. This keeps access to
7819 * status_win restricted. */
7820 switch (request) {
7821 case REQ_NONE:
7822 report("Unknown key, press %s for help",
7823 get_key(view->keymap, REQ_VIEW_HELP));
7824 break;
7825 case REQ_PROMPT:
7826 {
7827 char *cmd = read_prompt(":");
7829 if (cmd && isdigit(*cmd)) {
7830 int lineno = view->lineno + 1;
7832 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7833 select_view_line(view, lineno - 1);
7834 report("");
7835 } else {
7836 report("Unable to parse '%s' as a line number", cmd);
7837 }
7839 } else if (cmd) {
7840 struct view *next = VIEW(REQ_VIEW_PAGER);
7841 const char *argv[SIZEOF_ARG] = { "git" };
7842 int argc = 1;
7844 /* When running random commands, initially show the
7845 * command in the title. However, it maybe later be
7846 * overwritten if a commit line is selected. */
7847 string_ncopy(next->ref, cmd, strlen(cmd));
7849 if (!argv_from_string(argv, &argc, cmd)) {
7850 report("Too many arguments");
7851 } else if (!prepare_update(next, argv, NULL)) {
7852 report("Failed to format command");
7853 } else {
7854 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7855 }
7856 }
7858 request = REQ_NONE;
7859 break;
7860 }
7861 case REQ_SEARCH:
7862 case REQ_SEARCH_BACK:
7863 {
7864 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7865 char *search = read_prompt(prompt);
7867 if (search)
7868 string_ncopy(opt_search, search, strlen(search));
7869 else if (*opt_search)
7870 request = request == REQ_SEARCH ?
7871 REQ_FIND_NEXT :
7872 REQ_FIND_PREV;
7873 else
7874 request = REQ_NONE;
7875 break;
7876 }
7877 default:
7878 break;
7879 }
7880 }
7882 quit(0);
7884 return 0;
7885 }