1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164 * Allocation helpers ... Entering macro hell to never be seen again.
165 */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
170 { \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
174 \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
179 } \
180 \
181 return tmp; \
182 }
184 /*
185 * String helpers
186 */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
190 {
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
196 }
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
214 {
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
227 }
228 }
230 dst[size] = 0;
231 }
233 static char *
234 chomp_string(char *name)
235 {
236 int namelen;
238 while (isspace(*name))
239 name++;
241 namelen = strlen(name) - 1;
242 while (namelen > 0 && isspace(name[namelen]))
243 name[namelen--] = 0;
245 return name;
246 }
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
250 {
251 va_list args;
252 size_t pos = bufpos ? *bufpos : 0;
254 va_start(args, fmt);
255 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256 va_end(args);
258 if (bufpos)
259 *bufpos = pos;
261 return pos >= bufsize ? FALSE : TRUE;
262 }
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
272 {
273 size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i = 0; i < len; i++) {
279 if (toupper(str1[i]) == toupper(str2[i]))
280 continue;
282 if (string_enum_sep(str1[i]) &&
283 string_enum_sep(str2[i]))
284 continue;
286 return str1[i] - str2[i];
287 }
289 return 0;
290 }
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296 const char *name;
297 int namelen;
298 int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
305 {
306 static char buf[SIZEOF_STR];
307 int bufpos;
309 for (bufpos = 0; bufpos <= namelen; bufpos++) {
310 buf[bufpos] = tolower(name[bufpos]);
311 if (buf[bufpos] == '_')
312 buf[bufpos] = '-';
313 }
315 buf[bufpos] = 0;
316 return buf;
317 }
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
323 {
324 size_t namelen = strlen(name);
325 int i;
327 for (i = 0; i < map_size; i++)
328 if (enum_equals(map[i], name, namelen)) {
329 *value = map[i].value;
330 return TRUE;
331 }
333 return FALSE;
334 }
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
344 {
345 size_t len = slen >= 0 ? slen : strlen(str);
346 size_t suffixlen = strlen(suffix);
348 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
349 }
352 /*
353 * Unicode / UTF-8 handling
354 *
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358 */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
362 {
363 if (c >= 0x1100 &&
364 (c <= 0x115f /* Hangul Jamo */
365 || c == 0x2329
366 || c == 0x232a
367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
368 /* CJK ... Yi */
369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
373 || (c >= 0xffe0 && c <= 0xffe6)
374 || (c >= 0x20000 && c <= 0x2fffd)
375 || (c >= 0x30000 && c <= 0x3fffd)))
376 return 2;
378 if (c == '\t')
379 return tab_size;
381 return 1;
382 }
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
394 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
399 {
400 int c = *(unsigned char *) string;
402 return utf8_bytes[c];
403 }
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
408 {
409 unsigned long unicode;
411 switch (length) {
412 case 1:
413 unicode = string[0];
414 break;
415 case 2:
416 unicode = (string[0] & 0x1f) << 6;
417 unicode += (string[1] & 0x3f);
418 break;
419 case 3:
420 unicode = (string[0] & 0x0f) << 12;
421 unicode += ((string[1] & 0x3f) << 6);
422 unicode += (string[2] & 0x3f);
423 break;
424 case 4:
425 unicode = (string[0] & 0x0f) << 18;
426 unicode += ((string[1] & 0x3f) << 12);
427 unicode += ((string[2] & 0x3f) << 6);
428 unicode += (string[3] & 0x3f);
429 break;
430 case 5:
431 unicode = (string[0] & 0x0f) << 24;
432 unicode += ((string[1] & 0x3f) << 18);
433 unicode += ((string[2] & 0x3f) << 12);
434 unicode += ((string[3] & 0x3f) << 6);
435 unicode += (string[4] & 0x3f);
436 break;
437 case 6:
438 unicode = (string[0] & 0x01) << 30;
439 unicode += ((string[1] & 0x3f) << 24);
440 unicode += ((string[2] & 0x3f) << 18);
441 unicode += ((string[3] & 0x3f) << 12);
442 unicode += ((string[4] & 0x3f) << 6);
443 unicode += (string[5] & 0x3f);
444 break;
445 default:
446 return 0;
447 }
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode > 0xffff ? 0 : unicode;
452 }
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
458 *
459 * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
462 {
463 const char *string = *start;
464 const char *end = strchr(string, '\0');
465 unsigned char last_bytes = 0;
466 size_t last_ucwidth = 0;
468 *width = 0;
469 *trimmed = 0;
471 while (string < end) {
472 unsigned char bytes = utf8_char_length(string, end);
473 size_t ucwidth;
474 unsigned long unicode;
476 if (string + bytes > end)
477 break;
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode = utf8_to_unicode(string, bytes);
483 /* FIXME: Graceful handling of invalid Unicode character. */
484 if (!unicode)
485 break;
487 ucwidth = unicode_width(unicode, tab_size);
488 if (skip > 0) {
489 skip -= ucwidth <= skip ? ucwidth : skip;
490 *start += bytes;
491 }
492 *width += ucwidth;
493 if (*width > max_width) {
494 *trimmed = 1;
495 *width -= ucwidth;
496 if (reserve && *width == max_width) {
497 string -= last_bytes;
498 *width -= last_ucwidth;
499 }
500 break;
501 }
503 string += bytes;
504 last_bytes = ucwidth ? bytes : 0;
505 last_ucwidth = ucwidth;
506 }
508 return string - *start;
509 }
512 #define DATE_INFO \
513 DATE_(NO), \
514 DATE_(DEFAULT), \
515 DATE_(LOCAL), \
516 DATE_(RELATIVE), \
517 DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521 DATE_INFO
522 #undef DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527 DATE_INFO
528 #undef DATE_
529 };
531 struct time {
532 time_t sec;
533 int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
537 {
538 return t1->sec - t2->sec;
539 }
541 static const char *
542 mkdate(const struct time *time, enum date date)
543 {
544 static char buf[DATE_COLS + 1];
545 static const struct enum_map reldate[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
552 };
553 struct tm tm;
555 if (!date || !time || !time->sec)
556 return "";
558 if (date == DATE_RELATIVE) {
559 struct timeval now;
560 time_t date = time->sec + time->tz;
561 time_t seconds;
562 int i;
564 gettimeofday(&now, NULL);
565 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567 if (seconds >= reldate[i].value)
568 continue;
570 seconds /= reldate[i].namelen;
571 if (!string_format(buf, "%ld %s%s %s",
572 seconds, reldate[i].name,
573 seconds > 1 ? "s" : "",
574 now.tv_sec >= date ? "ago" : "ahead"))
575 break;
576 return buf;
577 }
578 }
580 if (date == DATE_LOCAL) {
581 time_t date = time->sec + time->tz;
582 localtime_r(&date, &tm);
583 }
584 else {
585 gmtime_r(&time->sec, &tm);
586 }
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
674 static void
675 argv_free(const char *argv[])
676 {
677 int argc;
679 for (argc = 0; argv[argc]; argc++)
680 free((void *) argv[argc]);
681 argv[0] = NULL;
682 }
684 static bool
685 argv_copy(const char *dst[], const char *src[])
686 {
687 int argc;
689 for (argc = 0; src[argc]; argc++)
690 if (!(dst[argc] = strdup(src[argc])))
691 return FALSE;
692 return TRUE;
693 }
696 /*
697 * Executing external commands.
698 */
700 enum io_type {
701 IO_FD, /* File descriptor based IO. */
702 IO_BG, /* Execute command in the background. */
703 IO_FG, /* Execute command with same std{in,out,err}. */
704 IO_RD, /* Read only fork+exec IO. */
705 IO_WR, /* Write only fork+exec IO. */
706 IO_AP, /* Append fork+exec output to file. */
707 };
709 struct io {
710 pid_t pid; /* PID of spawned process. */
711 int pipe; /* Pipe end for reading or writing. */
712 int error; /* Error status. */
713 char *buf; /* Read buffer. */
714 size_t bufalloc; /* Allocated buffer size. */
715 size_t bufsize; /* Buffer content size. */
716 char *bufpos; /* Current buffer position. */
717 unsigned int eof:1; /* Has end of file been reached. */
718 };
720 static void
721 io_reset(struct io *io)
722 {
723 io->pipe = -1;
724 io->pid = 0;
725 io->buf = io->bufpos = NULL;
726 io->bufalloc = io->bufsize = 0;
727 io->error = 0;
728 io->eof = 0;
729 }
731 static void
732 io_init(struct io *io)
733 {
734 io_reset(io);
735 }
737 static bool
738 io_open(struct io *io, const char *fmt, ...)
739 {
740 char name[SIZEOF_STR] = "";
741 bool fits;
742 va_list args;
744 io_init(io);
746 va_start(args, fmt);
747 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
748 va_end(args);
750 if (!fits) {
751 io->error = ENAMETOOLONG;
752 return FALSE;
753 }
754 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
755 if (io->pipe == -1)
756 io->error = errno;
757 return io->pipe != -1;
758 }
760 static bool
761 io_kill(struct io *io)
762 {
763 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
764 }
766 static bool
767 io_done(struct io *io)
768 {
769 pid_t pid = io->pid;
771 if (io->pipe != -1)
772 close(io->pipe);
773 free(io->buf);
774 io_reset(io);
776 while (pid > 0) {
777 int status;
778 pid_t waiting = waitpid(pid, &status, 0);
780 if (waiting < 0) {
781 if (errno == EINTR)
782 continue;
783 io->error = errno;
784 return FALSE;
785 }
787 return waiting == pid &&
788 !WIFSIGNALED(status) &&
789 WIFEXITED(status) &&
790 !WEXITSTATUS(status);
791 }
793 return TRUE;
794 }
796 static bool
797 io_start(struct io *io, enum io_type type, const char *dir, const char *argv[])
798 {
799 int pipefds[2] = { -1, -1 };
801 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
802 io->error = errno;
803 return FALSE;
804 } else if (type == IO_AP) {
805 pipefds[1] = io->pipe;
806 }
808 if ((io->pid = fork())) {
809 if (io->pid == -1)
810 io->error = errno;
811 if (pipefds[!(type == IO_WR)] != -1)
812 close(pipefds[!(type == IO_WR)]);
813 if (io->pid != -1) {
814 io->pipe = pipefds[!!(type == IO_WR)];
815 return TRUE;
816 }
818 } else {
819 if (type != IO_FG) {
820 int devnull = open("/dev/null", O_RDWR);
821 int readfd = type == IO_WR ? pipefds[0] : devnull;
822 int writefd = (type == IO_RD || type == IO_AP)
823 ? pipefds[1] : devnull;
825 dup2(readfd, STDIN_FILENO);
826 dup2(writefd, STDOUT_FILENO);
827 dup2(devnull, STDERR_FILENO);
829 close(devnull);
830 if (pipefds[0] != -1)
831 close(pipefds[0]);
832 if (pipefds[1] != -1)
833 close(pipefds[1]);
834 }
836 if (dir && *dir && chdir(dir) == -1)
837 exit(errno);
839 execvp(argv[0], (char *const*) argv);
840 exit(errno);
841 }
843 if (pipefds[!!(type == IO_WR)] != -1)
844 close(pipefds[!!(type == IO_WR)]);
845 return FALSE;
846 }
848 static bool
849 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
850 {
851 io_init(io);
852 return io_start(io, type, dir, argv);
853 }
855 static bool
856 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
857 {
858 struct io io = {};
860 io_init(&io);
861 io.pipe = fd;
862 return io_start(&io, type, dir, argv) && io_done(&io);
863 }
865 static bool
866 io_run_bg(const char **argv)
867 {
868 return io_complete(IO_BG, argv, NULL, -1);
869 }
871 static bool
872 io_run_fg(const char **argv, const char *dir)
873 {
874 return io_complete(IO_FG, argv, dir, -1);
875 }
877 static bool
878 io_run_append(const char **argv, int fd)
879 {
880 return io_complete(IO_AP, argv, NULL, -1);
881 }
883 static bool
884 io_eof(struct io *io)
885 {
886 return io->eof;
887 }
889 static int
890 io_error(struct io *io)
891 {
892 return io->error;
893 }
895 static char *
896 io_strerror(struct io *io)
897 {
898 return strerror(io->error);
899 }
901 static bool
902 io_can_read(struct io *io)
903 {
904 struct timeval tv = { 0, 500 };
905 fd_set fds;
907 FD_ZERO(&fds);
908 FD_SET(io->pipe, &fds);
910 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
911 }
913 static ssize_t
914 io_read(struct io *io, void *buf, size_t bufsize)
915 {
916 do {
917 ssize_t readsize = read(io->pipe, buf, bufsize);
919 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
920 continue;
921 else if (readsize == -1)
922 io->error = errno;
923 else if (readsize == 0)
924 io->eof = 1;
925 return readsize;
926 } while (1);
927 }
929 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
931 static char *
932 io_get(struct io *io, int c, bool can_read)
933 {
934 char *eol;
935 ssize_t readsize;
937 while (TRUE) {
938 if (io->bufsize > 0) {
939 eol = memchr(io->bufpos, c, io->bufsize);
940 if (eol) {
941 char *line = io->bufpos;
943 *eol = 0;
944 io->bufpos = eol + 1;
945 io->bufsize -= io->bufpos - line;
946 return line;
947 }
948 }
950 if (io_eof(io)) {
951 if (io->bufsize) {
952 io->bufpos[io->bufsize] = 0;
953 io->bufsize = 0;
954 return io->bufpos;
955 }
956 return NULL;
957 }
959 if (!can_read)
960 return NULL;
962 if (io->bufsize > 0 && io->bufpos > io->buf)
963 memmove(io->buf, io->bufpos, io->bufsize);
965 if (io->bufalloc == io->bufsize) {
966 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
967 return NULL;
968 io->bufalloc += BUFSIZ;
969 }
971 io->bufpos = io->buf;
972 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
973 if (io_error(io))
974 return NULL;
975 io->bufsize += readsize;
976 }
977 }
979 static bool
980 io_write(struct io *io, const void *buf, size_t bufsize)
981 {
982 size_t written = 0;
984 while (!io_error(io) && written < bufsize) {
985 ssize_t size;
987 size = write(io->pipe, buf + written, bufsize - written);
988 if (size < 0 && (errno == EAGAIN || errno == EINTR))
989 continue;
990 else if (size == -1)
991 io->error = errno;
992 else
993 written += size;
994 }
996 return written == bufsize;
997 }
999 static bool
1000 io_read_buf(struct io *io, char buf[], size_t bufsize)
1001 {
1002 char *result = io_get(io, '\n', TRUE);
1004 if (result) {
1005 result = chomp_string(result);
1006 string_ncopy_do(buf, bufsize, result, strlen(result));
1007 }
1009 return io_done(io) && result;
1010 }
1012 static bool
1013 io_run_buf(const char **argv, char buf[], size_t bufsize)
1014 {
1015 struct io io = {};
1017 io_init(&io);
1018 return io_start(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1019 }
1021 static int
1022 io_load(struct io *io, const char *separators,
1023 int (*read_property)(char *, size_t, char *, size_t))
1024 {
1025 char *name;
1026 int state = OK;
1028 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1029 char *value;
1030 size_t namelen;
1031 size_t valuelen;
1033 name = chomp_string(name);
1034 namelen = strcspn(name, separators);
1036 if (name[namelen]) {
1037 name[namelen] = 0;
1038 value = chomp_string(name + namelen + 1);
1039 valuelen = strlen(value);
1041 } else {
1042 value = "";
1043 valuelen = 0;
1044 }
1046 state = read_property(name, namelen, value, valuelen);
1047 }
1049 if (state != ERR && io_error(io))
1050 state = ERR;
1051 io_done(io);
1053 return state;
1054 }
1056 static int
1057 io_run_load(const char **argv, const char *separators,
1058 int (*read_property)(char *, size_t, char *, size_t))
1059 {
1060 struct io io = {};
1062 io_init(&io);
1063 if (!io_start(&io, IO_RD, NULL, argv))
1064 return ERR;
1065 return io_load(&io, separators, read_property);
1066 }
1069 /*
1070 * User requests
1071 */
1073 #define REQ_INFO \
1074 /* XXX: Keep the view request first and in sync with views[]. */ \
1075 REQ_GROUP("View switching") \
1076 REQ_(VIEW_MAIN, "Show main view"), \
1077 REQ_(VIEW_DIFF, "Show diff view"), \
1078 REQ_(VIEW_LOG, "Show log view"), \
1079 REQ_(VIEW_TREE, "Show tree view"), \
1080 REQ_(VIEW_BLOB, "Show blob view"), \
1081 REQ_(VIEW_BLAME, "Show blame view"), \
1082 REQ_(VIEW_BRANCH, "Show branch view"), \
1083 REQ_(VIEW_HELP, "Show help page"), \
1084 REQ_(VIEW_PAGER, "Show pager view"), \
1085 REQ_(VIEW_STATUS, "Show status view"), \
1086 REQ_(VIEW_STAGE, "Show stage view"), \
1087 \
1088 REQ_GROUP("View manipulation") \
1089 REQ_(ENTER, "Enter current line and scroll"), \
1090 REQ_(NEXT, "Move to next"), \
1091 REQ_(PREVIOUS, "Move to previous"), \
1092 REQ_(PARENT, "Move to parent"), \
1093 REQ_(VIEW_NEXT, "Move focus to next view"), \
1094 REQ_(REFRESH, "Reload and refresh"), \
1095 REQ_(MAXIMIZE, "Maximize the current view"), \
1096 REQ_(VIEW_CLOSE, "Close the current view"), \
1097 REQ_(QUIT, "Close all views and quit"), \
1098 \
1099 REQ_GROUP("View specific requests") \
1100 REQ_(STATUS_UPDATE, "Update file status"), \
1101 REQ_(STATUS_REVERT, "Revert file changes"), \
1102 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1103 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1104 \
1105 REQ_GROUP("Cursor navigation") \
1106 REQ_(MOVE_UP, "Move cursor one line up"), \
1107 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1108 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1109 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1110 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1111 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1112 \
1113 REQ_GROUP("Scrolling") \
1114 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1115 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1116 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1117 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1118 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1119 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1120 \
1121 REQ_GROUP("Searching") \
1122 REQ_(SEARCH, "Search the view"), \
1123 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1124 REQ_(FIND_NEXT, "Find next search match"), \
1125 REQ_(FIND_PREV, "Find previous search match"), \
1126 \
1127 REQ_GROUP("Option manipulation") \
1128 REQ_(OPTIONS, "Open option menu"), \
1129 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1130 REQ_(TOGGLE_DATE, "Toggle date display"), \
1131 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1132 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1133 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1134 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1135 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1136 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1137 \
1138 REQ_GROUP("Misc") \
1139 REQ_(PROMPT, "Bring up the prompt"), \
1140 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1141 REQ_(SHOW_VERSION, "Show version information"), \
1142 REQ_(STOP_LOADING, "Stop all loading views"), \
1143 REQ_(EDIT, "Open in editor"), \
1144 REQ_(NONE, "Do nothing")
1147 /* User action requests. */
1148 enum request {
1149 #define REQ_GROUP(help)
1150 #define REQ_(req, help) REQ_##req
1152 /* Offset all requests to avoid conflicts with ncurses getch values. */
1153 REQ_UNKNOWN = KEY_MAX + 1,
1154 REQ_OFFSET,
1155 REQ_INFO
1157 #undef REQ_GROUP
1158 #undef REQ_
1159 };
1161 struct request_info {
1162 enum request request;
1163 const char *name;
1164 int namelen;
1165 const char *help;
1166 };
1168 static const struct request_info req_info[] = {
1169 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1170 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1171 REQ_INFO
1172 #undef REQ_GROUP
1173 #undef REQ_
1174 };
1176 static enum request
1177 get_request(const char *name)
1178 {
1179 int namelen = strlen(name);
1180 int i;
1182 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1183 if (enum_equals(req_info[i], name, namelen))
1184 return req_info[i].request;
1186 return REQ_UNKNOWN;
1187 }
1190 /*
1191 * Options
1192 */
1194 /* Option and state variables. */
1195 static enum date opt_date = DATE_DEFAULT;
1196 static enum author opt_author = AUTHOR_DEFAULT;
1197 static bool opt_line_number = FALSE;
1198 static bool opt_line_graphics = TRUE;
1199 static bool opt_rev_graph = FALSE;
1200 static bool opt_show_refs = TRUE;
1201 static int opt_num_interval = 5;
1202 static double opt_hscroll = 0.50;
1203 static double opt_scale_split_view = 2.0 / 3.0;
1204 static int opt_tab_size = 8;
1205 static int opt_author_cols = AUTHOR_COLS;
1206 static char opt_path[SIZEOF_STR] = "";
1207 static char opt_file[SIZEOF_STR] = "";
1208 static char opt_ref[SIZEOF_REF] = "";
1209 static char opt_head[SIZEOF_REF] = "";
1210 static char opt_remote[SIZEOF_REF] = "";
1211 static char opt_encoding[20] = "UTF-8";
1212 static iconv_t opt_iconv_in = ICONV_NONE;
1213 static iconv_t opt_iconv_out = ICONV_NONE;
1214 static char opt_search[SIZEOF_STR] = "";
1215 static char opt_cdup[SIZEOF_STR] = "";
1216 static char opt_prefix[SIZEOF_STR] = "";
1217 static char opt_git_dir[SIZEOF_STR] = "";
1218 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1219 static char opt_editor[SIZEOF_STR] = "";
1220 static FILE *opt_tty = NULL;
1222 #define is_initial_commit() (!get_ref_head())
1223 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1226 /*
1227 * Line-oriented content detection.
1228 */
1230 #define LINE_INFO \
1231 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1232 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1233 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1234 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1235 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1236 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1237 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1238 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1239 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1240 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1241 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1242 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1243 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1244 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1245 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1246 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1247 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1248 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1249 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1250 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1251 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1252 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1253 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1254 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1255 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1256 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1257 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1262 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1263 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1264 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1265 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1266 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1267 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1268 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1269 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1270 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1271 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1272 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1273 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1275 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1276 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1277 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1279 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1280 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1281 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1283 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1284 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1285 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1287 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1288 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1289 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1291 enum line_type {
1292 #define LINE(type, line, fg, bg, attr) \
1293 LINE_##type
1294 LINE_INFO,
1295 LINE_NONE
1296 #undef LINE
1297 };
1299 struct line_info {
1300 const char *name; /* Option name. */
1301 int namelen; /* Size of option name. */
1302 const char *line; /* The start of line to match. */
1303 int linelen; /* Size of string to match. */
1304 int fg, bg, attr; /* Color and text attributes for the lines. */
1305 };
1307 static struct line_info line_info[] = {
1308 #define LINE(type, line, fg, bg, attr) \
1309 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1310 LINE_INFO
1311 #undef LINE
1312 };
1314 static enum line_type
1315 get_line_type(const char *line)
1316 {
1317 int linelen = strlen(line);
1318 enum line_type type;
1320 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1321 /* Case insensitive search matches Signed-off-by lines better. */
1322 if (linelen >= line_info[type].linelen &&
1323 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1324 return type;
1326 return LINE_DEFAULT;
1327 }
1329 static inline int
1330 get_line_attr(enum line_type type)
1331 {
1332 assert(type < ARRAY_SIZE(line_info));
1333 return COLOR_PAIR(type) | line_info[type].attr;
1334 }
1336 static struct line_info *
1337 get_line_info(const char *name)
1338 {
1339 size_t namelen = strlen(name);
1340 enum line_type type;
1342 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1343 if (enum_equals(line_info[type], name, namelen))
1344 return &line_info[type];
1346 return NULL;
1347 }
1349 static void
1350 init_colors(void)
1351 {
1352 int default_bg = line_info[LINE_DEFAULT].bg;
1353 int default_fg = line_info[LINE_DEFAULT].fg;
1354 enum line_type type;
1356 start_color();
1358 if (assume_default_colors(default_fg, default_bg) == ERR) {
1359 default_bg = COLOR_BLACK;
1360 default_fg = COLOR_WHITE;
1361 }
1363 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1364 struct line_info *info = &line_info[type];
1365 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1366 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1368 init_pair(type, fg, bg);
1369 }
1370 }
1372 struct line {
1373 enum line_type type;
1375 /* State flags */
1376 unsigned int selected:1;
1377 unsigned int dirty:1;
1378 unsigned int cleareol:1;
1379 unsigned int other:16;
1381 void *data; /* User data */
1382 };
1385 /*
1386 * Keys
1387 */
1389 struct keybinding {
1390 int alias;
1391 enum request request;
1392 };
1394 static struct keybinding default_keybindings[] = {
1395 /* View switching */
1396 { 'm', REQ_VIEW_MAIN },
1397 { 'd', REQ_VIEW_DIFF },
1398 { 'l', REQ_VIEW_LOG },
1399 { 't', REQ_VIEW_TREE },
1400 { 'f', REQ_VIEW_BLOB },
1401 { 'B', REQ_VIEW_BLAME },
1402 { 'H', REQ_VIEW_BRANCH },
1403 { 'p', REQ_VIEW_PAGER },
1404 { 'h', REQ_VIEW_HELP },
1405 { 'S', REQ_VIEW_STATUS },
1406 { 'c', REQ_VIEW_STAGE },
1408 /* View manipulation */
1409 { 'q', REQ_VIEW_CLOSE },
1410 { KEY_TAB, REQ_VIEW_NEXT },
1411 { KEY_RETURN, REQ_ENTER },
1412 { KEY_UP, REQ_PREVIOUS },
1413 { KEY_DOWN, REQ_NEXT },
1414 { 'R', REQ_REFRESH },
1415 { KEY_F(5), REQ_REFRESH },
1416 { 'O', REQ_MAXIMIZE },
1418 /* Cursor navigation */
1419 { 'k', REQ_MOVE_UP },
1420 { 'j', REQ_MOVE_DOWN },
1421 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1422 { KEY_END, REQ_MOVE_LAST_LINE },
1423 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1424 { ' ', REQ_MOVE_PAGE_DOWN },
1425 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1426 { 'b', REQ_MOVE_PAGE_UP },
1427 { '-', REQ_MOVE_PAGE_UP },
1429 /* Scrolling */
1430 { KEY_LEFT, REQ_SCROLL_LEFT },
1431 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1432 { KEY_IC, REQ_SCROLL_LINE_UP },
1433 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1434 { 'w', REQ_SCROLL_PAGE_UP },
1435 { 's', REQ_SCROLL_PAGE_DOWN },
1437 /* Searching */
1438 { '/', REQ_SEARCH },
1439 { '?', REQ_SEARCH_BACK },
1440 { 'n', REQ_FIND_NEXT },
1441 { 'N', REQ_FIND_PREV },
1443 /* Misc */
1444 { 'Q', REQ_QUIT },
1445 { 'z', REQ_STOP_LOADING },
1446 { 'v', REQ_SHOW_VERSION },
1447 { 'r', REQ_SCREEN_REDRAW },
1448 { 'o', REQ_OPTIONS },
1449 { '.', REQ_TOGGLE_LINENO },
1450 { 'D', REQ_TOGGLE_DATE },
1451 { 'A', REQ_TOGGLE_AUTHOR },
1452 { 'g', REQ_TOGGLE_REV_GRAPH },
1453 { 'F', REQ_TOGGLE_REFS },
1454 { 'I', REQ_TOGGLE_SORT_ORDER },
1455 { 'i', REQ_TOGGLE_SORT_FIELD },
1456 { ':', REQ_PROMPT },
1457 { 'u', REQ_STATUS_UPDATE },
1458 { '!', REQ_STATUS_REVERT },
1459 { 'M', REQ_STATUS_MERGE },
1460 { '@', REQ_STAGE_NEXT },
1461 { ',', REQ_PARENT },
1462 { 'e', REQ_EDIT },
1463 };
1465 #define KEYMAP_INFO \
1466 KEYMAP_(GENERIC), \
1467 KEYMAP_(MAIN), \
1468 KEYMAP_(DIFF), \
1469 KEYMAP_(LOG), \
1470 KEYMAP_(TREE), \
1471 KEYMAP_(BLOB), \
1472 KEYMAP_(BLAME), \
1473 KEYMAP_(BRANCH), \
1474 KEYMAP_(PAGER), \
1475 KEYMAP_(HELP), \
1476 KEYMAP_(STATUS), \
1477 KEYMAP_(STAGE)
1479 enum keymap {
1480 #define KEYMAP_(name) KEYMAP_##name
1481 KEYMAP_INFO
1482 #undef KEYMAP_
1483 };
1485 static const struct enum_map keymap_table[] = {
1486 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1487 KEYMAP_INFO
1488 #undef KEYMAP_
1489 };
1491 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1493 struct keybinding_table {
1494 struct keybinding *data;
1495 size_t size;
1496 };
1498 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1500 static void
1501 add_keybinding(enum keymap keymap, enum request request, int key)
1502 {
1503 struct keybinding_table *table = &keybindings[keymap];
1504 size_t i;
1506 for (i = 0; i < keybindings[keymap].size; i++) {
1507 if (keybindings[keymap].data[i].alias == key) {
1508 keybindings[keymap].data[i].request = request;
1509 return;
1510 }
1511 }
1513 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1514 if (!table->data)
1515 die("Failed to allocate keybinding");
1516 table->data[table->size].alias = key;
1517 table->data[table->size++].request = request;
1519 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1520 int i;
1522 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1523 if (default_keybindings[i].alias == key)
1524 default_keybindings[i].request = REQ_NONE;
1525 }
1526 }
1528 /* Looks for a key binding first in the given map, then in the generic map, and
1529 * lastly in the default keybindings. */
1530 static enum request
1531 get_keybinding(enum keymap keymap, int key)
1532 {
1533 size_t i;
1535 for (i = 0; i < keybindings[keymap].size; i++)
1536 if (keybindings[keymap].data[i].alias == key)
1537 return keybindings[keymap].data[i].request;
1539 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1540 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1541 return keybindings[KEYMAP_GENERIC].data[i].request;
1543 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1544 if (default_keybindings[i].alias == key)
1545 return default_keybindings[i].request;
1547 return (enum request) key;
1548 }
1551 struct key {
1552 const char *name;
1553 int value;
1554 };
1556 static const struct key key_table[] = {
1557 { "Enter", KEY_RETURN },
1558 { "Space", ' ' },
1559 { "Backspace", KEY_BACKSPACE },
1560 { "Tab", KEY_TAB },
1561 { "Escape", KEY_ESC },
1562 { "Left", KEY_LEFT },
1563 { "Right", KEY_RIGHT },
1564 { "Up", KEY_UP },
1565 { "Down", KEY_DOWN },
1566 { "Insert", KEY_IC },
1567 { "Delete", KEY_DC },
1568 { "Hash", '#' },
1569 { "Home", KEY_HOME },
1570 { "End", KEY_END },
1571 { "PageUp", KEY_PPAGE },
1572 { "PageDown", KEY_NPAGE },
1573 { "F1", KEY_F(1) },
1574 { "F2", KEY_F(2) },
1575 { "F3", KEY_F(3) },
1576 { "F4", KEY_F(4) },
1577 { "F5", KEY_F(5) },
1578 { "F6", KEY_F(6) },
1579 { "F7", KEY_F(7) },
1580 { "F8", KEY_F(8) },
1581 { "F9", KEY_F(9) },
1582 { "F10", KEY_F(10) },
1583 { "F11", KEY_F(11) },
1584 { "F12", KEY_F(12) },
1585 };
1587 static int
1588 get_key_value(const char *name)
1589 {
1590 int i;
1592 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1593 if (!strcasecmp(key_table[i].name, name))
1594 return key_table[i].value;
1596 if (strlen(name) == 1 && isprint(*name))
1597 return (int) *name;
1599 return ERR;
1600 }
1602 static const char *
1603 get_key_name(int key_value)
1604 {
1605 static char key_char[] = "'X'";
1606 const char *seq = NULL;
1607 int key;
1609 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1610 if (key_table[key].value == key_value)
1611 seq = key_table[key].name;
1613 if (seq == NULL &&
1614 key_value < 127 &&
1615 isprint(key_value)) {
1616 key_char[1] = (char) key_value;
1617 seq = key_char;
1618 }
1620 return seq ? seq : "(no key)";
1621 }
1623 static bool
1624 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1625 {
1626 const char *sep = *pos > 0 ? ", " : "";
1627 const char *keyname = get_key_name(keybinding->alias);
1629 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1630 }
1632 static bool
1633 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1634 enum keymap keymap, bool all)
1635 {
1636 int i;
1638 for (i = 0; i < keybindings[keymap].size; i++) {
1639 if (keybindings[keymap].data[i].request == request) {
1640 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1641 return FALSE;
1642 if (!all)
1643 break;
1644 }
1645 }
1647 return TRUE;
1648 }
1650 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1652 static const char *
1653 get_keys(enum keymap keymap, enum request request, bool all)
1654 {
1655 static char buf[BUFSIZ];
1656 size_t pos = 0;
1657 int i;
1659 buf[pos] = 0;
1661 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1662 return "Too many keybindings!";
1663 if (pos > 0 && !all)
1664 return buf;
1666 if (keymap != KEYMAP_GENERIC) {
1667 /* Only the generic keymap includes the default keybindings when
1668 * listing all keys. */
1669 if (all)
1670 return buf;
1672 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1673 return "Too many keybindings!";
1674 if (pos)
1675 return buf;
1676 }
1678 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1679 if (default_keybindings[i].request == request) {
1680 if (!append_key(buf, &pos, &default_keybindings[i]))
1681 return "Too many keybindings!";
1682 if (!all)
1683 return buf;
1684 }
1685 }
1687 return buf;
1688 }
1690 struct run_request {
1691 enum keymap keymap;
1692 int key;
1693 const char *argv[SIZEOF_ARG];
1694 };
1696 static struct run_request *run_request;
1697 static size_t run_requests;
1699 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1701 static enum request
1702 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1703 {
1704 struct run_request *req;
1706 if (argc >= ARRAY_SIZE(req->argv) - 1)
1707 return REQ_NONE;
1709 if (!realloc_run_requests(&run_request, run_requests, 1))
1710 return REQ_NONE;
1712 req = &run_request[run_requests];
1713 req->keymap = keymap;
1714 req->key = key;
1715 req->argv[0] = NULL;
1717 if (!argv_copy(req->argv, argv))
1718 return REQ_NONE;
1720 return REQ_NONE + ++run_requests;
1721 }
1723 static struct run_request *
1724 get_run_request(enum request request)
1725 {
1726 if (request <= REQ_NONE)
1727 return NULL;
1728 return &run_request[request - REQ_NONE - 1];
1729 }
1731 static void
1732 add_builtin_run_requests(void)
1733 {
1734 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1735 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1736 const char *commit[] = { "git", "commit", NULL };
1737 const char *gc[] = { "git", "gc", NULL };
1738 struct {
1739 enum keymap keymap;
1740 int key;
1741 int argc;
1742 const char **argv;
1743 } reqs[] = {
1744 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1745 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1746 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1747 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1748 };
1749 int i;
1751 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1752 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1754 if (req != reqs[i].key)
1755 continue;
1756 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1757 if (req != REQ_NONE)
1758 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1759 }
1760 }
1762 /*
1763 * User config file handling.
1764 */
1766 static int config_lineno;
1767 static bool config_errors;
1768 static const char *config_msg;
1770 static const struct enum_map color_map[] = {
1771 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1772 COLOR_MAP(DEFAULT),
1773 COLOR_MAP(BLACK),
1774 COLOR_MAP(BLUE),
1775 COLOR_MAP(CYAN),
1776 COLOR_MAP(GREEN),
1777 COLOR_MAP(MAGENTA),
1778 COLOR_MAP(RED),
1779 COLOR_MAP(WHITE),
1780 COLOR_MAP(YELLOW),
1781 };
1783 static const struct enum_map attr_map[] = {
1784 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1785 ATTR_MAP(NORMAL),
1786 ATTR_MAP(BLINK),
1787 ATTR_MAP(BOLD),
1788 ATTR_MAP(DIM),
1789 ATTR_MAP(REVERSE),
1790 ATTR_MAP(STANDOUT),
1791 ATTR_MAP(UNDERLINE),
1792 };
1794 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1796 static int parse_step(double *opt, const char *arg)
1797 {
1798 *opt = atoi(arg);
1799 if (!strchr(arg, '%'))
1800 return OK;
1802 /* "Shift down" so 100% and 1 does not conflict. */
1803 *opt = (*opt - 1) / 100;
1804 if (*opt >= 1.0) {
1805 *opt = 0.99;
1806 config_msg = "Step value larger than 100%";
1807 return ERR;
1808 }
1809 if (*opt < 0.0) {
1810 *opt = 1;
1811 config_msg = "Invalid step value";
1812 return ERR;
1813 }
1814 return OK;
1815 }
1817 static int
1818 parse_int(int *opt, const char *arg, int min, int max)
1819 {
1820 int value = atoi(arg);
1822 if (min <= value && value <= max) {
1823 *opt = value;
1824 return OK;
1825 }
1827 config_msg = "Integer value out of bound";
1828 return ERR;
1829 }
1831 static bool
1832 set_color(int *color, const char *name)
1833 {
1834 if (map_enum(color, color_map, name))
1835 return TRUE;
1836 if (!prefixcmp(name, "color"))
1837 return parse_int(color, name + 5, 0, 255) == OK;
1838 return FALSE;
1839 }
1841 /* Wants: object fgcolor bgcolor [attribute] */
1842 static int
1843 option_color_command(int argc, const char *argv[])
1844 {
1845 struct line_info *info;
1847 if (argc < 3) {
1848 config_msg = "Wrong number of arguments given to color command";
1849 return ERR;
1850 }
1852 info = get_line_info(argv[0]);
1853 if (!info) {
1854 static const struct enum_map obsolete[] = {
1855 ENUM_MAP("main-delim", LINE_DELIMITER),
1856 ENUM_MAP("main-date", LINE_DATE),
1857 ENUM_MAP("main-author", LINE_AUTHOR),
1858 };
1859 int index;
1861 if (!map_enum(&index, obsolete, argv[0])) {
1862 config_msg = "Unknown color name";
1863 return ERR;
1864 }
1865 info = &line_info[index];
1866 }
1868 if (!set_color(&info->fg, argv[1]) ||
1869 !set_color(&info->bg, argv[2])) {
1870 config_msg = "Unknown color";
1871 return ERR;
1872 }
1874 info->attr = 0;
1875 while (argc-- > 3) {
1876 int attr;
1878 if (!set_attribute(&attr, argv[argc])) {
1879 config_msg = "Unknown attribute";
1880 return ERR;
1881 }
1882 info->attr |= attr;
1883 }
1885 return OK;
1886 }
1888 static int parse_bool(bool *opt, const char *arg)
1889 {
1890 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1891 ? TRUE : FALSE;
1892 return OK;
1893 }
1895 static int parse_enum_do(unsigned int *opt, const char *arg,
1896 const struct enum_map *map, size_t map_size)
1897 {
1898 bool is_true;
1900 assert(map_size > 1);
1902 if (map_enum_do(map, map_size, (int *) opt, arg))
1903 return OK;
1905 if (parse_bool(&is_true, arg) != OK)
1906 return ERR;
1908 *opt = is_true ? map[1].value : map[0].value;
1909 return OK;
1910 }
1912 #define parse_enum(opt, arg, map) \
1913 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1915 static int
1916 parse_string(char *opt, const char *arg, size_t optsize)
1917 {
1918 int arglen = strlen(arg);
1920 switch (arg[0]) {
1921 case '\"':
1922 case '\'':
1923 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1924 config_msg = "Unmatched quotation";
1925 return ERR;
1926 }
1927 arg += 1; arglen -= 2;
1928 default:
1929 string_ncopy_do(opt, optsize, arg, arglen);
1930 return OK;
1931 }
1932 }
1934 /* Wants: name = value */
1935 static int
1936 option_set_command(int argc, const char *argv[])
1937 {
1938 if (argc != 3) {
1939 config_msg = "Wrong number of arguments given to set command";
1940 return ERR;
1941 }
1943 if (strcmp(argv[1], "=")) {
1944 config_msg = "No value assigned";
1945 return ERR;
1946 }
1948 if (!strcmp(argv[0], "show-author"))
1949 return parse_enum(&opt_author, argv[2], author_map);
1951 if (!strcmp(argv[0], "show-date"))
1952 return parse_enum(&opt_date, argv[2], date_map);
1954 if (!strcmp(argv[0], "show-rev-graph"))
1955 return parse_bool(&opt_rev_graph, argv[2]);
1957 if (!strcmp(argv[0], "show-refs"))
1958 return parse_bool(&opt_show_refs, argv[2]);
1960 if (!strcmp(argv[0], "show-line-numbers"))
1961 return parse_bool(&opt_line_number, argv[2]);
1963 if (!strcmp(argv[0], "line-graphics"))
1964 return parse_bool(&opt_line_graphics, argv[2]);
1966 if (!strcmp(argv[0], "line-number-interval"))
1967 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1969 if (!strcmp(argv[0], "author-width"))
1970 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1972 if (!strcmp(argv[0], "horizontal-scroll"))
1973 return parse_step(&opt_hscroll, argv[2]);
1975 if (!strcmp(argv[0], "split-view-height"))
1976 return parse_step(&opt_scale_split_view, argv[2]);
1978 if (!strcmp(argv[0], "tab-size"))
1979 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1981 if (!strcmp(argv[0], "commit-encoding"))
1982 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1984 config_msg = "Unknown variable name";
1985 return ERR;
1986 }
1988 /* Wants: mode request key */
1989 static int
1990 option_bind_command(int argc, const char *argv[])
1991 {
1992 enum request request;
1993 int keymap = -1;
1994 int key;
1996 if (argc < 3) {
1997 config_msg = "Wrong number of arguments given to bind command";
1998 return ERR;
1999 }
2001 if (!set_keymap(&keymap, argv[0])) {
2002 config_msg = "Unknown key map";
2003 return ERR;
2004 }
2006 key = get_key_value(argv[1]);
2007 if (key == ERR) {
2008 config_msg = "Unknown key";
2009 return ERR;
2010 }
2012 request = get_request(argv[2]);
2013 if (request == REQ_UNKNOWN) {
2014 static const struct enum_map obsolete[] = {
2015 ENUM_MAP("cherry-pick", REQ_NONE),
2016 ENUM_MAP("screen-resize", REQ_NONE),
2017 ENUM_MAP("tree-parent", REQ_PARENT),
2018 };
2019 int alias;
2021 if (map_enum(&alias, obsolete, argv[2])) {
2022 if (alias != REQ_NONE)
2023 add_keybinding(keymap, alias, key);
2024 config_msg = "Obsolete request name";
2025 return ERR;
2026 }
2027 }
2028 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2029 request = add_run_request(keymap, key, argc - 2, argv + 2);
2030 if (request == REQ_UNKNOWN) {
2031 config_msg = "Unknown request name";
2032 return ERR;
2033 }
2035 add_keybinding(keymap, request, key);
2037 return OK;
2038 }
2040 static int
2041 set_option(const char *opt, char *value)
2042 {
2043 const char *argv[SIZEOF_ARG];
2044 int argc = 0;
2046 if (!argv_from_string(argv, &argc, value)) {
2047 config_msg = "Too many option arguments";
2048 return ERR;
2049 }
2051 if (!strcmp(opt, "color"))
2052 return option_color_command(argc, argv);
2054 if (!strcmp(opt, "set"))
2055 return option_set_command(argc, argv);
2057 if (!strcmp(opt, "bind"))
2058 return option_bind_command(argc, argv);
2060 config_msg = "Unknown option command";
2061 return ERR;
2062 }
2064 static int
2065 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2066 {
2067 int status = OK;
2069 config_lineno++;
2070 config_msg = "Internal error";
2072 /* Check for comment markers, since read_properties() will
2073 * only ensure opt and value are split at first " \t". */
2074 optlen = strcspn(opt, "#");
2075 if (optlen == 0)
2076 return OK;
2078 if (opt[optlen] != 0) {
2079 config_msg = "No option value";
2080 status = ERR;
2082 } else {
2083 /* Look for comment endings in the value. */
2084 size_t len = strcspn(value, "#");
2086 if (len < valuelen) {
2087 valuelen = len;
2088 value[valuelen] = 0;
2089 }
2091 status = set_option(opt, value);
2092 }
2094 if (status == ERR) {
2095 warn("Error on line %d, near '%.*s': %s",
2096 config_lineno, (int) optlen, opt, config_msg);
2097 config_errors = TRUE;
2098 }
2100 /* Always keep going if errors are encountered. */
2101 return OK;
2102 }
2104 static void
2105 load_option_file(const char *path)
2106 {
2107 struct io io = {};
2109 /* It's OK that the file doesn't exist. */
2110 if (!io_open(&io, "%s", path))
2111 return;
2113 config_lineno = 0;
2114 config_errors = FALSE;
2116 if (io_load(&io, " \t", read_option) == ERR ||
2117 config_errors == TRUE)
2118 warn("Errors while loading %s.", path);
2119 }
2121 static int
2122 load_options(void)
2123 {
2124 const char *home = getenv("HOME");
2125 const char *tigrc_user = getenv("TIGRC_USER");
2126 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2127 char buf[SIZEOF_STR];
2129 if (!tigrc_system)
2130 tigrc_system = SYSCONFDIR "/tigrc";
2131 load_option_file(tigrc_system);
2133 if (!tigrc_user) {
2134 if (!home || !string_format(buf, "%s/.tigrc", home))
2135 return ERR;
2136 tigrc_user = buf;
2137 }
2138 load_option_file(tigrc_user);
2140 /* Add _after_ loading config files to avoid adding run requests
2141 * that conflict with keybindings. */
2142 add_builtin_run_requests();
2144 return OK;
2145 }
2148 /*
2149 * The viewer
2150 */
2152 struct view;
2153 struct view_ops;
2155 /* The display array of active views and the index of the current view. */
2156 static struct view *display[2];
2157 static unsigned int current_view;
2159 #define foreach_displayed_view(view, i) \
2160 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2162 #define displayed_views() (display[1] != NULL ? 2 : 1)
2164 /* Current head and commit ID */
2165 static char ref_blob[SIZEOF_REF] = "";
2166 static char ref_commit[SIZEOF_REF] = "HEAD";
2167 static char ref_head[SIZEOF_REF] = "HEAD";
2168 static char ref_branch[SIZEOF_REF] = "";
2170 enum view_type {
2171 VIEW_MAIN,
2172 VIEW_DIFF,
2173 VIEW_LOG,
2174 VIEW_TREE,
2175 VIEW_BLOB,
2176 VIEW_BLAME,
2177 VIEW_BRANCH,
2178 VIEW_HELP,
2179 VIEW_PAGER,
2180 VIEW_STATUS,
2181 VIEW_STAGE,
2182 };
2184 struct view {
2185 enum view_type type; /* View type */
2186 const char *name; /* View name */
2187 const char *cmd_env; /* Command line set via environment */
2188 const char *id; /* Points to either of ref_{head,commit,blob} */
2190 struct view_ops *ops; /* View operations */
2192 enum keymap keymap; /* What keymap does this view have */
2193 bool git_dir; /* Whether the view requires a git directory. */
2195 char ref[SIZEOF_REF]; /* Hovered commit reference */
2196 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2198 int height, width; /* The width and height of the main window */
2199 WINDOW *win; /* The main window */
2200 WINDOW *title; /* The title window living below the main window */
2202 /* Navigation */
2203 unsigned long offset; /* Offset of the window top */
2204 unsigned long yoffset; /* Offset from the window side. */
2205 unsigned long lineno; /* Current line number */
2206 unsigned long p_offset; /* Previous offset of the window top */
2207 unsigned long p_yoffset;/* Previous offset from the window side */
2208 unsigned long p_lineno; /* Previous current line number */
2209 bool p_restore; /* Should the previous position be restored. */
2211 /* Searching */
2212 char grep[SIZEOF_STR]; /* Search string */
2213 regex_t *regex; /* Pre-compiled regexp */
2215 /* If non-NULL, points to the view that opened this view. If this view
2216 * is closed tig will switch back to the parent view. */
2217 struct view *parent;
2218 struct view *prev;
2220 /* Buffering */
2221 size_t lines; /* Total number of lines */
2222 struct line *line; /* Line index */
2223 unsigned int digits; /* Number of digits in the lines member. */
2225 /* Drawing */
2226 struct line *curline; /* Line currently being drawn. */
2227 enum line_type curtype; /* Attribute currently used for drawing. */
2228 unsigned long col; /* Column when drawing. */
2229 bool has_scrolled; /* View was scrolled. */
2231 /* Loading */
2232 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
2233 const char *dir; /* Directory from which to execute. */
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) \
2274 { type, name, #env, ref, ops, map, git }
2276 #define VIEW_(id, name, ops, git, ref) \
2277 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2279 static struct view views[] = {
2280 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2281 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2282 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2283 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2284 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2285 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2286 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2287 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2288 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2289 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2290 VIEW_(STAGE, "stage", &stage_ops, 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 static enum request
2302 view_request(struct view *view, enum request request)
2303 {
2304 if (!view || !view->lines)
2305 return request;
2306 return view->ops->request(view, request, &view->line[view->lineno]);
2307 }
2310 /*
2311 * View drawing.
2312 */
2314 static inline void
2315 set_view_attr(struct view *view, enum line_type type)
2316 {
2317 if (!view->curline->selected && view->curtype != type) {
2318 (void) wattrset(view->win, get_line_attr(type));
2319 wchgat(view->win, -1, 0, type, NULL);
2320 view->curtype = type;
2321 }
2322 }
2324 static int
2325 draw_chars(struct view *view, enum line_type type, const char *string,
2326 int max_len, bool use_tilde)
2327 {
2328 static char out_buffer[BUFSIZ * 2];
2329 int len = 0;
2330 int col = 0;
2331 int trimmed = FALSE;
2332 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2334 if (max_len <= 0)
2335 return 0;
2337 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2339 set_view_attr(view, type);
2340 if (len > 0) {
2341 if (opt_iconv_out != ICONV_NONE) {
2342 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2343 size_t inlen = len + 1;
2345 char *outbuf = out_buffer;
2346 size_t outlen = sizeof(out_buffer);
2348 size_t ret;
2350 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2351 if (ret != (size_t) -1) {
2352 string = out_buffer;
2353 len = sizeof(out_buffer) - outlen;
2354 }
2355 }
2357 waddnstr(view->win, string, len);
2358 }
2359 if (trimmed && use_tilde) {
2360 set_view_attr(view, LINE_DELIMITER);
2361 waddch(view->win, '~');
2362 col++;
2363 }
2365 return col;
2366 }
2368 static int
2369 draw_space(struct view *view, enum line_type type, int max, int spaces)
2370 {
2371 static char space[] = " ";
2372 int col = 0;
2374 spaces = MIN(max, spaces);
2376 while (spaces > 0) {
2377 int len = MIN(spaces, sizeof(space) - 1);
2379 col += draw_chars(view, type, space, len, FALSE);
2380 spaces -= len;
2381 }
2383 return col;
2384 }
2386 static bool
2387 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2388 {
2389 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2390 return view->width + view->yoffset <= view->col;
2391 }
2393 static bool
2394 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2395 {
2396 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2397 int max = view->width + view->yoffset - view->col;
2398 int i;
2400 if (max < size)
2401 size = max;
2403 set_view_attr(view, type);
2404 /* Using waddch() instead of waddnstr() ensures that
2405 * they'll be rendered correctly for the cursor line. */
2406 for (i = skip; i < size; i++)
2407 waddch(view->win, graphic[i]);
2409 view->col += size;
2410 if (size < max && skip <= size)
2411 waddch(view->win, ' ');
2412 view->col++;
2414 return view->width + view->yoffset <= view->col;
2415 }
2417 static bool
2418 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2419 {
2420 int max = MIN(view->width + view->yoffset - view->col, len);
2421 int col;
2423 if (text)
2424 col = draw_chars(view, type, text, max - 1, trim);
2425 else
2426 col = draw_space(view, type, max - 1, max - 1);
2428 view->col += col;
2429 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2430 return view->width + view->yoffset <= view->col;
2431 }
2433 static bool
2434 draw_date(struct view *view, struct time *time)
2435 {
2436 const char *date = mkdate(time, opt_date);
2437 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2439 return draw_field(view, LINE_DATE, date, cols, FALSE);
2440 }
2442 static bool
2443 draw_author(struct view *view, const char *author)
2444 {
2445 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2446 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2448 if (abbreviate && author)
2449 author = get_author_initials(author);
2451 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2452 }
2454 static bool
2455 draw_mode(struct view *view, mode_t mode)
2456 {
2457 const char *str;
2459 if (S_ISDIR(mode))
2460 str = "drwxr-xr-x";
2461 else if (S_ISLNK(mode))
2462 str = "lrwxrwxrwx";
2463 else if (S_ISGITLINK(mode))
2464 str = "m---------";
2465 else if (S_ISREG(mode) && mode & S_IXUSR)
2466 str = "-rwxr-xr-x";
2467 else if (S_ISREG(mode))
2468 str = "-rw-r--r--";
2469 else
2470 str = "----------";
2472 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2473 }
2475 static bool
2476 draw_lineno(struct view *view, unsigned int lineno)
2477 {
2478 char number[10];
2479 int digits3 = view->digits < 3 ? 3 : view->digits;
2480 int max = MIN(view->width + view->yoffset - view->col, digits3);
2481 char *text = NULL;
2482 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2484 lineno += view->offset + 1;
2485 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2486 static char fmt[] = "%1ld";
2488 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2489 if (string_format(number, fmt, lineno))
2490 text = number;
2491 }
2492 if (text)
2493 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2494 else
2495 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2496 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2497 }
2499 static bool
2500 draw_view_line(struct view *view, unsigned int lineno)
2501 {
2502 struct line *line;
2503 bool selected = (view->offset + lineno == view->lineno);
2505 assert(view_is_displayed(view));
2507 if (view->offset + lineno >= view->lines)
2508 return FALSE;
2510 line = &view->line[view->offset + lineno];
2512 wmove(view->win, lineno, 0);
2513 if (line->cleareol)
2514 wclrtoeol(view->win);
2515 view->col = 0;
2516 view->curline = line;
2517 view->curtype = LINE_NONE;
2518 line->selected = FALSE;
2519 line->dirty = line->cleareol = 0;
2521 if (selected) {
2522 set_view_attr(view, LINE_CURSOR);
2523 line->selected = TRUE;
2524 view->ops->select(view, line);
2525 }
2527 return view->ops->draw(view, line, lineno);
2528 }
2530 static void
2531 redraw_view_dirty(struct view *view)
2532 {
2533 bool dirty = FALSE;
2534 int lineno;
2536 for (lineno = 0; lineno < view->height; lineno++) {
2537 if (view->offset + lineno >= view->lines)
2538 break;
2539 if (!view->line[view->offset + lineno].dirty)
2540 continue;
2541 dirty = TRUE;
2542 if (!draw_view_line(view, lineno))
2543 break;
2544 }
2546 if (!dirty)
2547 return;
2548 wnoutrefresh(view->win);
2549 }
2551 static void
2552 redraw_view_from(struct view *view, int lineno)
2553 {
2554 assert(0 <= lineno && lineno < view->height);
2556 for (; lineno < view->height; lineno++) {
2557 if (!draw_view_line(view, lineno))
2558 break;
2559 }
2561 wnoutrefresh(view->win);
2562 }
2564 static void
2565 redraw_view(struct view *view)
2566 {
2567 werase(view->win);
2568 redraw_view_from(view, 0);
2569 }
2572 static void
2573 update_view_title(struct view *view)
2574 {
2575 char buf[SIZEOF_STR];
2576 char state[SIZEOF_STR];
2577 size_t bufpos = 0, statelen = 0;
2579 assert(view_is_displayed(view));
2581 if (view->type != VIEW_STATUS && view->lines) {
2582 unsigned int view_lines = view->offset + view->height;
2583 unsigned int lines = view->lines
2584 ? MIN(view_lines, view->lines) * 100 / view->lines
2585 : 0;
2587 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2588 view->ops->type,
2589 view->lineno + 1,
2590 view->lines,
2591 lines);
2593 }
2595 if (view->pipe) {
2596 time_t secs = time(NULL) - view->start_time;
2598 /* Three git seconds are a long time ... */
2599 if (secs > 2)
2600 string_format_from(state, &statelen, " loading %lds", secs);
2601 }
2603 string_format_from(buf, &bufpos, "[%s]", view->name);
2604 if (*view->ref && bufpos < view->width) {
2605 size_t refsize = strlen(view->ref);
2606 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2608 if (minsize < view->width)
2609 refsize = view->width - minsize + 7;
2610 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2611 }
2613 if (statelen && bufpos < view->width) {
2614 string_format_from(buf, &bufpos, "%s", state);
2615 }
2617 if (view == display[current_view])
2618 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2619 else
2620 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2622 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2623 wclrtoeol(view->title);
2624 wnoutrefresh(view->title);
2625 }
2627 static int
2628 apply_step(double step, int value)
2629 {
2630 if (step >= 1)
2631 return (int) step;
2632 value *= step + 0.01;
2633 return value ? value : 1;
2634 }
2636 static void
2637 resize_display(void)
2638 {
2639 int offset, i;
2640 struct view *base = display[0];
2641 struct view *view = display[1] ? display[1] : display[0];
2643 /* Setup window dimensions */
2645 getmaxyx(stdscr, base->height, base->width);
2647 /* Make room for the status window. */
2648 base->height -= 1;
2650 if (view != base) {
2651 /* Horizontal split. */
2652 view->width = base->width;
2653 view->height = apply_step(opt_scale_split_view, base->height);
2654 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2655 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2656 base->height -= view->height;
2658 /* Make room for the title bar. */
2659 view->height -= 1;
2660 }
2662 /* Make room for the title bar. */
2663 base->height -= 1;
2665 offset = 0;
2667 foreach_displayed_view (view, i) {
2668 if (!view->win) {
2669 view->win = newwin(view->height, 0, offset, 0);
2670 if (!view->win)
2671 die("Failed to create %s view", view->name);
2673 scrollok(view->win, FALSE);
2675 view->title = newwin(1, 0, offset + view->height, 0);
2676 if (!view->title)
2677 die("Failed to create title window");
2679 } else {
2680 wresize(view->win, view->height, view->width);
2681 mvwin(view->win, offset, 0);
2682 mvwin(view->title, offset + view->height, 0);
2683 }
2685 offset += view->height + 1;
2686 }
2687 }
2689 static void
2690 redraw_display(bool clear)
2691 {
2692 struct view *view;
2693 int i;
2695 foreach_displayed_view (view, i) {
2696 if (clear)
2697 wclear(view->win);
2698 redraw_view(view);
2699 update_view_title(view);
2700 }
2701 }
2704 /*
2705 * Option management
2706 */
2708 static void
2709 toggle_enum_option_do(unsigned int *opt, const char *help,
2710 const struct enum_map *map, size_t size)
2711 {
2712 *opt = (*opt + 1) % size;
2713 redraw_display(FALSE);
2714 report("Displaying %s %s", enum_name(map[*opt]), help);
2715 }
2717 #define toggle_enum_option(opt, help, map) \
2718 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2720 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2721 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2723 static void
2724 toggle_view_option(bool *option, const char *help)
2725 {
2726 *option = !*option;
2727 redraw_display(FALSE);
2728 report("%sabling %s", *option ? "En" : "Dis", help);
2729 }
2731 static void
2732 open_option_menu(void)
2733 {
2734 const struct menu_item menu[] = {
2735 { '.', "line numbers", &opt_line_number },
2736 { 'D', "date display", &opt_date },
2737 { 'A', "author display", &opt_author },
2738 { 'g', "revision graph display", &opt_rev_graph },
2739 { 'F', "reference display", &opt_show_refs },
2740 { 0 }
2741 };
2742 int selected = 0;
2744 if (prompt_menu("Toggle option", menu, &selected)) {
2745 if (menu[selected].data == &opt_date)
2746 toggle_date();
2747 else if (menu[selected].data == &opt_author)
2748 toggle_author();
2749 else
2750 toggle_view_option(menu[selected].data, menu[selected].text);
2751 }
2752 }
2754 static void
2755 maximize_view(struct view *view)
2756 {
2757 memset(display, 0, sizeof(display));
2758 current_view = 0;
2759 display[current_view] = view;
2760 resize_display();
2761 redraw_display(FALSE);
2762 report("");
2763 }
2766 /*
2767 * Navigation
2768 */
2770 static bool
2771 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2772 {
2773 if (lineno >= view->lines)
2774 lineno = view->lines > 0 ? view->lines - 1 : 0;
2776 if (offset > lineno || offset + view->height <= lineno) {
2777 unsigned long half = view->height / 2;
2779 if (lineno > half)
2780 offset = lineno - half;
2781 else
2782 offset = 0;
2783 }
2785 if (offset != view->offset || lineno != view->lineno) {
2786 view->offset = offset;
2787 view->lineno = lineno;
2788 return TRUE;
2789 }
2791 return FALSE;
2792 }
2794 /* Scrolling backend */
2795 static void
2796 do_scroll_view(struct view *view, int lines)
2797 {
2798 bool redraw_current_line = FALSE;
2800 /* The rendering expects the new offset. */
2801 view->offset += lines;
2803 assert(0 <= view->offset && view->offset < view->lines);
2804 assert(lines);
2806 /* Move current line into the view. */
2807 if (view->lineno < view->offset) {
2808 view->lineno = view->offset;
2809 redraw_current_line = TRUE;
2810 } else if (view->lineno >= view->offset + view->height) {
2811 view->lineno = view->offset + view->height - 1;
2812 redraw_current_line = TRUE;
2813 }
2815 assert(view->offset <= view->lineno && view->lineno < view->lines);
2817 /* Redraw the whole screen if scrolling is pointless. */
2818 if (view->height < ABS(lines)) {
2819 redraw_view(view);
2821 } else {
2822 int line = lines > 0 ? view->height - lines : 0;
2823 int end = line + ABS(lines);
2825 scrollok(view->win, TRUE);
2826 wscrl(view->win, lines);
2827 scrollok(view->win, FALSE);
2829 while (line < end && draw_view_line(view, line))
2830 line++;
2832 if (redraw_current_line)
2833 draw_view_line(view, view->lineno - view->offset);
2834 wnoutrefresh(view->win);
2835 }
2837 view->has_scrolled = TRUE;
2838 report("");
2839 }
2841 /* Scroll frontend */
2842 static void
2843 scroll_view(struct view *view, enum request request)
2844 {
2845 int lines = 1;
2847 assert(view_is_displayed(view));
2849 switch (request) {
2850 case REQ_SCROLL_LEFT:
2851 if (view->yoffset == 0) {
2852 report("Cannot scroll beyond the first column");
2853 return;
2854 }
2855 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2856 view->yoffset = 0;
2857 else
2858 view->yoffset -= apply_step(opt_hscroll, view->width);
2859 redraw_view_from(view, 0);
2860 report("");
2861 return;
2862 case REQ_SCROLL_RIGHT:
2863 view->yoffset += apply_step(opt_hscroll, view->width);
2864 redraw_view(view);
2865 report("");
2866 return;
2867 case REQ_SCROLL_PAGE_DOWN:
2868 lines = view->height;
2869 case REQ_SCROLL_LINE_DOWN:
2870 if (view->offset + lines > view->lines)
2871 lines = view->lines - view->offset;
2873 if (lines == 0 || view->offset + view->height >= view->lines) {
2874 report("Cannot scroll beyond the last line");
2875 return;
2876 }
2877 break;
2879 case REQ_SCROLL_PAGE_UP:
2880 lines = view->height;
2881 case REQ_SCROLL_LINE_UP:
2882 if (lines > view->offset)
2883 lines = view->offset;
2885 if (lines == 0) {
2886 report("Cannot scroll beyond the first line");
2887 return;
2888 }
2890 lines = -lines;
2891 break;
2893 default:
2894 die("request %d not handled in switch", request);
2895 }
2897 do_scroll_view(view, lines);
2898 }
2900 /* Cursor moving */
2901 static void
2902 move_view(struct view *view, enum request request)
2903 {
2904 int scroll_steps = 0;
2905 int steps;
2907 switch (request) {
2908 case REQ_MOVE_FIRST_LINE:
2909 steps = -view->lineno;
2910 break;
2912 case REQ_MOVE_LAST_LINE:
2913 steps = view->lines - view->lineno - 1;
2914 break;
2916 case REQ_MOVE_PAGE_UP:
2917 steps = view->height > view->lineno
2918 ? -view->lineno : -view->height;
2919 break;
2921 case REQ_MOVE_PAGE_DOWN:
2922 steps = view->lineno + view->height >= view->lines
2923 ? view->lines - view->lineno - 1 : view->height;
2924 break;
2926 case REQ_MOVE_UP:
2927 steps = -1;
2928 break;
2930 case REQ_MOVE_DOWN:
2931 steps = 1;
2932 break;
2934 default:
2935 die("request %d not handled in switch", request);
2936 }
2938 if (steps <= 0 && view->lineno == 0) {
2939 report("Cannot move beyond the first line");
2940 return;
2942 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2943 report("Cannot move beyond the last line");
2944 return;
2945 }
2947 /* Move the current line */
2948 view->lineno += steps;
2949 assert(0 <= view->lineno && view->lineno < view->lines);
2951 /* Check whether the view needs to be scrolled */
2952 if (view->lineno < view->offset ||
2953 view->lineno >= view->offset + view->height) {
2954 scroll_steps = steps;
2955 if (steps < 0 && -steps > view->offset) {
2956 scroll_steps = -view->offset;
2958 } else if (steps > 0) {
2959 if (view->lineno == view->lines - 1 &&
2960 view->lines > view->height) {
2961 scroll_steps = view->lines - view->offset - 1;
2962 if (scroll_steps >= view->height)
2963 scroll_steps -= view->height - 1;
2964 }
2965 }
2966 }
2968 if (!view_is_displayed(view)) {
2969 view->offset += scroll_steps;
2970 assert(0 <= view->offset && view->offset < view->lines);
2971 view->ops->select(view, &view->line[view->lineno]);
2972 return;
2973 }
2975 /* Repaint the old "current" line if we be scrolling */
2976 if (ABS(steps) < view->height)
2977 draw_view_line(view, view->lineno - steps - view->offset);
2979 if (scroll_steps) {
2980 do_scroll_view(view, scroll_steps);
2981 return;
2982 }
2984 /* Draw the current line */
2985 draw_view_line(view, view->lineno - view->offset);
2987 wnoutrefresh(view->win);
2988 report("");
2989 }
2992 /*
2993 * Searching
2994 */
2996 static void search_view(struct view *view, enum request request);
2998 static bool
2999 grep_text(struct view *view, const char *text[])
3000 {
3001 regmatch_t pmatch;
3002 size_t i;
3004 for (i = 0; text[i]; i++)
3005 if (*text[i] &&
3006 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3007 return TRUE;
3008 return FALSE;
3009 }
3011 static void
3012 select_view_line(struct view *view, unsigned long lineno)
3013 {
3014 unsigned long old_lineno = view->lineno;
3015 unsigned long old_offset = view->offset;
3017 if (goto_view_line(view, view->offset, lineno)) {
3018 if (view_is_displayed(view)) {
3019 if (old_offset != view->offset) {
3020 redraw_view(view);
3021 } else {
3022 draw_view_line(view, old_lineno - view->offset);
3023 draw_view_line(view, view->lineno - view->offset);
3024 wnoutrefresh(view->win);
3025 }
3026 } else {
3027 view->ops->select(view, &view->line[view->lineno]);
3028 }
3029 }
3030 }
3032 static void
3033 find_next(struct view *view, enum request request)
3034 {
3035 unsigned long lineno = view->lineno;
3036 int direction;
3038 if (!*view->grep) {
3039 if (!*opt_search)
3040 report("No previous search");
3041 else
3042 search_view(view, request);
3043 return;
3044 }
3046 switch (request) {
3047 case REQ_SEARCH:
3048 case REQ_FIND_NEXT:
3049 direction = 1;
3050 break;
3052 case REQ_SEARCH_BACK:
3053 case REQ_FIND_PREV:
3054 direction = -1;
3055 break;
3057 default:
3058 return;
3059 }
3061 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3062 lineno += direction;
3064 /* Note, lineno is unsigned long so will wrap around in which case it
3065 * will become bigger than view->lines. */
3066 for (; lineno < view->lines; lineno += direction) {
3067 if (view->ops->grep(view, &view->line[lineno])) {
3068 select_view_line(view, lineno);
3069 report("Line %ld matches '%s'", lineno + 1, view->grep);
3070 return;
3071 }
3072 }
3074 report("No match found for '%s'", view->grep);
3075 }
3077 static void
3078 search_view(struct view *view, enum request request)
3079 {
3080 int regex_err;
3082 if (view->regex) {
3083 regfree(view->regex);
3084 *view->grep = 0;
3085 } else {
3086 view->regex = calloc(1, sizeof(*view->regex));
3087 if (!view->regex)
3088 return;
3089 }
3091 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3092 if (regex_err != 0) {
3093 char buf[SIZEOF_STR] = "unknown error";
3095 regerror(regex_err, view->regex, buf, sizeof(buf));
3096 report("Search failed: %s", buf);
3097 return;
3098 }
3100 string_copy(view->grep, opt_search);
3102 find_next(view, request);
3103 }
3105 /*
3106 * Incremental updating
3107 */
3109 static void
3110 reset_view(struct view *view)
3111 {
3112 int i;
3114 for (i = 0; i < view->lines; i++)
3115 free(view->line[i].data);
3116 free(view->line);
3118 view->p_offset = view->offset;
3119 view->p_yoffset = view->yoffset;
3120 view->p_lineno = view->lineno;
3122 view->line = NULL;
3123 view->offset = 0;
3124 view->yoffset = 0;
3125 view->lines = 0;
3126 view->lineno = 0;
3127 view->vid[0] = 0;
3128 view->update_secs = 0;
3129 }
3131 static const char *
3132 format_arg(const char *name)
3133 {
3134 static struct {
3135 const char *name;
3136 size_t namelen;
3137 const char *value;
3138 const char *value_if_empty;
3139 } vars[] = {
3140 #define FORMAT_VAR(name, value, value_if_empty) \
3141 { name, STRING_SIZE(name), value, value_if_empty }
3142 FORMAT_VAR("%(directory)", opt_path, ""),
3143 FORMAT_VAR("%(file)", opt_file, ""),
3144 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3145 FORMAT_VAR("%(head)", ref_head, ""),
3146 FORMAT_VAR("%(commit)", ref_commit, ""),
3147 FORMAT_VAR("%(blob)", ref_blob, ""),
3148 FORMAT_VAR("%(branch)", ref_branch, ""),
3149 };
3150 int i;
3152 for (i = 0; i < ARRAY_SIZE(vars); i++)
3153 if (!strncmp(name, vars[i].name, vars[i].namelen))
3154 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3156 report("Unknown replacement: `%s`", name);
3157 return NULL;
3158 }
3160 static bool
3161 format_argv(const char *dst_argv[], const char *src_argv[], bool replace)
3162 {
3163 char buf[SIZEOF_STR];
3164 int argc;
3166 argv_free(dst_argv);
3168 for (argc = 0; src_argv[argc]; argc++) {
3169 const char *arg = src_argv[argc];
3170 size_t bufpos = 0;
3172 while (arg) {
3173 char *next = strstr(arg, "%(");
3174 int len = next - arg;
3175 const char *value;
3177 if (!next || !replace) {
3178 len = strlen(arg);
3179 value = "";
3181 } else {
3182 value = format_arg(next);
3184 if (!value) {
3185 return FALSE;
3186 }
3187 }
3189 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3190 return FALSE;
3192 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3193 }
3195 dst_argv[argc] = strdup(buf);
3196 if (!dst_argv[argc])
3197 break;
3198 }
3200 dst_argv[argc] = NULL;
3202 return src_argv[argc] == NULL;
3203 }
3205 static bool
3206 restore_view_position(struct view *view)
3207 {
3208 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3209 return FALSE;
3211 /* Changing the view position cancels the restoring. */
3212 /* FIXME: Changing back to the first line is not detected. */
3213 if (view->offset != 0 || view->lineno != 0) {
3214 view->p_restore = FALSE;
3215 return FALSE;
3216 }
3218 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3219 view_is_displayed(view))
3220 werase(view->win);
3222 view->yoffset = view->p_yoffset;
3223 view->p_restore = FALSE;
3225 return TRUE;
3226 }
3228 static void
3229 end_update(struct view *view, bool force)
3230 {
3231 if (!view->pipe)
3232 return;
3233 while (!view->ops->read(view, NULL))
3234 if (!force)
3235 return;
3236 if (force)
3237 io_kill(view->pipe);
3238 io_done(view->pipe);
3239 view->pipe = NULL;
3240 }
3242 static void
3243 setup_update(struct view *view, const char *vid)
3244 {
3245 reset_view(view);
3246 string_copy_rev(view->vid, vid);
3247 view->pipe = &view->io;
3248 view->start_time = time(NULL);
3249 }
3251 static bool
3252 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3253 {
3254 io_init(&view->io);
3255 view->dir = dir;
3256 return format_argv(view->argv, argv, replace);
3257 }
3259 static bool
3260 prepare_update(struct view *view, const char *argv[], const char *dir)
3261 {
3262 if (view->pipe)
3263 end_update(view, TRUE);
3264 return prepare_io(view, dir, argv, FALSE);
3265 }
3267 static bool
3268 start_update(struct view *view, const char **argv, const char *dir)
3269 {
3270 if (view->pipe)
3271 io_done(view->pipe);
3272 return prepare_io(view, dir, argv, FALSE) &&
3273 io_start(&view->io, IO_RD, dir, view->argv);
3274 }
3276 static bool
3277 prepare_update_file(struct view *view, const char *name)
3278 {
3279 if (view->pipe)
3280 end_update(view, TRUE);
3281 argv_free(view->argv);
3282 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3283 }
3285 static bool
3286 begin_update(struct view *view, bool refresh)
3287 {
3288 if (view->pipe)
3289 end_update(view, TRUE);
3291 if (!refresh) {
3292 if (view->ops->prepare) {
3293 if (!view->ops->prepare(view))
3294 return FALSE;
3295 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3296 return FALSE;
3297 }
3299 /* Put the current ref_* value to the view title ref
3300 * member. This is needed by the blob view. Most other
3301 * views sets it automatically after loading because the
3302 * first line is a commit line. */
3303 string_copy_rev(view->ref, view->id);
3304 }
3306 if (view->argv[0] && !io_start(&view->io, IO_RD, view->dir, view->argv))
3307 return FALSE;
3309 setup_update(view, view->id);
3311 return TRUE;
3312 }
3314 static bool
3315 update_view(struct view *view)
3316 {
3317 char out_buffer[BUFSIZ * 2];
3318 char *line;
3319 /* Clear the view and redraw everything since the tree sorting
3320 * might have rearranged things. */
3321 bool redraw = view->lines == 0;
3322 bool can_read = TRUE;
3324 if (!view->pipe)
3325 return TRUE;
3327 if (!io_can_read(view->pipe)) {
3328 if (view->lines == 0 && view_is_displayed(view)) {
3329 time_t secs = time(NULL) - view->start_time;
3331 if (secs > 1 && secs > view->update_secs) {
3332 if (view->update_secs == 0)
3333 redraw_view(view);
3334 update_view_title(view);
3335 view->update_secs = secs;
3336 }
3337 }
3338 return TRUE;
3339 }
3341 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3342 if (opt_iconv_in != ICONV_NONE) {
3343 ICONV_CONST char *inbuf = line;
3344 size_t inlen = strlen(line) + 1;
3346 char *outbuf = out_buffer;
3347 size_t outlen = sizeof(out_buffer);
3349 size_t ret;
3351 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3352 if (ret != (size_t) -1)
3353 line = out_buffer;
3354 }
3356 if (!view->ops->read(view, line)) {
3357 report("Allocation failure");
3358 end_update(view, TRUE);
3359 return FALSE;
3360 }
3361 }
3363 {
3364 unsigned long lines = view->lines;
3365 int digits;
3367 for (digits = 0; lines; digits++)
3368 lines /= 10;
3370 /* Keep the displayed view in sync with line number scaling. */
3371 if (digits != view->digits) {
3372 view->digits = digits;
3373 if (opt_line_number || view->type == VIEW_BLAME)
3374 redraw = TRUE;
3375 }
3376 }
3378 if (io_error(view->pipe)) {
3379 report("Failed to read: %s", io_strerror(view->pipe));
3380 end_update(view, TRUE);
3382 } else if (io_eof(view->pipe)) {
3383 if (view_is_displayed(view))
3384 report("");
3385 end_update(view, FALSE);
3386 }
3388 if (restore_view_position(view))
3389 redraw = TRUE;
3391 if (!view_is_displayed(view))
3392 return TRUE;
3394 if (redraw)
3395 redraw_view_from(view, 0);
3396 else
3397 redraw_view_dirty(view);
3399 /* Update the title _after_ the redraw so that if the redraw picks up a
3400 * commit reference in view->ref it'll be available here. */
3401 update_view_title(view);
3402 return TRUE;
3403 }
3405 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3407 static struct line *
3408 add_line_data(struct view *view, void *data, enum line_type type)
3409 {
3410 struct line *line;
3412 if (!realloc_lines(&view->line, view->lines, 1))
3413 return NULL;
3415 line = &view->line[view->lines++];
3416 memset(line, 0, sizeof(*line));
3417 line->type = type;
3418 line->data = data;
3419 line->dirty = 1;
3421 return line;
3422 }
3424 static struct line *
3425 add_line_text(struct view *view, const char *text, enum line_type type)
3426 {
3427 char *data = text ? strdup(text) : NULL;
3429 return data ? add_line_data(view, data, type) : NULL;
3430 }
3432 static struct line *
3433 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3434 {
3435 char buf[SIZEOF_STR];
3436 va_list args;
3438 va_start(args, fmt);
3439 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3440 buf[0] = 0;
3441 va_end(args);
3443 return buf[0] ? add_line_text(view, buf, type) : NULL;
3444 }
3446 /*
3447 * View opening
3448 */
3450 enum open_flags {
3451 OPEN_DEFAULT = 0, /* Use default view switching. */
3452 OPEN_SPLIT = 1, /* Split current view. */
3453 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3454 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3455 OPEN_PREPARED = 32, /* Open already prepared command. */
3456 };
3458 static void
3459 open_view(struct view *prev, enum request request, enum open_flags flags)
3460 {
3461 bool split = !!(flags & OPEN_SPLIT);
3462 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3463 bool nomaximize = !!(flags & OPEN_REFRESH);
3464 struct view *view = VIEW(request);
3465 int nviews = displayed_views();
3466 struct view *base_view = display[0];
3468 if (view == prev && nviews == 1 && !reload) {
3469 report("Already in %s view", view->name);
3470 return;
3471 }
3473 if (view->git_dir && !opt_git_dir[0]) {
3474 report("The %s view is disabled in pager view", view->name);
3475 return;
3476 }
3478 if (split) {
3479 display[1] = view;
3480 current_view = 1;
3481 view->parent = prev;
3482 } else if (!nomaximize) {
3483 /* Maximize the current view. */
3484 memset(display, 0, sizeof(display));
3485 current_view = 0;
3486 display[current_view] = view;
3487 }
3489 /* No prev signals that this is the first loaded view. */
3490 if (prev && view != prev) {
3491 view->prev = prev;
3492 }
3494 /* Resize the view when switching between split- and full-screen,
3495 * or when switching between two different full-screen views. */
3496 if (nviews != displayed_views() ||
3497 (nviews == 1 && base_view != display[0]))
3498 resize_display();
3500 if (view->ops->open) {
3501 if (view->pipe)
3502 end_update(view, TRUE);
3503 if (!view->ops->open(view)) {
3504 report("Failed to load %s view", view->name);
3505 return;
3506 }
3507 restore_view_position(view);
3509 } else if ((reload || strcmp(view->vid, view->id)) &&
3510 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3511 report("Failed to load %s view", view->name);
3512 return;
3513 }
3515 if (split && prev->lineno - prev->offset >= prev->height) {
3516 /* Take the title line into account. */
3517 int lines = prev->lineno - prev->offset - prev->height + 1;
3519 /* Scroll the view that was split if the current line is
3520 * outside the new limited view. */
3521 do_scroll_view(prev, lines);
3522 }
3524 if (prev && view != prev && split && view_is_displayed(prev)) {
3525 /* "Blur" the previous view. */
3526 update_view_title(prev);
3527 }
3529 if (view->pipe && view->lines == 0) {
3530 /* Clear the old view and let the incremental updating refill
3531 * the screen. */
3532 werase(view->win);
3533 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3534 report("");
3535 } else if (view_is_displayed(view)) {
3536 redraw_view(view);
3537 report("");
3538 }
3539 }
3541 static void
3542 open_external_viewer(const char *argv[], const char *dir)
3543 {
3544 def_prog_mode(); /* save current tty modes */
3545 endwin(); /* restore original tty modes */
3546 io_run_fg(argv, dir);
3547 fprintf(stderr, "Press Enter to continue");
3548 getc(opt_tty);
3549 reset_prog_mode();
3550 redraw_display(TRUE);
3551 }
3553 static void
3554 open_mergetool(const char *file)
3555 {
3556 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3558 open_external_viewer(mergetool_argv, opt_cdup);
3559 }
3561 static void
3562 open_editor(const char *file)
3563 {
3564 const char *editor_argv[] = { "vi", file, NULL };
3565 const char *editor;
3567 editor = getenv("GIT_EDITOR");
3568 if (!editor && *opt_editor)
3569 editor = opt_editor;
3570 if (!editor)
3571 editor = getenv("VISUAL");
3572 if (!editor)
3573 editor = getenv("EDITOR");
3574 if (!editor)
3575 editor = "vi";
3577 editor_argv[0] = editor;
3578 open_external_viewer(editor_argv, opt_cdup);
3579 }
3581 static void
3582 open_run_request(enum request request)
3583 {
3584 struct run_request *req = get_run_request(request);
3585 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3587 if (!req) {
3588 report("Unknown run request");
3589 return;
3590 }
3592 if (format_argv(argv, req->argv, TRUE))
3593 open_external_viewer(argv, NULL);
3594 argv_free(argv);
3595 }
3597 /*
3598 * User request switch noodle
3599 */
3601 static int
3602 view_driver(struct view *view, enum request request)
3603 {
3604 int i;
3606 if (request == REQ_NONE)
3607 return TRUE;
3609 if (request > REQ_NONE) {
3610 open_run_request(request);
3611 view_request(view, REQ_REFRESH);
3612 return TRUE;
3613 }
3615 request = view_request(view, request);
3616 if (request == REQ_NONE)
3617 return TRUE;
3619 switch (request) {
3620 case REQ_MOVE_UP:
3621 case REQ_MOVE_DOWN:
3622 case REQ_MOVE_PAGE_UP:
3623 case REQ_MOVE_PAGE_DOWN:
3624 case REQ_MOVE_FIRST_LINE:
3625 case REQ_MOVE_LAST_LINE:
3626 move_view(view, request);
3627 break;
3629 case REQ_SCROLL_LEFT:
3630 case REQ_SCROLL_RIGHT:
3631 case REQ_SCROLL_LINE_DOWN:
3632 case REQ_SCROLL_LINE_UP:
3633 case REQ_SCROLL_PAGE_DOWN:
3634 case REQ_SCROLL_PAGE_UP:
3635 scroll_view(view, request);
3636 break;
3638 case REQ_VIEW_BLAME:
3639 if (!opt_file[0]) {
3640 report("No file chosen, press %s to open tree view",
3641 get_key(view->keymap, REQ_VIEW_TREE));
3642 break;
3643 }
3644 open_view(view, request, OPEN_DEFAULT);
3645 break;
3647 case REQ_VIEW_BLOB:
3648 if (!ref_blob[0]) {
3649 report("No file chosen, press %s to open tree view",
3650 get_key(view->keymap, REQ_VIEW_TREE));
3651 break;
3652 }
3653 open_view(view, request, OPEN_DEFAULT);
3654 break;
3656 case REQ_VIEW_PAGER:
3657 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3658 report("No pager content, press %s to run command from prompt",
3659 get_key(view->keymap, REQ_PROMPT));
3660 break;
3661 }
3662 open_view(view, request, OPEN_DEFAULT);
3663 break;
3665 case REQ_VIEW_STAGE:
3666 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3667 report("No stage content, press %s to open the status view and choose file",
3668 get_key(view->keymap, REQ_VIEW_STATUS));
3669 break;
3670 }
3671 open_view(view, request, OPEN_DEFAULT);
3672 break;
3674 case REQ_VIEW_STATUS:
3675 if (opt_is_inside_work_tree == FALSE) {
3676 report("The status view requires a working tree");
3677 break;
3678 }
3679 open_view(view, request, OPEN_DEFAULT);
3680 break;
3682 case REQ_VIEW_MAIN:
3683 case REQ_VIEW_DIFF:
3684 case REQ_VIEW_LOG:
3685 case REQ_VIEW_TREE:
3686 case REQ_VIEW_HELP:
3687 case REQ_VIEW_BRANCH:
3688 open_view(view, request, OPEN_DEFAULT);
3689 break;
3691 case REQ_NEXT:
3692 case REQ_PREVIOUS:
3693 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3695 if (view->parent) {
3696 int line;
3698 view = view->parent;
3699 line = view->lineno;
3700 move_view(view, request);
3701 if (view_is_displayed(view))
3702 update_view_title(view);
3703 if (line != view->lineno)
3704 view_request(view, REQ_ENTER);
3705 } else {
3706 move_view(view, request);
3707 }
3708 break;
3710 case REQ_VIEW_NEXT:
3711 {
3712 int nviews = displayed_views();
3713 int next_view = (current_view + 1) % nviews;
3715 if (next_view == current_view) {
3716 report("Only one view is displayed");
3717 break;
3718 }
3720 current_view = next_view;
3721 /* Blur out the title of the previous view. */
3722 update_view_title(view);
3723 report("");
3724 break;
3725 }
3726 case REQ_REFRESH:
3727 report("Refreshing is not yet supported for the %s view", view->name);
3728 break;
3730 case REQ_MAXIMIZE:
3731 if (displayed_views() == 2)
3732 maximize_view(view);
3733 break;
3735 case REQ_OPTIONS:
3736 open_option_menu();
3737 break;
3739 case REQ_TOGGLE_LINENO:
3740 toggle_view_option(&opt_line_number, "line numbers");
3741 break;
3743 case REQ_TOGGLE_DATE:
3744 toggle_date();
3745 break;
3747 case REQ_TOGGLE_AUTHOR:
3748 toggle_author();
3749 break;
3751 case REQ_TOGGLE_REV_GRAPH:
3752 toggle_view_option(&opt_rev_graph, "revision graph display");
3753 break;
3755 case REQ_TOGGLE_REFS:
3756 toggle_view_option(&opt_show_refs, "reference display");
3757 break;
3759 case REQ_TOGGLE_SORT_FIELD:
3760 case REQ_TOGGLE_SORT_ORDER:
3761 report("Sorting is not yet supported for the %s view", view->name);
3762 break;
3764 case REQ_SEARCH:
3765 case REQ_SEARCH_BACK:
3766 search_view(view, request);
3767 break;
3769 case REQ_FIND_NEXT:
3770 case REQ_FIND_PREV:
3771 find_next(view, request);
3772 break;
3774 case REQ_STOP_LOADING:
3775 foreach_view(view, i) {
3776 if (view->pipe)
3777 report("Stopped loading the %s view", view->name),
3778 end_update(view, TRUE);
3779 }
3780 break;
3782 case REQ_SHOW_VERSION:
3783 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3784 return TRUE;
3786 case REQ_SCREEN_REDRAW:
3787 redraw_display(TRUE);
3788 break;
3790 case REQ_EDIT:
3791 report("Nothing to edit");
3792 break;
3794 case REQ_ENTER:
3795 report("Nothing to enter");
3796 break;
3798 case REQ_VIEW_CLOSE:
3799 /* XXX: Mark closed views by letting view->prev point to the
3800 * view itself. Parents to closed view should never be
3801 * followed. */
3802 if (view->prev && view->prev != view) {
3803 maximize_view(view->prev);
3804 view->prev = view;
3805 break;
3806 }
3807 /* Fall-through */
3808 case REQ_QUIT:
3809 return FALSE;
3811 default:
3812 report("Unknown key, press %s for help",
3813 get_key(view->keymap, REQ_VIEW_HELP));
3814 return TRUE;
3815 }
3817 return TRUE;
3818 }
3821 /*
3822 * View backend utilities
3823 */
3825 enum sort_field {
3826 ORDERBY_NAME,
3827 ORDERBY_DATE,
3828 ORDERBY_AUTHOR,
3829 };
3831 struct sort_state {
3832 const enum sort_field *fields;
3833 size_t size, current;
3834 bool reverse;
3835 };
3837 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3838 #define get_sort_field(state) ((state).fields[(state).current])
3839 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3841 static void
3842 sort_view(struct view *view, enum request request, struct sort_state *state,
3843 int (*compare)(const void *, const void *))
3844 {
3845 switch (request) {
3846 case REQ_TOGGLE_SORT_FIELD:
3847 state->current = (state->current + 1) % state->size;
3848 break;
3850 case REQ_TOGGLE_SORT_ORDER:
3851 state->reverse = !state->reverse;
3852 break;
3853 default:
3854 die("Not a sort request");
3855 }
3857 qsort(view->line, view->lines, sizeof(*view->line), compare);
3858 redraw_view(view);
3859 }
3861 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3863 /* Small author cache to reduce memory consumption. It uses binary
3864 * search to lookup or find place to position new entries. No entries
3865 * are ever freed. */
3866 static const char *
3867 get_author(const char *name)
3868 {
3869 static const char **authors;
3870 static size_t authors_size;
3871 int from = 0, to = authors_size - 1;
3873 while (from <= to) {
3874 size_t pos = (to + from) / 2;
3875 int cmp = strcmp(name, authors[pos]);
3877 if (!cmp)
3878 return authors[pos];
3880 if (cmp < 0)
3881 to = pos - 1;
3882 else
3883 from = pos + 1;
3884 }
3886 if (!realloc_authors(&authors, authors_size, 1))
3887 return NULL;
3888 name = strdup(name);
3889 if (!name)
3890 return NULL;
3892 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3893 authors[from] = name;
3894 authors_size++;
3896 return name;
3897 }
3899 static void
3900 parse_timesec(struct time *time, const char *sec)
3901 {
3902 time->sec = (time_t) atol(sec);
3903 }
3905 static void
3906 parse_timezone(struct time *time, const char *zone)
3907 {
3908 long tz;
3910 tz = ('0' - zone[1]) * 60 * 60 * 10;
3911 tz += ('0' - zone[2]) * 60 * 60;
3912 tz += ('0' - zone[3]) * 60 * 10;
3913 tz += ('0' - zone[4]) * 60;
3915 if (zone[0] == '-')
3916 tz = -tz;
3918 time->tz = tz;
3919 time->sec -= tz;
3920 }
3922 /* Parse author lines where the name may be empty:
3923 * author <email@address.tld> 1138474660 +0100
3924 */
3925 static void
3926 parse_author_line(char *ident, const char **author, struct time *time)
3927 {
3928 char *nameend = strchr(ident, '<');
3929 char *emailend = strchr(ident, '>');
3931 if (nameend && emailend)
3932 *nameend = *emailend = 0;
3933 ident = chomp_string(ident);
3934 if (!*ident) {
3935 if (nameend)
3936 ident = chomp_string(nameend + 1);
3937 if (!*ident)
3938 ident = "Unknown";
3939 }
3941 *author = get_author(ident);
3943 /* Parse epoch and timezone */
3944 if (emailend && emailend[1] == ' ') {
3945 char *secs = emailend + 2;
3946 char *zone = strchr(secs, ' ');
3948 parse_timesec(time, secs);
3950 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3951 parse_timezone(time, zone + 1);
3952 }
3953 }
3955 static bool
3956 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3957 {
3958 char rev[SIZEOF_REV];
3959 const char *revlist_argv[] = {
3960 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3961 };
3962 struct menu_item *items;
3963 char text[SIZEOF_STR];
3964 bool ok = TRUE;
3965 int i;
3967 items = calloc(*parents + 1, sizeof(*items));
3968 if (!items)
3969 return FALSE;
3971 for (i = 0; i < *parents; i++) {
3972 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3973 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3974 !(items[i].text = strdup(text))) {
3975 ok = FALSE;
3976 break;
3977 }
3978 }
3980 if (ok) {
3981 *parents = 0;
3982 ok = prompt_menu("Select parent", items, parents);
3983 }
3984 for (i = 0; items[i].text; i++)
3985 free((char *) items[i].text);
3986 free(items);
3987 return ok;
3988 }
3990 static bool
3991 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3992 {
3993 char buf[SIZEOF_STR * 4];
3994 const char *revlist_argv[] = {
3995 "git", "log", "--no-color", "-1",
3996 "--pretty=format:%P", id, "--", path, NULL
3997 };
3998 int parents;
4000 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4001 (parents = strlen(buf) / 40) < 0) {
4002 report("Failed to get parent information");
4003 return FALSE;
4005 } else if (parents == 0) {
4006 if (path)
4007 report("Path '%s' does not exist in the parent", path);
4008 else
4009 report("The selected commit has no parents");
4010 return FALSE;
4011 }
4013 if (parents == 1)
4014 parents = 0;
4015 else if (!open_commit_parent_menu(buf, &parents))
4016 return FALSE;
4018 string_copy_rev(rev, &buf[41 * parents]);
4019 return TRUE;
4020 }
4022 /*
4023 * Pager backend
4024 */
4026 static bool
4027 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4028 {
4029 char text[SIZEOF_STR];
4031 if (opt_line_number && draw_lineno(view, lineno))
4032 return TRUE;
4034 string_expand(text, sizeof(text), line->data, opt_tab_size);
4035 draw_text(view, line->type, text, TRUE);
4036 return TRUE;
4037 }
4039 static bool
4040 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4041 {
4042 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4043 char ref[SIZEOF_STR];
4045 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4046 return TRUE;
4048 /* This is the only fatal call, since it can "corrupt" the buffer. */
4049 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4050 return FALSE;
4052 return TRUE;
4053 }
4055 static void
4056 add_pager_refs(struct view *view, struct line *line)
4057 {
4058 char buf[SIZEOF_STR];
4059 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4060 struct ref_list *list;
4061 size_t bufpos = 0, i;
4062 const char *sep = "Refs: ";
4063 bool is_tag = FALSE;
4065 assert(line->type == LINE_COMMIT);
4067 list = get_ref_list(commit_id);
4068 if (!list) {
4069 if (view->type == VIEW_DIFF)
4070 goto try_add_describe_ref;
4071 return;
4072 }
4074 for (i = 0; i < list->size; i++) {
4075 struct ref *ref = list->refs[i];
4076 const char *fmt = ref->tag ? "%s[%s]" :
4077 ref->remote ? "%s<%s>" : "%s%s";
4079 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4080 return;
4081 sep = ", ";
4082 if (ref->tag)
4083 is_tag = TRUE;
4084 }
4086 if (!is_tag && view->type == VIEW_DIFF) {
4087 try_add_describe_ref:
4088 /* Add <tag>-g<commit_id> "fake" reference. */
4089 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4090 return;
4091 }
4093 if (bufpos == 0)
4094 return;
4096 add_line_text(view, buf, LINE_PP_REFS);
4097 }
4099 static bool
4100 pager_read(struct view *view, char *data)
4101 {
4102 struct line *line;
4104 if (!data)
4105 return TRUE;
4107 line = add_line_text(view, data, get_line_type(data));
4108 if (!line)
4109 return FALSE;
4111 if (line->type == LINE_COMMIT &&
4112 (view->type == VIEW_DIFF ||
4113 view->type == VIEW_LOG))
4114 add_pager_refs(view, line);
4116 return TRUE;
4117 }
4119 static enum request
4120 pager_request(struct view *view, enum request request, struct line *line)
4121 {
4122 int split = 0;
4124 if (request != REQ_ENTER)
4125 return request;
4127 if (line->type == LINE_COMMIT &&
4128 (view->type == VIEW_LOG ||
4129 view->type == VIEW_PAGER)) {
4130 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4131 split = 1;
4132 }
4134 /* Always scroll the view even if it was split. That way
4135 * you can use Enter to scroll through the log view and
4136 * split open each commit diff. */
4137 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4139 /* FIXME: A minor workaround. Scrolling the view will call report("")
4140 * but if we are scrolling a non-current view this won't properly
4141 * update the view title. */
4142 if (split)
4143 update_view_title(view);
4145 return REQ_NONE;
4146 }
4148 static bool
4149 pager_grep(struct view *view, struct line *line)
4150 {
4151 const char *text[] = { line->data, NULL };
4153 return grep_text(view, text);
4154 }
4156 static void
4157 pager_select(struct view *view, struct line *line)
4158 {
4159 if (line->type == LINE_COMMIT) {
4160 char *text = (char *)line->data + STRING_SIZE("commit ");
4162 if (view->type != VIEW_PAGER)
4163 string_copy_rev(view->ref, text);
4164 string_copy_rev(ref_commit, text);
4165 }
4166 }
4168 static struct view_ops pager_ops = {
4169 "line",
4170 NULL,
4171 NULL,
4172 pager_read,
4173 pager_draw,
4174 pager_request,
4175 pager_grep,
4176 pager_select,
4177 };
4179 static const char *log_argv[SIZEOF_ARG] = {
4180 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4181 };
4183 static enum request
4184 log_request(struct view *view, enum request request, struct line *line)
4185 {
4186 switch (request) {
4187 case REQ_REFRESH:
4188 load_refs();
4189 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4190 return REQ_NONE;
4191 default:
4192 return pager_request(view, request, line);
4193 }
4194 }
4196 static struct view_ops log_ops = {
4197 "line",
4198 log_argv,
4199 NULL,
4200 pager_read,
4201 pager_draw,
4202 log_request,
4203 pager_grep,
4204 pager_select,
4205 };
4207 static const char *diff_argv[SIZEOF_ARG] = {
4208 "git", "show", "--pretty=fuller", "--no-color", "--root",
4209 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4210 };
4212 static struct view_ops diff_ops = {
4213 "line",
4214 diff_argv,
4215 NULL,
4216 pager_read,
4217 pager_draw,
4218 pager_request,
4219 pager_grep,
4220 pager_select,
4221 };
4223 /*
4224 * Help backend
4225 */
4227 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4229 static bool
4230 help_open_keymap_title(struct view *view, enum keymap keymap)
4231 {
4232 struct line *line;
4234 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4235 help_keymap_hidden[keymap] ? '+' : '-',
4236 enum_name(keymap_table[keymap]));
4237 if (line)
4238 line->other = keymap;
4240 return help_keymap_hidden[keymap];
4241 }
4243 static void
4244 help_open_keymap(struct view *view, enum keymap keymap)
4245 {
4246 const char *group = NULL;
4247 char buf[SIZEOF_STR];
4248 size_t bufpos;
4249 bool add_title = TRUE;
4250 int i;
4252 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4253 const char *key = NULL;
4255 if (req_info[i].request == REQ_NONE)
4256 continue;
4258 if (!req_info[i].request) {
4259 group = req_info[i].help;
4260 continue;
4261 }
4263 key = get_keys(keymap, req_info[i].request, TRUE);
4264 if (!key || !*key)
4265 continue;
4267 if (add_title && help_open_keymap_title(view, keymap))
4268 return;
4269 add_title = FALSE;
4271 if (group) {
4272 add_line_text(view, group, LINE_HELP_GROUP);
4273 group = NULL;
4274 }
4276 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4277 enum_name(req_info[i]), req_info[i].help);
4278 }
4280 group = "External commands:";
4282 for (i = 0; i < run_requests; i++) {
4283 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4284 const char *key;
4285 int argc;
4287 if (!req || req->keymap != keymap)
4288 continue;
4290 key = get_key_name(req->key);
4291 if (!*key)
4292 key = "(no key defined)";
4294 if (add_title && help_open_keymap_title(view, keymap))
4295 return;
4296 if (group) {
4297 add_line_text(view, group, LINE_HELP_GROUP);
4298 group = NULL;
4299 }
4301 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4302 if (!string_format_from(buf, &bufpos, "%s%s",
4303 argc ? " " : "", req->argv[argc]))
4304 return;
4306 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4307 }
4308 }
4310 static bool
4311 help_open(struct view *view)
4312 {
4313 enum keymap keymap;
4315 reset_view(view);
4316 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4317 add_line_text(view, "", LINE_DEFAULT);
4319 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4320 help_open_keymap(view, keymap);
4322 return TRUE;
4323 }
4325 static enum request
4326 help_request(struct view *view, enum request request, struct line *line)
4327 {
4328 switch (request) {
4329 case REQ_ENTER:
4330 if (line->type == LINE_HELP_KEYMAP) {
4331 help_keymap_hidden[line->other] =
4332 !help_keymap_hidden[line->other];
4333 view->p_restore = TRUE;
4334 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4335 }
4337 return REQ_NONE;
4338 default:
4339 return pager_request(view, request, line);
4340 }
4341 }
4343 static struct view_ops help_ops = {
4344 "line",
4345 NULL,
4346 help_open,
4347 NULL,
4348 pager_draw,
4349 help_request,
4350 pager_grep,
4351 pager_select,
4352 };
4355 /*
4356 * Tree backend
4357 */
4359 struct tree_stack_entry {
4360 struct tree_stack_entry *prev; /* Entry below this in the stack */
4361 unsigned long lineno; /* Line number to restore */
4362 char *name; /* Position of name in opt_path */
4363 };
4365 /* The top of the path stack. */
4366 static struct tree_stack_entry *tree_stack = NULL;
4367 unsigned long tree_lineno = 0;
4369 static void
4370 pop_tree_stack_entry(void)
4371 {
4372 struct tree_stack_entry *entry = tree_stack;
4374 tree_lineno = entry->lineno;
4375 entry->name[0] = 0;
4376 tree_stack = entry->prev;
4377 free(entry);
4378 }
4380 static void
4381 push_tree_stack_entry(const char *name, unsigned long lineno)
4382 {
4383 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4384 size_t pathlen = strlen(opt_path);
4386 if (!entry)
4387 return;
4389 entry->prev = tree_stack;
4390 entry->name = opt_path + pathlen;
4391 tree_stack = entry;
4393 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4394 pop_tree_stack_entry();
4395 return;
4396 }
4398 /* Move the current line to the first tree entry. */
4399 tree_lineno = 1;
4400 entry->lineno = lineno;
4401 }
4403 /* Parse output from git-ls-tree(1):
4404 *
4405 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4406 */
4408 #define SIZEOF_TREE_ATTR \
4409 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4411 #define SIZEOF_TREE_MODE \
4412 STRING_SIZE("100644 ")
4414 #define TREE_ID_OFFSET \
4415 STRING_SIZE("100644 blob ")
4417 struct tree_entry {
4418 char id[SIZEOF_REV];
4419 mode_t mode;
4420 struct time time; /* Date from the author ident. */
4421 const char *author; /* Author of the commit. */
4422 char name[1];
4423 };
4425 static const char *
4426 tree_path(const struct line *line)
4427 {
4428 return ((struct tree_entry *) line->data)->name;
4429 }
4431 static int
4432 tree_compare_entry(const struct line *line1, const struct line *line2)
4433 {
4434 if (line1->type != line2->type)
4435 return line1->type == LINE_TREE_DIR ? -1 : 1;
4436 return strcmp(tree_path(line1), tree_path(line2));
4437 }
4439 static const enum sort_field tree_sort_fields[] = {
4440 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4441 };
4442 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4444 static int
4445 tree_compare(const void *l1, const void *l2)
4446 {
4447 const struct line *line1 = (const struct line *) l1;
4448 const struct line *line2 = (const struct line *) l2;
4449 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4450 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4452 if (line1->type == LINE_TREE_HEAD)
4453 return -1;
4454 if (line2->type == LINE_TREE_HEAD)
4455 return 1;
4457 switch (get_sort_field(tree_sort_state)) {
4458 case ORDERBY_DATE:
4459 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4461 case ORDERBY_AUTHOR:
4462 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4464 case ORDERBY_NAME:
4465 default:
4466 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4467 }
4468 }
4471 static struct line *
4472 tree_entry(struct view *view, enum line_type type, const char *path,
4473 const char *mode, const char *id)
4474 {
4475 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4476 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4478 if (!entry || !line) {
4479 free(entry);
4480 return NULL;
4481 }
4483 strncpy(entry->name, path, strlen(path));
4484 if (mode)
4485 entry->mode = strtoul(mode, NULL, 8);
4486 if (id)
4487 string_copy_rev(entry->id, id);
4489 return line;
4490 }
4492 static bool
4493 tree_read_date(struct view *view, char *text, bool *read_date)
4494 {
4495 static const char *author_name;
4496 static struct time author_time;
4498 if (!text && *read_date) {
4499 *read_date = FALSE;
4500 return TRUE;
4502 } else if (!text) {
4503 char *path = *opt_path ? opt_path : ".";
4504 /* Find next entry to process */
4505 const char *log_file[] = {
4506 "git", "log", "--no-color", "--pretty=raw",
4507 "--cc", "--raw", view->id, "--", path, NULL
4508 };
4510 if (!view->lines) {
4511 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4512 report("Tree is empty");
4513 return TRUE;
4514 }
4516 if (!start_update(view, log_file, opt_cdup)) {
4517 report("Failed to load tree data");
4518 return TRUE;
4519 }
4521 *read_date = TRUE;
4522 return FALSE;
4524 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4525 parse_author_line(text + STRING_SIZE("author "),
4526 &author_name, &author_time);
4528 } else if (*text == ':') {
4529 char *pos;
4530 size_t annotated = 1;
4531 size_t i;
4533 pos = strchr(text, '\t');
4534 if (!pos)
4535 return TRUE;
4536 text = pos + 1;
4537 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4538 text += strlen(opt_path);
4539 pos = strchr(text, '/');
4540 if (pos)
4541 *pos = 0;
4543 for (i = 1; i < view->lines; i++) {
4544 struct line *line = &view->line[i];
4545 struct tree_entry *entry = line->data;
4547 annotated += !!entry->author;
4548 if (entry->author || strcmp(entry->name, text))
4549 continue;
4551 entry->author = author_name;
4552 entry->time = author_time;
4553 line->dirty = 1;
4554 break;
4555 }
4557 if (annotated == view->lines)
4558 io_kill(view->pipe);
4559 }
4560 return TRUE;
4561 }
4563 static bool
4564 tree_read(struct view *view, char *text)
4565 {
4566 static bool read_date = FALSE;
4567 struct tree_entry *data;
4568 struct line *entry, *line;
4569 enum line_type type;
4570 size_t textlen = text ? strlen(text) : 0;
4571 char *path = text + SIZEOF_TREE_ATTR;
4573 if (read_date || !text)
4574 return tree_read_date(view, text, &read_date);
4576 if (textlen <= SIZEOF_TREE_ATTR)
4577 return FALSE;
4578 if (view->lines == 0 &&
4579 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4580 return FALSE;
4582 /* Strip the path part ... */
4583 if (*opt_path) {
4584 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4585 size_t striplen = strlen(opt_path);
4587 if (pathlen > striplen)
4588 memmove(path, path + striplen,
4589 pathlen - striplen + 1);
4591 /* Insert "link" to parent directory. */
4592 if (view->lines == 1 &&
4593 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4594 return FALSE;
4595 }
4597 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4598 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4599 if (!entry)
4600 return FALSE;
4601 data = entry->data;
4603 /* Skip "Directory ..." and ".." line. */
4604 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4605 if (tree_compare_entry(line, entry) <= 0)
4606 continue;
4608 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4610 line->data = data;
4611 line->type = type;
4612 for (; line <= entry; line++)
4613 line->dirty = line->cleareol = 1;
4614 return TRUE;
4615 }
4617 if (tree_lineno > view->lineno) {
4618 view->lineno = tree_lineno;
4619 tree_lineno = 0;
4620 }
4622 return TRUE;
4623 }
4625 static bool
4626 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4627 {
4628 struct tree_entry *entry = line->data;
4630 if (line->type == LINE_TREE_HEAD) {
4631 if (draw_text(view, line->type, "Directory path /", TRUE))
4632 return TRUE;
4633 } else {
4634 if (draw_mode(view, entry->mode))
4635 return TRUE;
4637 if (opt_author && draw_author(view, entry->author))
4638 return TRUE;
4640 if (opt_date && draw_date(view, &entry->time))
4641 return TRUE;
4642 }
4643 if (draw_text(view, line->type, entry->name, TRUE))
4644 return TRUE;
4645 return TRUE;
4646 }
4648 static void
4649 open_blob_editor(const char *id)
4650 {
4651 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4652 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4653 int fd = mkstemp(file);
4655 if (fd == -1)
4656 report("Failed to create temporary file");
4657 else if (!io_run_append(blob_argv, fd))
4658 report("Failed to save blob data to file");
4659 else
4660 open_editor(file);
4661 if (fd != -1)
4662 unlink(file);
4663 }
4665 static enum request
4666 tree_request(struct view *view, enum request request, struct line *line)
4667 {
4668 enum open_flags flags;
4669 struct tree_entry *entry = line->data;
4671 switch (request) {
4672 case REQ_VIEW_BLAME:
4673 if (line->type != LINE_TREE_FILE) {
4674 report("Blame only supported for files");
4675 return REQ_NONE;
4676 }
4678 string_copy(opt_ref, view->vid);
4679 return request;
4681 case REQ_EDIT:
4682 if (line->type != LINE_TREE_FILE) {
4683 report("Edit only supported for files");
4684 } else if (!is_head_commit(view->vid)) {
4685 open_blob_editor(entry->id);
4686 } else {
4687 open_editor(opt_file);
4688 }
4689 return REQ_NONE;
4691 case REQ_TOGGLE_SORT_FIELD:
4692 case REQ_TOGGLE_SORT_ORDER:
4693 sort_view(view, request, &tree_sort_state, tree_compare);
4694 return REQ_NONE;
4696 case REQ_PARENT:
4697 if (!*opt_path) {
4698 /* quit view if at top of tree */
4699 return REQ_VIEW_CLOSE;
4700 }
4701 /* fake 'cd ..' */
4702 line = &view->line[1];
4703 break;
4705 case REQ_ENTER:
4706 break;
4708 default:
4709 return request;
4710 }
4712 /* Cleanup the stack if the tree view is at a different tree. */
4713 while (!*opt_path && tree_stack)
4714 pop_tree_stack_entry();
4716 switch (line->type) {
4717 case LINE_TREE_DIR:
4718 /* Depending on whether it is a subdirectory or parent link
4719 * mangle the path buffer. */
4720 if (line == &view->line[1] && *opt_path) {
4721 pop_tree_stack_entry();
4723 } else {
4724 const char *basename = tree_path(line);
4726 push_tree_stack_entry(basename, view->lineno);
4727 }
4729 /* Trees and subtrees share the same ID, so they are not not
4730 * unique like blobs. */
4731 flags = OPEN_RELOAD;
4732 request = REQ_VIEW_TREE;
4733 break;
4735 case LINE_TREE_FILE:
4736 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4737 request = REQ_VIEW_BLOB;
4738 break;
4740 default:
4741 return REQ_NONE;
4742 }
4744 open_view(view, request, flags);
4745 if (request == REQ_VIEW_TREE)
4746 view->lineno = tree_lineno;
4748 return REQ_NONE;
4749 }
4751 static bool
4752 tree_grep(struct view *view, struct line *line)
4753 {
4754 struct tree_entry *entry = line->data;
4755 const char *text[] = {
4756 entry->name,
4757 opt_author ? entry->author : "",
4758 mkdate(&entry->time, opt_date),
4759 NULL
4760 };
4762 return grep_text(view, text);
4763 }
4765 static void
4766 tree_select(struct view *view, struct line *line)
4767 {
4768 struct tree_entry *entry = line->data;
4770 if (line->type == LINE_TREE_FILE) {
4771 string_copy_rev(ref_blob, entry->id);
4772 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4774 } else if (line->type != LINE_TREE_DIR) {
4775 return;
4776 }
4778 string_copy_rev(view->ref, entry->id);
4779 }
4781 static bool
4782 tree_prepare(struct view *view)
4783 {
4784 if (view->lines == 0 && opt_prefix[0]) {
4785 char *pos = opt_prefix;
4787 while (pos && *pos) {
4788 char *end = strchr(pos, '/');
4790 if (end)
4791 *end = 0;
4792 push_tree_stack_entry(pos, 0);
4793 pos = end;
4794 if (end) {
4795 *end = '/';
4796 pos++;
4797 }
4798 }
4800 } else if (strcmp(view->vid, view->id)) {
4801 opt_path[0] = 0;
4802 }
4804 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4805 }
4807 static const char *tree_argv[SIZEOF_ARG] = {
4808 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4809 };
4811 static struct view_ops tree_ops = {
4812 "file",
4813 tree_argv,
4814 NULL,
4815 tree_read,
4816 tree_draw,
4817 tree_request,
4818 tree_grep,
4819 tree_select,
4820 tree_prepare,
4821 };
4823 static bool
4824 blob_read(struct view *view, char *line)
4825 {
4826 if (!line)
4827 return TRUE;
4828 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4829 }
4831 static enum request
4832 blob_request(struct view *view, enum request request, struct line *line)
4833 {
4834 switch (request) {
4835 case REQ_EDIT:
4836 open_blob_editor(view->vid);
4837 return REQ_NONE;
4838 default:
4839 return pager_request(view, request, line);
4840 }
4841 }
4843 static const char *blob_argv[SIZEOF_ARG] = {
4844 "git", "cat-file", "blob", "%(blob)", NULL
4845 };
4847 static struct view_ops blob_ops = {
4848 "line",
4849 blob_argv,
4850 NULL,
4851 blob_read,
4852 pager_draw,
4853 blob_request,
4854 pager_grep,
4855 pager_select,
4856 };
4858 /*
4859 * Blame backend
4860 *
4861 * Loading the blame view is a two phase job:
4862 *
4863 * 1. File content is read either using opt_file from the
4864 * filesystem or using git-cat-file.
4865 * 2. Then blame information is incrementally added by
4866 * reading output from git-blame.
4867 */
4869 struct blame_commit {
4870 char id[SIZEOF_REV]; /* SHA1 ID. */
4871 char title[128]; /* First line of the commit message. */
4872 const char *author; /* Author of the commit. */
4873 struct time time; /* Date from the author ident. */
4874 char filename[128]; /* Name of file. */
4875 bool has_previous; /* Was a "previous" line detected. */
4876 };
4878 struct blame {
4879 struct blame_commit *commit;
4880 unsigned long lineno;
4881 char text[1];
4882 };
4884 static bool
4885 blame_open(struct view *view)
4886 {
4887 char path[SIZEOF_STR];
4889 if (!view->prev && *opt_prefix) {
4890 string_copy(path, opt_file);
4891 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4892 return FALSE;
4893 }
4895 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4896 const char *blame_cat_file_argv[] = {
4897 "git", "cat-file", "blob", path, NULL
4898 };
4900 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4901 !start_update(view, blame_cat_file_argv, opt_cdup))
4902 return FALSE;
4903 }
4905 setup_update(view, opt_file);
4906 string_format(view->ref, "%s ...", opt_file);
4908 return TRUE;
4909 }
4911 static struct blame_commit *
4912 get_blame_commit(struct view *view, const char *id)
4913 {
4914 size_t i;
4916 for (i = 0; i < view->lines; i++) {
4917 struct blame *blame = view->line[i].data;
4919 if (!blame->commit)
4920 continue;
4922 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4923 return blame->commit;
4924 }
4926 {
4927 struct blame_commit *commit = calloc(1, sizeof(*commit));
4929 if (commit)
4930 string_ncopy(commit->id, id, SIZEOF_REV);
4931 return commit;
4932 }
4933 }
4935 static bool
4936 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4937 {
4938 const char *pos = *posref;
4940 *posref = NULL;
4941 pos = strchr(pos + 1, ' ');
4942 if (!pos || !isdigit(pos[1]))
4943 return FALSE;
4944 *number = atoi(pos + 1);
4945 if (*number < min || *number > max)
4946 return FALSE;
4948 *posref = pos;
4949 return TRUE;
4950 }
4952 static struct blame_commit *
4953 parse_blame_commit(struct view *view, const char *text, int *blamed)
4954 {
4955 struct blame_commit *commit;
4956 struct blame *blame;
4957 const char *pos = text + SIZEOF_REV - 2;
4958 size_t orig_lineno = 0;
4959 size_t lineno;
4960 size_t group;
4962 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4963 return NULL;
4965 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4966 !parse_number(&pos, &lineno, 1, view->lines) ||
4967 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4968 return NULL;
4970 commit = get_blame_commit(view, text);
4971 if (!commit)
4972 return NULL;
4974 *blamed += group;
4975 while (group--) {
4976 struct line *line = &view->line[lineno + group - 1];
4978 blame = line->data;
4979 blame->commit = commit;
4980 blame->lineno = orig_lineno + group - 1;
4981 line->dirty = 1;
4982 }
4984 return commit;
4985 }
4987 static bool
4988 blame_read_file(struct view *view, const char *line, bool *read_file)
4989 {
4990 if (!line) {
4991 const char *blame_argv[] = {
4992 "git", "blame", "--incremental",
4993 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4994 };
4996 if (view->lines == 0 && !view->prev)
4997 die("No blame exist for %s", view->vid);
4999 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5000 report("Failed to load blame data");
5001 return TRUE;
5002 }
5004 *read_file = FALSE;
5005 return FALSE;
5007 } else {
5008 size_t linelen = strlen(line);
5009 struct blame *blame = malloc(sizeof(*blame) + linelen);
5011 if (!blame)
5012 return FALSE;
5014 blame->commit = NULL;
5015 strncpy(blame->text, line, linelen);
5016 blame->text[linelen] = 0;
5017 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5018 }
5019 }
5021 static bool
5022 match_blame_header(const char *name, char **line)
5023 {
5024 size_t namelen = strlen(name);
5025 bool matched = !strncmp(name, *line, namelen);
5027 if (matched)
5028 *line += namelen;
5030 return matched;
5031 }
5033 static bool
5034 blame_read(struct view *view, char *line)
5035 {
5036 static struct blame_commit *commit = NULL;
5037 static int blamed = 0;
5038 static bool read_file = TRUE;
5040 if (read_file)
5041 return blame_read_file(view, line, &read_file);
5043 if (!line) {
5044 /* Reset all! */
5045 commit = NULL;
5046 blamed = 0;
5047 read_file = TRUE;
5048 string_format(view->ref, "%s", view->vid);
5049 if (view_is_displayed(view)) {
5050 update_view_title(view);
5051 redraw_view_from(view, 0);
5052 }
5053 return TRUE;
5054 }
5056 if (!commit) {
5057 commit = parse_blame_commit(view, line, &blamed);
5058 string_format(view->ref, "%s %2d%%", view->vid,
5059 view->lines ? blamed * 100 / view->lines : 0);
5061 } else if (match_blame_header("author ", &line)) {
5062 commit->author = get_author(line);
5064 } else if (match_blame_header("author-time ", &line)) {
5065 parse_timesec(&commit->time, line);
5067 } else if (match_blame_header("author-tz ", &line)) {
5068 parse_timezone(&commit->time, line);
5070 } else if (match_blame_header("summary ", &line)) {
5071 string_ncopy(commit->title, line, strlen(line));
5073 } else if (match_blame_header("previous ", &line)) {
5074 commit->has_previous = TRUE;
5076 } else if (match_blame_header("filename ", &line)) {
5077 string_ncopy(commit->filename, line, strlen(line));
5078 commit = NULL;
5079 }
5081 return TRUE;
5082 }
5084 static bool
5085 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5086 {
5087 struct blame *blame = line->data;
5088 struct time *time = NULL;
5089 const char *id = NULL, *author = NULL;
5090 char text[SIZEOF_STR];
5092 if (blame->commit && *blame->commit->filename) {
5093 id = blame->commit->id;
5094 author = blame->commit->author;
5095 time = &blame->commit->time;
5096 }
5098 if (opt_date && draw_date(view, time))
5099 return TRUE;
5101 if (opt_author && draw_author(view, author))
5102 return TRUE;
5104 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5105 return TRUE;
5107 if (draw_lineno(view, lineno))
5108 return TRUE;
5110 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5111 draw_text(view, LINE_DEFAULT, text, TRUE);
5112 return TRUE;
5113 }
5115 static bool
5116 check_blame_commit(struct blame *blame, bool check_null_id)
5117 {
5118 if (!blame->commit)
5119 report("Commit data not loaded yet");
5120 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5121 report("No commit exist for the selected line");
5122 else
5123 return TRUE;
5124 return FALSE;
5125 }
5127 static void
5128 setup_blame_parent_line(struct view *view, struct blame *blame)
5129 {
5130 const char *diff_tree_argv[] = {
5131 "git", "diff-tree", "-U0", blame->commit->id,
5132 "--", blame->commit->filename, NULL
5133 };
5134 struct io io = {};
5135 int parent_lineno = -1;
5136 int blamed_lineno = -1;
5137 char *line;
5139 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5140 return;
5142 while ((line = io_get(&io, '\n', TRUE))) {
5143 if (*line == '@') {
5144 char *pos = strchr(line, '+');
5146 parent_lineno = atoi(line + 4);
5147 if (pos)
5148 blamed_lineno = atoi(pos + 1);
5150 } else if (*line == '+' && parent_lineno != -1) {
5151 if (blame->lineno == blamed_lineno - 1 &&
5152 !strcmp(blame->text, line + 1)) {
5153 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5154 break;
5155 }
5156 blamed_lineno++;
5157 }
5158 }
5160 io_done(&io);
5161 }
5163 static enum request
5164 blame_request(struct view *view, enum request request, struct line *line)
5165 {
5166 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5167 struct blame *blame = line->data;
5169 switch (request) {
5170 case REQ_VIEW_BLAME:
5171 if (check_blame_commit(blame, TRUE)) {
5172 string_copy(opt_ref, blame->commit->id);
5173 string_copy(opt_file, blame->commit->filename);
5174 if (blame->lineno)
5175 view->lineno = blame->lineno;
5176 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5177 }
5178 break;
5180 case REQ_PARENT:
5181 if (check_blame_commit(blame, TRUE) &&
5182 select_commit_parent(blame->commit->id, opt_ref,
5183 blame->commit->filename)) {
5184 string_copy(opt_file, blame->commit->filename);
5185 setup_blame_parent_line(view, blame);
5186 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5187 }
5188 break;
5190 case REQ_ENTER:
5191 if (!check_blame_commit(blame, FALSE))
5192 break;
5194 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5195 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5196 break;
5198 if (!strcmp(blame->commit->id, NULL_ID)) {
5199 struct view *diff = VIEW(REQ_VIEW_DIFF);
5200 const char *diff_index_argv[] = {
5201 "git", "diff-index", "--root", "--patch-with-stat",
5202 "-C", "-M", "HEAD", "--", view->vid, NULL
5203 };
5205 if (!blame->commit->has_previous) {
5206 diff_index_argv[1] = "diff";
5207 diff_index_argv[2] = "--no-color";
5208 diff_index_argv[6] = "--";
5209 diff_index_argv[7] = "/dev/null";
5210 }
5212 if (!prepare_update(diff, diff_index_argv, NULL)) {
5213 report("Failed to allocate diff command");
5214 break;
5215 }
5216 flags |= OPEN_PREPARED;
5217 }
5219 open_view(view, REQ_VIEW_DIFF, flags);
5220 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5221 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5222 break;
5224 default:
5225 return request;
5226 }
5228 return REQ_NONE;
5229 }
5231 static bool
5232 blame_grep(struct view *view, struct line *line)
5233 {
5234 struct blame *blame = line->data;
5235 struct blame_commit *commit = blame->commit;
5236 const char *text[] = {
5237 blame->text,
5238 commit ? commit->title : "",
5239 commit ? commit->id : "",
5240 commit && opt_author ? commit->author : "",
5241 commit ? mkdate(&commit->time, opt_date) : "",
5242 NULL
5243 };
5245 return grep_text(view, text);
5246 }
5248 static void
5249 blame_select(struct view *view, struct line *line)
5250 {
5251 struct blame *blame = line->data;
5252 struct blame_commit *commit = blame->commit;
5254 if (!commit)
5255 return;
5257 if (!strcmp(commit->id, NULL_ID))
5258 string_ncopy(ref_commit, "HEAD", 4);
5259 else
5260 string_copy_rev(ref_commit, commit->id);
5261 }
5263 static struct view_ops blame_ops = {
5264 "line",
5265 NULL,
5266 blame_open,
5267 blame_read,
5268 blame_draw,
5269 blame_request,
5270 blame_grep,
5271 blame_select,
5272 };
5274 /*
5275 * Branch backend
5276 */
5278 struct branch {
5279 const char *author; /* Author of the last commit. */
5280 struct time time; /* Date of the last activity. */
5281 const struct ref *ref; /* Name and commit ID information. */
5282 };
5284 static const struct ref branch_all;
5286 static const enum sort_field branch_sort_fields[] = {
5287 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5288 };
5289 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5291 static int
5292 branch_compare(const void *l1, const void *l2)
5293 {
5294 const struct branch *branch1 = ((const struct line *) l1)->data;
5295 const struct branch *branch2 = ((const struct line *) l2)->data;
5297 switch (get_sort_field(branch_sort_state)) {
5298 case ORDERBY_DATE:
5299 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5301 case ORDERBY_AUTHOR:
5302 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5304 case ORDERBY_NAME:
5305 default:
5306 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5307 }
5308 }
5310 static bool
5311 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5312 {
5313 struct branch *branch = line->data;
5314 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5316 if (opt_date && draw_date(view, &branch->time))
5317 return TRUE;
5319 if (opt_author && draw_author(view, branch->author))
5320 return TRUE;
5322 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5323 return TRUE;
5324 }
5326 static enum request
5327 branch_request(struct view *view, enum request request, struct line *line)
5328 {
5329 struct branch *branch = line->data;
5331 switch (request) {
5332 case REQ_REFRESH:
5333 load_refs();
5334 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5335 return REQ_NONE;
5337 case REQ_TOGGLE_SORT_FIELD:
5338 case REQ_TOGGLE_SORT_ORDER:
5339 sort_view(view, request, &branch_sort_state, branch_compare);
5340 return REQ_NONE;
5342 case REQ_ENTER:
5343 if (branch->ref == &branch_all) {
5344 const char *all_branches_argv[] = {
5345 "git", "log", "--no-color", "--pretty=raw", "--parents",
5346 "--topo-order", "--all", NULL
5347 };
5348 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5350 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5351 report("Failed to load view of all branches");
5352 return REQ_NONE;
5353 }
5354 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5355 } else {
5356 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5357 }
5358 return REQ_NONE;
5360 default:
5361 return request;
5362 }
5363 }
5365 static bool
5366 branch_read(struct view *view, char *line)
5367 {
5368 static char id[SIZEOF_REV];
5369 struct branch *reference;
5370 size_t i;
5372 if (!line)
5373 return TRUE;
5375 switch (get_line_type(line)) {
5376 case LINE_COMMIT:
5377 string_copy_rev(id, line + STRING_SIZE("commit "));
5378 return TRUE;
5380 case LINE_AUTHOR:
5381 for (i = 0, reference = NULL; i < view->lines; i++) {
5382 struct branch *branch = view->line[i].data;
5384 if (strcmp(branch->ref->id, id))
5385 continue;
5387 view->line[i].dirty = TRUE;
5388 if (reference) {
5389 branch->author = reference->author;
5390 branch->time = reference->time;
5391 continue;
5392 }
5394 parse_author_line(line + STRING_SIZE("author "),
5395 &branch->author, &branch->time);
5396 reference = branch;
5397 }
5398 return TRUE;
5400 default:
5401 return TRUE;
5402 }
5404 }
5406 static bool
5407 branch_open_visitor(void *data, const struct ref *ref)
5408 {
5409 struct view *view = data;
5410 struct branch *branch;
5412 if (ref->tag || ref->ltag || ref->remote)
5413 return TRUE;
5415 branch = calloc(1, sizeof(*branch));
5416 if (!branch)
5417 return FALSE;
5419 branch->ref = ref;
5420 return !!add_line_data(view, branch, LINE_DEFAULT);
5421 }
5423 static bool
5424 branch_open(struct view *view)
5425 {
5426 const char *branch_log[] = {
5427 "git", "log", "--no-color", "--pretty=raw",
5428 "--simplify-by-decoration", "--all", NULL
5429 };
5431 if (!start_update(view, branch_log, NULL)) {
5432 report("Failed to load branch data");
5433 return TRUE;
5434 }
5436 setup_update(view, view->id);
5437 branch_open_visitor(view, &branch_all);
5438 foreach_ref(branch_open_visitor, view);
5439 view->p_restore = TRUE;
5441 return TRUE;
5442 }
5444 static bool
5445 branch_grep(struct view *view, struct line *line)
5446 {
5447 struct branch *branch = line->data;
5448 const char *text[] = {
5449 branch->ref->name,
5450 branch->author,
5451 NULL
5452 };
5454 return grep_text(view, text);
5455 }
5457 static void
5458 branch_select(struct view *view, struct line *line)
5459 {
5460 struct branch *branch = line->data;
5462 string_copy_rev(view->ref, branch->ref->id);
5463 string_copy_rev(ref_commit, branch->ref->id);
5464 string_copy_rev(ref_head, branch->ref->id);
5465 string_copy_rev(ref_branch, branch->ref->name);
5466 }
5468 static struct view_ops branch_ops = {
5469 "branch",
5470 NULL,
5471 branch_open,
5472 branch_read,
5473 branch_draw,
5474 branch_request,
5475 branch_grep,
5476 branch_select,
5477 };
5479 /*
5480 * Status backend
5481 */
5483 struct status {
5484 char status;
5485 struct {
5486 mode_t mode;
5487 char rev[SIZEOF_REV];
5488 char name[SIZEOF_STR];
5489 } old;
5490 struct {
5491 mode_t mode;
5492 char rev[SIZEOF_REV];
5493 char name[SIZEOF_STR];
5494 } new;
5495 };
5497 static char status_onbranch[SIZEOF_STR];
5498 static struct status stage_status;
5499 static enum line_type stage_line_type;
5500 static size_t stage_chunks;
5501 static int *stage_chunk;
5503 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5505 /* This should work even for the "On branch" line. */
5506 static inline bool
5507 status_has_none(struct view *view, struct line *line)
5508 {
5509 return line < view->line + view->lines && !line[1].data;
5510 }
5512 /* Get fields from the diff line:
5513 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5514 */
5515 static inline bool
5516 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5517 {
5518 const char *old_mode = buf + 1;
5519 const char *new_mode = buf + 8;
5520 const char *old_rev = buf + 15;
5521 const char *new_rev = buf + 56;
5522 const char *status = buf + 97;
5524 if (bufsize < 98 ||
5525 old_mode[-1] != ':' ||
5526 new_mode[-1] != ' ' ||
5527 old_rev[-1] != ' ' ||
5528 new_rev[-1] != ' ' ||
5529 status[-1] != ' ')
5530 return FALSE;
5532 file->status = *status;
5534 string_copy_rev(file->old.rev, old_rev);
5535 string_copy_rev(file->new.rev, new_rev);
5537 file->old.mode = strtoul(old_mode, NULL, 8);
5538 file->new.mode = strtoul(new_mode, NULL, 8);
5540 file->old.name[0] = file->new.name[0] = 0;
5542 return TRUE;
5543 }
5545 static bool
5546 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5547 {
5548 struct status *unmerged = NULL;
5549 char *buf;
5550 struct io io = {};
5552 if (!io_run(&io, argv, opt_cdup, IO_RD))
5553 return FALSE;
5555 add_line_data(view, NULL, type);
5557 while ((buf = io_get(&io, 0, TRUE))) {
5558 struct status *file = unmerged;
5560 if (!file) {
5561 file = calloc(1, sizeof(*file));
5562 if (!file || !add_line_data(view, file, type))
5563 goto error_out;
5564 }
5566 /* Parse diff info part. */
5567 if (status) {
5568 file->status = status;
5569 if (status == 'A')
5570 string_copy(file->old.rev, NULL_ID);
5572 } else if (!file->status || file == unmerged) {
5573 if (!status_get_diff(file, buf, strlen(buf)))
5574 goto error_out;
5576 buf = io_get(&io, 0, TRUE);
5577 if (!buf)
5578 break;
5580 /* Collapse all modified entries that follow an
5581 * associated unmerged entry. */
5582 if (unmerged == file) {
5583 unmerged->status = 'U';
5584 unmerged = NULL;
5585 } else if (file->status == 'U') {
5586 unmerged = file;
5587 }
5588 }
5590 /* Grab the old name for rename/copy. */
5591 if (!*file->old.name &&
5592 (file->status == 'R' || file->status == 'C')) {
5593 string_ncopy(file->old.name, buf, strlen(buf));
5595 buf = io_get(&io, 0, TRUE);
5596 if (!buf)
5597 break;
5598 }
5600 /* git-ls-files just delivers a NUL separated list of
5601 * file names similar to the second half of the
5602 * git-diff-* output. */
5603 string_ncopy(file->new.name, buf, strlen(buf));
5604 if (!*file->old.name)
5605 string_copy(file->old.name, file->new.name);
5606 file = NULL;
5607 }
5609 if (io_error(&io)) {
5610 error_out:
5611 io_done(&io);
5612 return FALSE;
5613 }
5615 if (!view->line[view->lines - 1].data)
5616 add_line_data(view, NULL, LINE_STAT_NONE);
5618 io_done(&io);
5619 return TRUE;
5620 }
5622 /* Don't show unmerged entries in the staged section. */
5623 static const char *status_diff_index_argv[] = {
5624 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5625 "--cached", "-M", "HEAD", NULL
5626 };
5628 static const char *status_diff_files_argv[] = {
5629 "git", "diff-files", "-z", NULL
5630 };
5632 static const char *status_list_other_argv[] = {
5633 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5634 };
5636 static const char *status_list_no_head_argv[] = {
5637 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5638 };
5640 static const char *update_index_argv[] = {
5641 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5642 };
5644 /* Restore the previous line number to stay in the context or select a
5645 * line with something that can be updated. */
5646 static void
5647 status_restore(struct view *view)
5648 {
5649 if (view->p_lineno >= view->lines)
5650 view->p_lineno = view->lines - 1;
5651 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5652 view->p_lineno++;
5653 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5654 view->p_lineno--;
5656 /* If the above fails, always skip the "On branch" line. */
5657 if (view->p_lineno < view->lines)
5658 view->lineno = view->p_lineno;
5659 else
5660 view->lineno = 1;
5662 if (view->lineno < view->offset)
5663 view->offset = view->lineno;
5664 else if (view->offset + view->height <= view->lineno)
5665 view->offset = view->lineno - view->height + 1;
5667 view->p_restore = FALSE;
5668 }
5670 static void
5671 status_update_onbranch(void)
5672 {
5673 static const char *paths[][2] = {
5674 { "rebase-apply/rebasing", "Rebasing" },
5675 { "rebase-apply/applying", "Applying mailbox" },
5676 { "rebase-apply/", "Rebasing mailbox" },
5677 { "rebase-merge/interactive", "Interactive rebase" },
5678 { "rebase-merge/", "Rebase merge" },
5679 { "MERGE_HEAD", "Merging" },
5680 { "BISECT_LOG", "Bisecting" },
5681 { "HEAD", "On branch" },
5682 };
5683 char buf[SIZEOF_STR];
5684 struct stat stat;
5685 int i;
5687 if (is_initial_commit()) {
5688 string_copy(status_onbranch, "Initial commit");
5689 return;
5690 }
5692 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5693 char *head = opt_head;
5695 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5696 lstat(buf, &stat) < 0)
5697 continue;
5699 if (!*opt_head) {
5700 struct io io = {};
5702 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5703 io_read_buf(&io, buf, sizeof(buf))) {
5704 head = buf;
5705 if (!prefixcmp(head, "refs/heads/"))
5706 head += STRING_SIZE("refs/heads/");
5707 }
5708 }
5710 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5711 string_copy(status_onbranch, opt_head);
5712 return;
5713 }
5715 string_copy(status_onbranch, "Not currently on any branch");
5716 }
5718 /* First parse staged info using git-diff-index(1), then parse unstaged
5719 * info using git-diff-files(1), and finally untracked files using
5720 * git-ls-files(1). */
5721 static bool
5722 status_open(struct view *view)
5723 {
5724 reset_view(view);
5726 add_line_data(view, NULL, LINE_STAT_HEAD);
5727 status_update_onbranch();
5729 io_run_bg(update_index_argv);
5731 if (is_initial_commit()) {
5732 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5733 return FALSE;
5734 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5735 return FALSE;
5736 }
5738 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5739 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5740 return FALSE;
5742 /* Restore the exact position or use the specialized restore
5743 * mode? */
5744 if (!view->p_restore)
5745 status_restore(view);
5746 return TRUE;
5747 }
5749 static bool
5750 status_draw(struct view *view, struct line *line, unsigned int lineno)
5751 {
5752 struct status *status = line->data;
5753 enum line_type type;
5754 const char *text;
5756 if (!status) {
5757 switch (line->type) {
5758 case LINE_STAT_STAGED:
5759 type = LINE_STAT_SECTION;
5760 text = "Changes to be committed:";
5761 break;
5763 case LINE_STAT_UNSTAGED:
5764 type = LINE_STAT_SECTION;
5765 text = "Changed but not updated:";
5766 break;
5768 case LINE_STAT_UNTRACKED:
5769 type = LINE_STAT_SECTION;
5770 text = "Untracked files:";
5771 break;
5773 case LINE_STAT_NONE:
5774 type = LINE_DEFAULT;
5775 text = " (no files)";
5776 break;
5778 case LINE_STAT_HEAD:
5779 type = LINE_STAT_HEAD;
5780 text = status_onbranch;
5781 break;
5783 default:
5784 return FALSE;
5785 }
5786 } else {
5787 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5789 buf[0] = status->status;
5790 if (draw_text(view, line->type, buf, TRUE))
5791 return TRUE;
5792 type = LINE_DEFAULT;
5793 text = status->new.name;
5794 }
5796 draw_text(view, type, text, TRUE);
5797 return TRUE;
5798 }
5800 static enum request
5801 status_load_error(struct view *view, struct view *stage, const char *path)
5802 {
5803 if (displayed_views() == 2 || display[current_view] != view)
5804 maximize_view(view);
5805 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5806 return REQ_NONE;
5807 }
5809 static enum request
5810 status_enter(struct view *view, struct line *line)
5811 {
5812 struct status *status = line->data;
5813 const char *oldpath = status ? status->old.name : NULL;
5814 /* Diffs for unmerged entries are empty when passing the new
5815 * path, so leave it empty. */
5816 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5817 const char *info;
5818 enum open_flags split;
5819 struct view *stage = VIEW(REQ_VIEW_STAGE);
5821 if (line->type == LINE_STAT_NONE ||
5822 (!status && line[1].type == LINE_STAT_NONE)) {
5823 report("No file to diff");
5824 return REQ_NONE;
5825 }
5827 switch (line->type) {
5828 case LINE_STAT_STAGED:
5829 if (is_initial_commit()) {
5830 const char *no_head_diff_argv[] = {
5831 "git", "diff", "--no-color", "--patch-with-stat",
5832 "--", "/dev/null", newpath, NULL
5833 };
5835 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5836 return status_load_error(view, stage, newpath);
5837 } else {
5838 const char *index_show_argv[] = {
5839 "git", "diff-index", "--root", "--patch-with-stat",
5840 "-C", "-M", "--cached", "HEAD", "--",
5841 oldpath, newpath, NULL
5842 };
5844 if (!prepare_update(stage, index_show_argv, opt_cdup))
5845 return status_load_error(view, stage, newpath);
5846 }
5848 if (status)
5849 info = "Staged changes to %s";
5850 else
5851 info = "Staged changes";
5852 break;
5854 case LINE_STAT_UNSTAGED:
5855 {
5856 const char *files_show_argv[] = {
5857 "git", "diff-files", "--root", "--patch-with-stat",
5858 "-C", "-M", "--", oldpath, newpath, NULL
5859 };
5861 if (!prepare_update(stage, files_show_argv, opt_cdup))
5862 return status_load_error(view, stage, newpath);
5863 if (status)
5864 info = "Unstaged changes to %s";
5865 else
5866 info = "Unstaged changes";
5867 break;
5868 }
5869 case LINE_STAT_UNTRACKED:
5870 if (!newpath) {
5871 report("No file to show");
5872 return REQ_NONE;
5873 }
5875 if (!suffixcmp(status->new.name, -1, "/")) {
5876 report("Cannot display a directory");
5877 return REQ_NONE;
5878 }
5880 if (!prepare_update_file(stage, newpath))
5881 return status_load_error(view, stage, newpath);
5882 info = "Untracked file %s";
5883 break;
5885 case LINE_STAT_HEAD:
5886 return REQ_NONE;
5888 default:
5889 die("line type %d not handled in switch", line->type);
5890 }
5892 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5893 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5894 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5895 if (status) {
5896 stage_status = *status;
5897 } else {
5898 memset(&stage_status, 0, sizeof(stage_status));
5899 }
5901 stage_line_type = line->type;
5902 stage_chunks = 0;
5903 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5904 }
5906 return REQ_NONE;
5907 }
5909 static bool
5910 status_exists(struct status *status, enum line_type type)
5911 {
5912 struct view *view = VIEW(REQ_VIEW_STATUS);
5913 unsigned long lineno;
5915 for (lineno = 0; lineno < view->lines; lineno++) {
5916 struct line *line = &view->line[lineno];
5917 struct status *pos = line->data;
5919 if (line->type != type)
5920 continue;
5921 if (!pos && (!status || !status->status) && line[1].data) {
5922 select_view_line(view, lineno);
5923 return TRUE;
5924 }
5925 if (pos && !strcmp(status->new.name, pos->new.name)) {
5926 select_view_line(view, lineno);
5927 return TRUE;
5928 }
5929 }
5931 return FALSE;
5932 }
5935 static bool
5936 status_update_prepare(struct io *io, enum line_type type)
5937 {
5938 const char *staged_argv[] = {
5939 "git", "update-index", "-z", "--index-info", NULL
5940 };
5941 const char *others_argv[] = {
5942 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5943 };
5945 switch (type) {
5946 case LINE_STAT_STAGED:
5947 return io_run(io, staged_argv, opt_cdup, IO_WR);
5949 case LINE_STAT_UNSTAGED:
5950 case LINE_STAT_UNTRACKED:
5951 return io_run(io, others_argv, opt_cdup, IO_WR);
5953 default:
5954 die("line type %d not handled in switch", type);
5955 return FALSE;
5956 }
5957 }
5959 static bool
5960 status_update_write(struct io *io, struct status *status, enum line_type type)
5961 {
5962 char buf[SIZEOF_STR];
5963 size_t bufsize = 0;
5965 switch (type) {
5966 case LINE_STAT_STAGED:
5967 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5968 status->old.mode,
5969 status->old.rev,
5970 status->old.name, 0))
5971 return FALSE;
5972 break;
5974 case LINE_STAT_UNSTAGED:
5975 case LINE_STAT_UNTRACKED:
5976 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5977 return FALSE;
5978 break;
5980 default:
5981 die("line type %d not handled in switch", type);
5982 }
5984 return io_write(io, buf, bufsize);
5985 }
5987 static bool
5988 status_update_file(struct status *status, enum line_type type)
5989 {
5990 struct io io = {};
5991 bool result;
5993 if (!status_update_prepare(&io, type))
5994 return FALSE;
5996 result = status_update_write(&io, status, type);
5997 return io_done(&io) && result;
5998 }
6000 static bool
6001 status_update_files(struct view *view, struct line *line)
6002 {
6003 char buf[sizeof(view->ref)];
6004 struct io io = {};
6005 bool result = TRUE;
6006 struct line *pos = view->line + view->lines;
6007 int files = 0;
6008 int file, done;
6009 int cursor_y = -1, cursor_x = -1;
6011 if (!status_update_prepare(&io, line->type))
6012 return FALSE;
6014 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6015 files++;
6017 string_copy(buf, view->ref);
6018 getsyx(cursor_y, cursor_x);
6019 for (file = 0, done = 5; result && file < files; line++, file++) {
6020 int almost_done = file * 100 / files;
6022 if (almost_done > done) {
6023 done = almost_done;
6024 string_format(view->ref, "updating file %u of %u (%d%% done)",
6025 file, files, done);
6026 update_view_title(view);
6027 setsyx(cursor_y, cursor_x);
6028 doupdate();
6029 }
6030 result = status_update_write(&io, line->data, line->type);
6031 }
6032 string_copy(view->ref, buf);
6034 return io_done(&io) && result;
6035 }
6037 static bool
6038 status_update(struct view *view)
6039 {
6040 struct line *line = &view->line[view->lineno];
6042 assert(view->lines);
6044 if (!line->data) {
6045 /* This should work even for the "On branch" line. */
6046 if (line < view->line + view->lines && !line[1].data) {
6047 report("Nothing to update");
6048 return FALSE;
6049 }
6051 if (!status_update_files(view, line + 1)) {
6052 report("Failed to update file status");
6053 return FALSE;
6054 }
6056 } else if (!status_update_file(line->data, line->type)) {
6057 report("Failed to update file status");
6058 return FALSE;
6059 }
6061 return TRUE;
6062 }
6064 static bool
6065 status_revert(struct status *status, enum line_type type, bool has_none)
6066 {
6067 if (!status || type != LINE_STAT_UNSTAGED) {
6068 if (type == LINE_STAT_STAGED) {
6069 report("Cannot revert changes to staged files");
6070 } else if (type == LINE_STAT_UNTRACKED) {
6071 report("Cannot revert changes to untracked files");
6072 } else if (has_none) {
6073 report("Nothing to revert");
6074 } else {
6075 report("Cannot revert changes to multiple files");
6076 }
6078 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6079 char mode[10] = "100644";
6080 const char *reset_argv[] = {
6081 "git", "update-index", "--cacheinfo", mode,
6082 status->old.rev, status->old.name, NULL
6083 };
6084 const char *checkout_argv[] = {
6085 "git", "checkout", "--", status->old.name, NULL
6086 };
6088 if (status->status == 'U') {
6089 string_format(mode, "%5o", status->old.mode);
6091 if (status->old.mode == 0 && status->new.mode == 0) {
6092 reset_argv[2] = "--force-remove";
6093 reset_argv[3] = status->old.name;
6094 reset_argv[4] = NULL;
6095 }
6097 if (!io_run_fg(reset_argv, opt_cdup))
6098 return FALSE;
6099 if (status->old.mode == 0 && status->new.mode == 0)
6100 return TRUE;
6101 }
6103 return io_run_fg(checkout_argv, opt_cdup);
6104 }
6106 return FALSE;
6107 }
6109 static enum request
6110 status_request(struct view *view, enum request request, struct line *line)
6111 {
6112 struct status *status = line->data;
6114 switch (request) {
6115 case REQ_STATUS_UPDATE:
6116 if (!status_update(view))
6117 return REQ_NONE;
6118 break;
6120 case REQ_STATUS_REVERT:
6121 if (!status_revert(status, line->type, status_has_none(view, line)))
6122 return REQ_NONE;
6123 break;
6125 case REQ_STATUS_MERGE:
6126 if (!status || status->status != 'U') {
6127 report("Merging only possible for files with unmerged status ('U').");
6128 return REQ_NONE;
6129 }
6130 open_mergetool(status->new.name);
6131 break;
6133 case REQ_EDIT:
6134 if (!status)
6135 return request;
6136 if (status->status == 'D') {
6137 report("File has been deleted.");
6138 return REQ_NONE;
6139 }
6141 open_editor(status->new.name);
6142 break;
6144 case REQ_VIEW_BLAME:
6145 if (status)
6146 opt_ref[0] = 0;
6147 return request;
6149 case REQ_ENTER:
6150 /* After returning the status view has been split to
6151 * show the stage view. No further reloading is
6152 * necessary. */
6153 return status_enter(view, line);
6155 case REQ_REFRESH:
6156 /* Simply reload the view. */
6157 break;
6159 default:
6160 return request;
6161 }
6163 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6165 return REQ_NONE;
6166 }
6168 static void
6169 status_select(struct view *view, struct line *line)
6170 {
6171 struct status *status = line->data;
6172 char file[SIZEOF_STR] = "all files";
6173 const char *text;
6174 const char *key;
6176 if (status && !string_format(file, "'%s'", status->new.name))
6177 return;
6179 if (!status && line[1].type == LINE_STAT_NONE)
6180 line++;
6182 switch (line->type) {
6183 case LINE_STAT_STAGED:
6184 text = "Press %s to unstage %s for commit";
6185 break;
6187 case LINE_STAT_UNSTAGED:
6188 text = "Press %s to stage %s for commit";
6189 break;
6191 case LINE_STAT_UNTRACKED:
6192 text = "Press %s to stage %s for addition";
6193 break;
6195 case LINE_STAT_HEAD:
6196 case LINE_STAT_NONE:
6197 text = "Nothing to update";
6198 break;
6200 default:
6201 die("line type %d not handled in switch", line->type);
6202 }
6204 if (status && status->status == 'U') {
6205 text = "Press %s to resolve conflict in %s";
6206 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6208 } else {
6209 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6210 }
6212 string_format(view->ref, text, key, file);
6213 if (status)
6214 string_copy(opt_file, status->new.name);
6215 }
6217 static bool
6218 status_grep(struct view *view, struct line *line)
6219 {
6220 struct status *status = line->data;
6222 if (status) {
6223 const char buf[2] = { status->status, 0 };
6224 const char *text[] = { status->new.name, buf, NULL };
6226 return grep_text(view, text);
6227 }
6229 return FALSE;
6230 }
6232 static struct view_ops status_ops = {
6233 "file",
6234 NULL,
6235 status_open,
6236 NULL,
6237 status_draw,
6238 status_request,
6239 status_grep,
6240 status_select,
6241 };
6244 static bool
6245 stage_diff_write(struct io *io, struct line *line, struct line *end)
6246 {
6247 while (line < end) {
6248 if (!io_write(io, line->data, strlen(line->data)) ||
6249 !io_write(io, "\n", 1))
6250 return FALSE;
6251 line++;
6252 if (line->type == LINE_DIFF_CHUNK ||
6253 line->type == LINE_DIFF_HEADER)
6254 break;
6255 }
6257 return TRUE;
6258 }
6260 static struct line *
6261 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6262 {
6263 for (; view->line < line; line--)
6264 if (line->type == type)
6265 return line;
6267 return NULL;
6268 }
6270 static bool
6271 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6272 {
6273 const char *apply_argv[SIZEOF_ARG] = {
6274 "git", "apply", "--whitespace=nowarn", NULL
6275 };
6276 struct line *diff_hdr;
6277 struct io io = {};
6278 int argc = 3;
6280 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6281 if (!diff_hdr)
6282 return FALSE;
6284 if (!revert)
6285 apply_argv[argc++] = "--cached";
6286 if (revert || stage_line_type == LINE_STAT_STAGED)
6287 apply_argv[argc++] = "-R";
6288 apply_argv[argc++] = "-";
6289 apply_argv[argc++] = NULL;
6290 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6291 return FALSE;
6293 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6294 !stage_diff_write(&io, chunk, view->line + view->lines))
6295 chunk = NULL;
6297 io_done(&io);
6298 io_run_bg(update_index_argv);
6300 return chunk ? TRUE : FALSE;
6301 }
6303 static bool
6304 stage_update(struct view *view, struct line *line)
6305 {
6306 struct line *chunk = NULL;
6308 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6309 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6311 if (chunk) {
6312 if (!stage_apply_chunk(view, chunk, FALSE)) {
6313 report("Failed to apply chunk");
6314 return FALSE;
6315 }
6317 } else if (!stage_status.status) {
6318 view = VIEW(REQ_VIEW_STATUS);
6320 for (line = view->line; line < view->line + view->lines; line++)
6321 if (line->type == stage_line_type)
6322 break;
6324 if (!status_update_files(view, line + 1)) {
6325 report("Failed to update files");
6326 return FALSE;
6327 }
6329 } else if (!status_update_file(&stage_status, stage_line_type)) {
6330 report("Failed to update file");
6331 return FALSE;
6332 }
6334 return TRUE;
6335 }
6337 static bool
6338 stage_revert(struct view *view, struct line *line)
6339 {
6340 struct line *chunk = NULL;
6342 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6343 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6345 if (chunk) {
6346 if (!prompt_yesno("Are you sure you want to revert changes?"))
6347 return FALSE;
6349 if (!stage_apply_chunk(view, chunk, TRUE)) {
6350 report("Failed to revert chunk");
6351 return FALSE;
6352 }
6353 return TRUE;
6355 } else {
6356 return status_revert(stage_status.status ? &stage_status : NULL,
6357 stage_line_type, FALSE);
6358 }
6359 }
6362 static void
6363 stage_next(struct view *view, struct line *line)
6364 {
6365 int i;
6367 if (!stage_chunks) {
6368 for (line = view->line; line < view->line + view->lines; line++) {
6369 if (line->type != LINE_DIFF_CHUNK)
6370 continue;
6372 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6373 report("Allocation failure");
6374 return;
6375 }
6377 stage_chunk[stage_chunks++] = line - view->line;
6378 }
6379 }
6381 for (i = 0; i < stage_chunks; i++) {
6382 if (stage_chunk[i] > view->lineno) {
6383 do_scroll_view(view, stage_chunk[i] - view->lineno);
6384 report("Chunk %d of %d", i + 1, stage_chunks);
6385 return;
6386 }
6387 }
6389 report("No next chunk found");
6390 }
6392 static enum request
6393 stage_request(struct view *view, enum request request, struct line *line)
6394 {
6395 switch (request) {
6396 case REQ_STATUS_UPDATE:
6397 if (!stage_update(view, line))
6398 return REQ_NONE;
6399 break;
6401 case REQ_STATUS_REVERT:
6402 if (!stage_revert(view, line))
6403 return REQ_NONE;
6404 break;
6406 case REQ_STAGE_NEXT:
6407 if (stage_line_type == LINE_STAT_UNTRACKED) {
6408 report("File is untracked; press %s to add",
6409 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6410 return REQ_NONE;
6411 }
6412 stage_next(view, line);
6413 return REQ_NONE;
6415 case REQ_EDIT:
6416 if (!stage_status.new.name[0])
6417 return request;
6418 if (stage_status.status == 'D') {
6419 report("File has been deleted.");
6420 return REQ_NONE;
6421 }
6423 open_editor(stage_status.new.name);
6424 break;
6426 case REQ_REFRESH:
6427 /* Reload everything ... */
6428 break;
6430 case REQ_VIEW_BLAME:
6431 if (stage_status.new.name[0]) {
6432 string_copy(opt_file, stage_status.new.name);
6433 opt_ref[0] = 0;
6434 }
6435 return request;
6437 case REQ_ENTER:
6438 return pager_request(view, request, line);
6440 default:
6441 return request;
6442 }
6444 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6445 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6447 /* Check whether the staged entry still exists, and close the
6448 * stage view if it doesn't. */
6449 if (!status_exists(&stage_status, stage_line_type)) {
6450 status_restore(VIEW(REQ_VIEW_STATUS));
6451 return REQ_VIEW_CLOSE;
6452 }
6454 if (stage_line_type == LINE_STAT_UNTRACKED) {
6455 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6456 report("Cannot display a directory");
6457 return REQ_NONE;
6458 }
6460 if (!prepare_update_file(view, stage_status.new.name)) {
6461 report("Failed to open file: %s", strerror(errno));
6462 return REQ_NONE;
6463 }
6464 }
6465 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6467 return REQ_NONE;
6468 }
6470 static struct view_ops stage_ops = {
6471 "line",
6472 NULL,
6473 NULL,
6474 pager_read,
6475 pager_draw,
6476 stage_request,
6477 pager_grep,
6478 pager_select,
6479 };
6482 /*
6483 * Revision graph
6484 */
6486 struct commit {
6487 char id[SIZEOF_REV]; /* SHA1 ID. */
6488 char title[128]; /* First line of the commit message. */
6489 const char *author; /* Author of the commit. */
6490 struct time time; /* Date from the author ident. */
6491 struct ref_list *refs; /* Repository references. */
6492 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6493 size_t graph_size; /* The width of the graph array. */
6494 bool has_parents; /* Rewritten --parents seen. */
6495 };
6497 /* Size of rev graph with no "padding" columns */
6498 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6500 struct rev_graph {
6501 struct rev_graph *prev, *next, *parents;
6502 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6503 size_t size;
6504 struct commit *commit;
6505 size_t pos;
6506 unsigned int boundary:1;
6507 };
6509 /* Parents of the commit being visualized. */
6510 static struct rev_graph graph_parents[4];
6512 /* The current stack of revisions on the graph. */
6513 static struct rev_graph graph_stacks[4] = {
6514 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6515 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6516 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6517 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6518 };
6520 static inline bool
6521 graph_parent_is_merge(struct rev_graph *graph)
6522 {
6523 return graph->parents->size > 1;
6524 }
6526 static inline void
6527 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6528 {
6529 struct commit *commit = graph->commit;
6531 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6532 commit->graph[commit->graph_size++] = symbol;
6533 }
6535 static void
6536 clear_rev_graph(struct rev_graph *graph)
6537 {
6538 graph->boundary = 0;
6539 graph->size = graph->pos = 0;
6540 graph->commit = NULL;
6541 memset(graph->parents, 0, sizeof(*graph->parents));
6542 }
6544 static void
6545 done_rev_graph(struct rev_graph *graph)
6546 {
6547 if (graph_parent_is_merge(graph) &&
6548 graph->pos < graph->size - 1 &&
6549 graph->next->size == graph->size + graph->parents->size - 1) {
6550 size_t i = graph->pos + graph->parents->size - 1;
6552 graph->commit->graph_size = i * 2;
6553 while (i < graph->next->size - 1) {
6554 append_to_rev_graph(graph, ' ');
6555 append_to_rev_graph(graph, '\\');
6556 i++;
6557 }
6558 }
6560 clear_rev_graph(graph);
6561 }
6563 static void
6564 push_rev_graph(struct rev_graph *graph, const char *parent)
6565 {
6566 int i;
6568 /* "Collapse" duplicate parents lines.
6569 *
6570 * FIXME: This needs to also update update the drawn graph but
6571 * for now it just serves as a method for pruning graph lines. */
6572 for (i = 0; i < graph->size; i++)
6573 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6574 return;
6576 if (graph->size < SIZEOF_REVITEMS) {
6577 string_copy_rev(graph->rev[graph->size++], parent);
6578 }
6579 }
6581 static chtype
6582 get_rev_graph_symbol(struct rev_graph *graph)
6583 {
6584 chtype symbol;
6586 if (graph->boundary)
6587 symbol = REVGRAPH_BOUND;
6588 else if (graph->parents->size == 0)
6589 symbol = REVGRAPH_INIT;
6590 else if (graph_parent_is_merge(graph))
6591 symbol = REVGRAPH_MERGE;
6592 else if (graph->pos >= graph->size)
6593 symbol = REVGRAPH_BRANCH;
6594 else
6595 symbol = REVGRAPH_COMMIT;
6597 return symbol;
6598 }
6600 static void
6601 draw_rev_graph(struct rev_graph *graph)
6602 {
6603 struct rev_filler {
6604 chtype separator, line;
6605 };
6606 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6607 static struct rev_filler fillers[] = {
6608 { ' ', '|' },
6609 { '`', '.' },
6610 { '\'', ' ' },
6611 { '/', ' ' },
6612 };
6613 chtype symbol = get_rev_graph_symbol(graph);
6614 struct rev_filler *filler;
6615 size_t i;
6617 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6618 filler = &fillers[DEFAULT];
6620 for (i = 0; i < graph->pos; i++) {
6621 append_to_rev_graph(graph, filler->line);
6622 if (graph_parent_is_merge(graph->prev) &&
6623 graph->prev->pos == i)
6624 filler = &fillers[RSHARP];
6626 append_to_rev_graph(graph, filler->separator);
6627 }
6629 /* Place the symbol for this revision. */
6630 append_to_rev_graph(graph, symbol);
6632 if (graph->prev->size > graph->size)
6633 filler = &fillers[RDIAG];
6634 else
6635 filler = &fillers[DEFAULT];
6637 i++;
6639 for (; i < graph->size; i++) {
6640 append_to_rev_graph(graph, filler->separator);
6641 append_to_rev_graph(graph, filler->line);
6642 if (graph_parent_is_merge(graph->prev) &&
6643 i < graph->prev->pos + graph->parents->size)
6644 filler = &fillers[RSHARP];
6645 if (graph->prev->size > graph->size)
6646 filler = &fillers[LDIAG];
6647 }
6649 if (graph->prev->size > graph->size) {
6650 append_to_rev_graph(graph, filler->separator);
6651 if (filler->line != ' ')
6652 append_to_rev_graph(graph, filler->line);
6653 }
6654 }
6656 /* Prepare the next rev graph */
6657 static void
6658 prepare_rev_graph(struct rev_graph *graph)
6659 {
6660 size_t i;
6662 /* First, traverse all lines of revisions up to the active one. */
6663 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6664 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6665 break;
6667 push_rev_graph(graph->next, graph->rev[graph->pos]);
6668 }
6670 /* Interleave the new revision parent(s). */
6671 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6672 push_rev_graph(graph->next, graph->parents->rev[i]);
6674 /* Lastly, put any remaining revisions. */
6675 for (i = graph->pos + 1; i < graph->size; i++)
6676 push_rev_graph(graph->next, graph->rev[i]);
6677 }
6679 static void
6680 update_rev_graph(struct view *view, struct rev_graph *graph)
6681 {
6682 /* If this is the finalizing update ... */
6683 if (graph->commit)
6684 prepare_rev_graph(graph);
6686 /* Graph visualization needs a one rev look-ahead,
6687 * so the first update doesn't visualize anything. */
6688 if (!graph->prev->commit)
6689 return;
6691 if (view->lines > 2)
6692 view->line[view->lines - 3].dirty = 1;
6693 if (view->lines > 1)
6694 view->line[view->lines - 2].dirty = 1;
6695 draw_rev_graph(graph->prev);
6696 done_rev_graph(graph->prev->prev);
6697 }
6700 /*
6701 * Main view backend
6702 */
6704 static const char *main_argv[SIZEOF_ARG] = {
6705 "git", "log", "--no-color", "--pretty=raw", "--parents",
6706 "--topo-order", "%(head)", NULL
6707 };
6709 static bool
6710 main_draw(struct view *view, struct line *line, unsigned int lineno)
6711 {
6712 struct commit *commit = line->data;
6714 if (!commit->author)
6715 return FALSE;
6717 if (opt_date && draw_date(view, &commit->time))
6718 return TRUE;
6720 if (opt_author && draw_author(view, commit->author))
6721 return TRUE;
6723 if (opt_rev_graph && commit->graph_size &&
6724 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6725 return TRUE;
6727 if (opt_show_refs && commit->refs) {
6728 size_t i;
6730 for (i = 0; i < commit->refs->size; i++) {
6731 struct ref *ref = commit->refs->refs[i];
6732 enum line_type type;
6734 if (ref->head)
6735 type = LINE_MAIN_HEAD;
6736 else if (ref->ltag)
6737 type = LINE_MAIN_LOCAL_TAG;
6738 else if (ref->tag)
6739 type = LINE_MAIN_TAG;
6740 else if (ref->tracked)
6741 type = LINE_MAIN_TRACKED;
6742 else if (ref->remote)
6743 type = LINE_MAIN_REMOTE;
6744 else
6745 type = LINE_MAIN_REF;
6747 if (draw_text(view, type, "[", TRUE) ||
6748 draw_text(view, type, ref->name, TRUE) ||
6749 draw_text(view, type, "]", TRUE))
6750 return TRUE;
6752 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6753 return TRUE;
6754 }
6755 }
6757 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6758 return TRUE;
6759 }
6761 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6762 static bool
6763 main_read(struct view *view, char *line)
6764 {
6765 static struct rev_graph *graph = graph_stacks;
6766 enum line_type type;
6767 struct commit *commit;
6769 if (!line) {
6770 int i;
6772 if (!view->lines && !view->prev)
6773 die("No revisions match the given arguments.");
6774 if (view->lines > 0) {
6775 commit = view->line[view->lines - 1].data;
6776 view->line[view->lines - 1].dirty = 1;
6777 if (!commit->author) {
6778 view->lines--;
6779 free(commit);
6780 graph->commit = NULL;
6781 }
6782 }
6783 update_rev_graph(view, graph);
6785 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6786 clear_rev_graph(&graph_stacks[i]);
6787 return TRUE;
6788 }
6790 type = get_line_type(line);
6791 if (type == LINE_COMMIT) {
6792 commit = calloc(1, sizeof(struct commit));
6793 if (!commit)
6794 return FALSE;
6796 line += STRING_SIZE("commit ");
6797 if (*line == '-') {
6798 graph->boundary = 1;
6799 line++;
6800 }
6802 string_copy_rev(commit->id, line);
6803 commit->refs = get_ref_list(commit->id);
6804 graph->commit = commit;
6805 add_line_data(view, commit, LINE_MAIN_COMMIT);
6807 while ((line = strchr(line, ' '))) {
6808 line++;
6809 push_rev_graph(graph->parents, line);
6810 commit->has_parents = TRUE;
6811 }
6812 return TRUE;
6813 }
6815 if (!view->lines)
6816 return TRUE;
6817 commit = view->line[view->lines - 1].data;
6819 switch (type) {
6820 case LINE_PARENT:
6821 if (commit->has_parents)
6822 break;
6823 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6824 break;
6826 case LINE_AUTHOR:
6827 parse_author_line(line + STRING_SIZE("author "),
6828 &commit->author, &commit->time);
6829 update_rev_graph(view, graph);
6830 graph = graph->next;
6831 break;
6833 default:
6834 /* Fill in the commit title if it has not already been set. */
6835 if (commit->title[0])
6836 break;
6838 /* Require titles to start with a non-space character at the
6839 * offset used by git log. */
6840 if (strncmp(line, " ", 4))
6841 break;
6842 line += 4;
6843 /* Well, if the title starts with a whitespace character,
6844 * try to be forgiving. Otherwise we end up with no title. */
6845 while (isspace(*line))
6846 line++;
6847 if (*line == '\0')
6848 break;
6849 /* FIXME: More graceful handling of titles; append "..." to
6850 * shortened titles, etc. */
6852 string_expand(commit->title, sizeof(commit->title), line, 1);
6853 view->line[view->lines - 1].dirty = 1;
6854 }
6856 return TRUE;
6857 }
6859 static enum request
6860 main_request(struct view *view, enum request request, struct line *line)
6861 {
6862 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6864 switch (request) {
6865 case REQ_ENTER:
6866 open_view(view, REQ_VIEW_DIFF, flags);
6867 break;
6868 case REQ_REFRESH:
6869 load_refs();
6870 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6871 break;
6872 default:
6873 return request;
6874 }
6876 return REQ_NONE;
6877 }
6879 static bool
6880 grep_refs(struct ref_list *list, regex_t *regex)
6881 {
6882 regmatch_t pmatch;
6883 size_t i;
6885 if (!opt_show_refs || !list)
6886 return FALSE;
6888 for (i = 0; i < list->size; i++) {
6889 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6890 return TRUE;
6891 }
6893 return FALSE;
6894 }
6896 static bool
6897 main_grep(struct view *view, struct line *line)
6898 {
6899 struct commit *commit = line->data;
6900 const char *text[] = {
6901 commit->title,
6902 opt_author ? commit->author : "",
6903 mkdate(&commit->time, opt_date),
6904 NULL
6905 };
6907 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6908 }
6910 static void
6911 main_select(struct view *view, struct line *line)
6912 {
6913 struct commit *commit = line->data;
6915 string_copy_rev(view->ref, commit->id);
6916 string_copy_rev(ref_commit, view->ref);
6917 }
6919 static struct view_ops main_ops = {
6920 "commit",
6921 main_argv,
6922 NULL,
6923 main_read,
6924 main_draw,
6925 main_request,
6926 main_grep,
6927 main_select,
6928 };
6931 /*
6932 * Status management
6933 */
6935 /* Whether or not the curses interface has been initialized. */
6936 static bool cursed = FALSE;
6938 /* Terminal hacks and workarounds. */
6939 static bool use_scroll_redrawwin;
6940 static bool use_scroll_status_wclear;
6942 /* The status window is used for polling keystrokes. */
6943 static WINDOW *status_win;
6945 /* Reading from the prompt? */
6946 static bool input_mode = FALSE;
6948 static bool status_empty = FALSE;
6950 /* Update status and title window. */
6951 static void
6952 report(const char *msg, ...)
6953 {
6954 struct view *view = display[current_view];
6956 if (input_mode)
6957 return;
6959 if (!view) {
6960 char buf[SIZEOF_STR];
6961 va_list args;
6963 va_start(args, msg);
6964 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6965 buf[sizeof(buf) - 1] = 0;
6966 buf[sizeof(buf) - 2] = '.';
6967 buf[sizeof(buf) - 3] = '.';
6968 buf[sizeof(buf) - 4] = '.';
6969 }
6970 va_end(args);
6971 die("%s", buf);
6972 }
6974 if (!status_empty || *msg) {
6975 va_list args;
6977 va_start(args, msg);
6979 wmove(status_win, 0, 0);
6980 if (view->has_scrolled && use_scroll_status_wclear)
6981 wclear(status_win);
6982 if (*msg) {
6983 vwprintw(status_win, msg, args);
6984 status_empty = FALSE;
6985 } else {
6986 status_empty = TRUE;
6987 }
6988 wclrtoeol(status_win);
6989 wnoutrefresh(status_win);
6991 va_end(args);
6992 }
6994 update_view_title(view);
6995 }
6997 static void
6998 init_display(void)
6999 {
7000 const char *term;
7001 int x, y;
7003 /* Initialize the curses library */
7004 if (isatty(STDIN_FILENO)) {
7005 cursed = !!initscr();
7006 opt_tty = stdin;
7007 } else {
7008 /* Leave stdin and stdout alone when acting as a pager. */
7009 opt_tty = fopen("/dev/tty", "r+");
7010 if (!opt_tty)
7011 die("Failed to open /dev/tty");
7012 cursed = !!newterm(NULL, opt_tty, opt_tty);
7013 }
7015 if (!cursed)
7016 die("Failed to initialize curses");
7018 nonl(); /* Disable conversion and detect newlines from input. */
7019 cbreak(); /* Take input chars one at a time, no wait for \n */
7020 noecho(); /* Don't echo input */
7021 leaveok(stdscr, FALSE);
7023 if (has_colors())
7024 init_colors();
7026 getmaxyx(stdscr, y, x);
7027 status_win = newwin(1, 0, y - 1, 0);
7028 if (!status_win)
7029 die("Failed to create status window");
7031 /* Enable keyboard mapping */
7032 keypad(status_win, TRUE);
7033 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7035 TABSIZE = opt_tab_size;
7037 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7038 if (term && !strcmp(term, "gnome-terminal")) {
7039 /* In the gnome-terminal-emulator, the message from
7040 * scrolling up one line when impossible followed by
7041 * scrolling down one line causes corruption of the
7042 * status line. This is fixed by calling wclear. */
7043 use_scroll_status_wclear = TRUE;
7044 use_scroll_redrawwin = FALSE;
7046 } else if (term && !strcmp(term, "xrvt-xpm")) {
7047 /* No problems with full optimizations in xrvt-(unicode)
7048 * and aterm. */
7049 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7051 } else {
7052 /* When scrolling in (u)xterm the last line in the
7053 * scrolling direction will update slowly. */
7054 use_scroll_redrawwin = TRUE;
7055 use_scroll_status_wclear = FALSE;
7056 }
7057 }
7059 static int
7060 get_input(int prompt_position)
7061 {
7062 struct view *view;
7063 int i, key, cursor_y, cursor_x;
7064 bool loading = FALSE;
7066 if (prompt_position)
7067 input_mode = TRUE;
7069 while (TRUE) {
7070 foreach_view (view, i) {
7071 update_view(view);
7072 if (view_is_displayed(view) && view->has_scrolled &&
7073 use_scroll_redrawwin)
7074 redrawwin(view->win);
7075 view->has_scrolled = FALSE;
7076 if (view->pipe)
7077 loading = TRUE;
7078 }
7080 /* Update the cursor position. */
7081 if (prompt_position) {
7082 getbegyx(status_win, cursor_y, cursor_x);
7083 cursor_x = prompt_position;
7084 } else {
7085 view = display[current_view];
7086 getbegyx(view->win, cursor_y, cursor_x);
7087 cursor_x = view->width - 1;
7088 cursor_y += view->lineno - view->offset;
7089 }
7090 setsyx(cursor_y, cursor_x);
7092 /* Refresh, accept single keystroke of input */
7093 doupdate();
7094 nodelay(status_win, loading);
7095 key = wgetch(status_win);
7097 /* wgetch() with nodelay() enabled returns ERR when
7098 * there's no input. */
7099 if (key == ERR) {
7101 } else if (key == KEY_RESIZE) {
7102 int height, width;
7104 getmaxyx(stdscr, height, width);
7106 wresize(status_win, 1, width);
7107 mvwin(status_win, height - 1, 0);
7108 wnoutrefresh(status_win);
7109 resize_display();
7110 redraw_display(TRUE);
7112 } else {
7113 input_mode = FALSE;
7114 return key;
7115 }
7116 }
7117 }
7119 static char *
7120 prompt_input(const char *prompt, input_handler handler, void *data)
7121 {
7122 enum input_status status = INPUT_OK;
7123 static char buf[SIZEOF_STR];
7124 size_t pos = 0;
7126 buf[pos] = 0;
7128 while (status == INPUT_OK || status == INPUT_SKIP) {
7129 int key;
7131 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7132 wclrtoeol(status_win);
7134 key = get_input(pos + 1);
7135 switch (key) {
7136 case KEY_RETURN:
7137 case KEY_ENTER:
7138 case '\n':
7139 status = pos ? INPUT_STOP : INPUT_CANCEL;
7140 break;
7142 case KEY_BACKSPACE:
7143 if (pos > 0)
7144 buf[--pos] = 0;
7145 else
7146 status = INPUT_CANCEL;
7147 break;
7149 case KEY_ESC:
7150 status = INPUT_CANCEL;
7151 break;
7153 default:
7154 if (pos >= sizeof(buf)) {
7155 report("Input string too long");
7156 return NULL;
7157 }
7159 status = handler(data, buf, key);
7160 if (status == INPUT_OK)
7161 buf[pos++] = (char) key;
7162 }
7163 }
7165 /* Clear the status window */
7166 status_empty = FALSE;
7167 report("");
7169 if (status == INPUT_CANCEL)
7170 return NULL;
7172 buf[pos++] = 0;
7174 return buf;
7175 }
7177 static enum input_status
7178 prompt_yesno_handler(void *data, char *buf, int c)
7179 {
7180 if (c == 'y' || c == 'Y')
7181 return INPUT_STOP;
7182 if (c == 'n' || c == 'N')
7183 return INPUT_CANCEL;
7184 return INPUT_SKIP;
7185 }
7187 static bool
7188 prompt_yesno(const char *prompt)
7189 {
7190 char prompt2[SIZEOF_STR];
7192 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7193 return FALSE;
7195 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7196 }
7198 static enum input_status
7199 read_prompt_handler(void *data, char *buf, int c)
7200 {
7201 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7202 }
7204 static char *
7205 read_prompt(const char *prompt)
7206 {
7207 return prompt_input(prompt, read_prompt_handler, NULL);
7208 }
7210 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7211 {
7212 enum input_status status = INPUT_OK;
7213 int size = 0;
7215 while (items[size].text)
7216 size++;
7218 while (status == INPUT_OK) {
7219 const struct menu_item *item = &items[*selected];
7220 int key;
7221 int i;
7223 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7224 prompt, *selected + 1, size);
7225 if (item->hotkey)
7226 wprintw(status_win, "[%c] ", (char) item->hotkey);
7227 wprintw(status_win, "%s", item->text);
7228 wclrtoeol(status_win);
7230 key = get_input(COLS - 1);
7231 switch (key) {
7232 case KEY_RETURN:
7233 case KEY_ENTER:
7234 case '\n':
7235 status = INPUT_STOP;
7236 break;
7238 case KEY_LEFT:
7239 case KEY_UP:
7240 *selected = *selected - 1;
7241 if (*selected < 0)
7242 *selected = size - 1;
7243 break;
7245 case KEY_RIGHT:
7246 case KEY_DOWN:
7247 *selected = (*selected + 1) % size;
7248 break;
7250 case KEY_ESC:
7251 status = INPUT_CANCEL;
7252 break;
7254 default:
7255 for (i = 0; items[i].text; i++)
7256 if (items[i].hotkey == key) {
7257 *selected = i;
7258 status = INPUT_STOP;
7259 break;
7260 }
7261 }
7262 }
7264 /* Clear the status window */
7265 status_empty = FALSE;
7266 report("");
7268 return status != INPUT_CANCEL;
7269 }
7271 /*
7272 * Repository properties
7273 */
7275 static struct ref **refs = NULL;
7276 static size_t refs_size = 0;
7277 static struct ref *refs_head = NULL;
7279 static struct ref_list **ref_lists = NULL;
7280 static size_t ref_lists_size = 0;
7282 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7283 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7284 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7286 static int
7287 compare_refs(const void *ref1_, const void *ref2_)
7288 {
7289 const struct ref *ref1 = *(const struct ref **)ref1_;
7290 const struct ref *ref2 = *(const struct ref **)ref2_;
7292 if (ref1->tag != ref2->tag)
7293 return ref2->tag - ref1->tag;
7294 if (ref1->ltag != ref2->ltag)
7295 return ref2->ltag - ref2->ltag;
7296 if (ref1->head != ref2->head)
7297 return ref2->head - ref1->head;
7298 if (ref1->tracked != ref2->tracked)
7299 return ref2->tracked - ref1->tracked;
7300 if (ref1->remote != ref2->remote)
7301 return ref2->remote - ref1->remote;
7302 return strcmp(ref1->name, ref2->name);
7303 }
7305 static void
7306 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7307 {
7308 size_t i;
7310 for (i = 0; i < refs_size; i++)
7311 if (!visitor(data, refs[i]))
7312 break;
7313 }
7315 static struct ref *
7316 get_ref_head()
7317 {
7318 return refs_head;
7319 }
7321 static struct ref_list *
7322 get_ref_list(const char *id)
7323 {
7324 struct ref_list *list;
7325 size_t i;
7327 for (i = 0; i < ref_lists_size; i++)
7328 if (!strcmp(id, ref_lists[i]->id))
7329 return ref_lists[i];
7331 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7332 return NULL;
7333 list = calloc(1, sizeof(*list));
7334 if (!list)
7335 return NULL;
7337 for (i = 0; i < refs_size; i++) {
7338 if (!strcmp(id, refs[i]->id) &&
7339 realloc_refs_list(&list->refs, list->size, 1))
7340 list->refs[list->size++] = refs[i];
7341 }
7343 if (!list->refs) {
7344 free(list);
7345 return NULL;
7346 }
7348 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7349 ref_lists[ref_lists_size++] = list;
7350 return list;
7351 }
7353 static int
7354 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7355 {
7356 struct ref *ref = NULL;
7357 bool tag = FALSE;
7358 bool ltag = FALSE;
7359 bool remote = FALSE;
7360 bool tracked = FALSE;
7361 bool head = FALSE;
7362 int from = 0, to = refs_size - 1;
7364 if (!prefixcmp(name, "refs/tags/")) {
7365 if (!suffixcmp(name, namelen, "^{}")) {
7366 namelen -= 3;
7367 name[namelen] = 0;
7368 } else {
7369 ltag = TRUE;
7370 }
7372 tag = TRUE;
7373 namelen -= STRING_SIZE("refs/tags/");
7374 name += STRING_SIZE("refs/tags/");
7376 } else if (!prefixcmp(name, "refs/remotes/")) {
7377 remote = TRUE;
7378 namelen -= STRING_SIZE("refs/remotes/");
7379 name += STRING_SIZE("refs/remotes/");
7380 tracked = !strcmp(opt_remote, name);
7382 } else if (!prefixcmp(name, "refs/heads/")) {
7383 namelen -= STRING_SIZE("refs/heads/");
7384 name += STRING_SIZE("refs/heads/");
7385 if (!strncmp(opt_head, name, namelen))
7386 return OK;
7388 } else if (!strcmp(name, "HEAD")) {
7389 head = TRUE;
7390 if (*opt_head) {
7391 namelen = strlen(opt_head);
7392 name = opt_head;
7393 }
7394 }
7396 /* If we are reloading or it's an annotated tag, replace the
7397 * previous SHA1 with the resolved commit id; relies on the fact
7398 * git-ls-remote lists the commit id of an annotated tag right
7399 * before the commit id it points to. */
7400 while (from <= to) {
7401 size_t pos = (to + from) / 2;
7402 int cmp = strcmp(name, refs[pos]->name);
7404 if (!cmp) {
7405 ref = refs[pos];
7406 break;
7407 }
7409 if (cmp < 0)
7410 to = pos - 1;
7411 else
7412 from = pos + 1;
7413 }
7415 if (!ref) {
7416 if (!realloc_refs(&refs, refs_size, 1))
7417 return ERR;
7418 ref = calloc(1, sizeof(*ref) + namelen);
7419 if (!ref)
7420 return ERR;
7421 memmove(refs + from + 1, refs + from,
7422 (refs_size - from) * sizeof(*refs));
7423 refs[from] = ref;
7424 strncpy(ref->name, name, namelen);
7425 refs_size++;
7426 }
7428 ref->head = head;
7429 ref->tag = tag;
7430 ref->ltag = ltag;
7431 ref->remote = remote;
7432 ref->tracked = tracked;
7433 string_copy_rev(ref->id, id);
7435 if (head)
7436 refs_head = ref;
7437 return OK;
7438 }
7440 static int
7441 load_refs(void)
7442 {
7443 const char *head_argv[] = {
7444 "git", "symbolic-ref", "HEAD", NULL
7445 };
7446 static const char *ls_remote_argv[SIZEOF_ARG] = {
7447 "git", "ls-remote", opt_git_dir, NULL
7448 };
7449 static bool init = FALSE;
7450 size_t i;
7452 if (!init) {
7453 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7454 die("TIG_LS_REMOTE contains too many arguments");
7455 init = TRUE;
7456 }
7458 if (!*opt_git_dir)
7459 return OK;
7461 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7462 !prefixcmp(opt_head, "refs/heads/")) {
7463 char *offset = opt_head + STRING_SIZE("refs/heads/");
7465 memmove(opt_head, offset, strlen(offset) + 1);
7466 }
7468 refs_head = NULL;
7469 for (i = 0; i < refs_size; i++)
7470 refs[i]->id[0] = 0;
7472 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7473 return ERR;
7475 /* Update the ref lists to reflect changes. */
7476 for (i = 0; i < ref_lists_size; i++) {
7477 struct ref_list *list = ref_lists[i];
7478 size_t old, new;
7480 for (old = new = 0; old < list->size; old++)
7481 if (!strcmp(list->id, list->refs[old]->id))
7482 list->refs[new++] = list->refs[old];
7483 list->size = new;
7484 }
7486 return OK;
7487 }
7489 static void
7490 set_remote_branch(const char *name, const char *value, size_t valuelen)
7491 {
7492 if (!strcmp(name, ".remote")) {
7493 string_ncopy(opt_remote, value, valuelen);
7495 } else if (*opt_remote && !strcmp(name, ".merge")) {
7496 size_t from = strlen(opt_remote);
7498 if (!prefixcmp(value, "refs/heads/"))
7499 value += STRING_SIZE("refs/heads/");
7501 if (!string_format_from(opt_remote, &from, "/%s", value))
7502 opt_remote[0] = 0;
7503 }
7504 }
7506 static void
7507 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7508 {
7509 const char *argv[SIZEOF_ARG] = { name, "=" };
7510 int argc = 1 + (cmd == option_set_command);
7511 int error = ERR;
7513 if (!argv_from_string(argv, &argc, value))
7514 config_msg = "Too many option arguments";
7515 else
7516 error = cmd(argc, argv);
7518 if (error == ERR)
7519 warn("Option 'tig.%s': %s", name, config_msg);
7520 }
7522 static bool
7523 set_environment_variable(const char *name, const char *value)
7524 {
7525 size_t len = strlen(name) + 1 + strlen(value) + 1;
7526 char *env = malloc(len);
7528 if (env &&
7529 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7530 putenv(env) == 0)
7531 return TRUE;
7532 free(env);
7533 return FALSE;
7534 }
7536 static void
7537 set_work_tree(const char *value)
7538 {
7539 char cwd[SIZEOF_STR];
7541 if (!getcwd(cwd, sizeof(cwd)))
7542 die("Failed to get cwd path: %s", strerror(errno));
7543 if (chdir(opt_git_dir) < 0)
7544 die("Failed to chdir(%s): %s", strerror(errno));
7545 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7546 die("Failed to get git path: %s", strerror(errno));
7547 if (chdir(cwd) < 0)
7548 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7549 if (chdir(value) < 0)
7550 die("Failed to chdir(%s): %s", value, strerror(errno));
7551 if (!getcwd(cwd, sizeof(cwd)))
7552 die("Failed to get cwd path: %s", strerror(errno));
7553 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7554 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7555 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7556 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7557 opt_is_inside_work_tree = TRUE;
7558 }
7560 static int
7561 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7562 {
7563 if (!strcmp(name, "i18n.commitencoding"))
7564 string_ncopy(opt_encoding, value, valuelen);
7566 else if (!strcmp(name, "core.editor"))
7567 string_ncopy(opt_editor, value, valuelen);
7569 else if (!strcmp(name, "core.worktree"))
7570 set_work_tree(value);
7572 else if (!prefixcmp(name, "tig.color."))
7573 set_repo_config_option(name + 10, value, option_color_command);
7575 else if (!prefixcmp(name, "tig.bind."))
7576 set_repo_config_option(name + 9, value, option_bind_command);
7578 else if (!prefixcmp(name, "tig."))
7579 set_repo_config_option(name + 4, value, option_set_command);
7581 else if (*opt_head && !prefixcmp(name, "branch.") &&
7582 !strncmp(name + 7, opt_head, strlen(opt_head)))
7583 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7585 return OK;
7586 }
7588 static int
7589 load_git_config(void)
7590 {
7591 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7593 return io_run_load(config_list_argv, "=", read_repo_config_option);
7594 }
7596 static int
7597 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7598 {
7599 if (!opt_git_dir[0]) {
7600 string_ncopy(opt_git_dir, name, namelen);
7602 } else if (opt_is_inside_work_tree == -1) {
7603 /* This can be 3 different values depending on the
7604 * version of git being used. If git-rev-parse does not
7605 * understand --is-inside-work-tree it will simply echo
7606 * the option else either "true" or "false" is printed.
7607 * Default to true for the unknown case. */
7608 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7610 } else if (*name == '.') {
7611 string_ncopy(opt_cdup, name, namelen);
7613 } else {
7614 string_ncopy(opt_prefix, name, namelen);
7615 }
7617 return OK;
7618 }
7620 static int
7621 load_repo_info(void)
7622 {
7623 const char *rev_parse_argv[] = {
7624 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7625 "--show-cdup", "--show-prefix", NULL
7626 };
7628 return io_run_load(rev_parse_argv, "=", read_repo_info);
7629 }
7632 /*
7633 * Main
7634 */
7636 static const char usage[] =
7637 "tig " TIG_VERSION " (" __DATE__ ")\n"
7638 "\n"
7639 "Usage: tig [options] [revs] [--] [paths]\n"
7640 " or: tig show [options] [revs] [--] [paths]\n"
7641 " or: tig blame [rev] path\n"
7642 " or: tig status\n"
7643 " or: tig < [git command output]\n"
7644 "\n"
7645 "Options:\n"
7646 " -v, --version Show version and exit\n"
7647 " -h, --help Show help message and exit";
7649 static void __NORETURN
7650 quit(int sig)
7651 {
7652 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7653 if (cursed)
7654 endwin();
7655 exit(0);
7656 }
7658 static void __NORETURN
7659 die(const char *err, ...)
7660 {
7661 va_list args;
7663 endwin();
7665 va_start(args, err);
7666 fputs("tig: ", stderr);
7667 vfprintf(stderr, err, args);
7668 fputs("\n", stderr);
7669 va_end(args);
7671 exit(1);
7672 }
7674 static void
7675 warn(const char *msg, ...)
7676 {
7677 va_list args;
7679 va_start(args, msg);
7680 fputs("tig warning: ", stderr);
7681 vfprintf(stderr, msg, args);
7682 fputs("\n", stderr);
7683 va_end(args);
7684 }
7686 static enum request
7687 parse_options(int argc, const char *argv[])
7688 {
7689 enum request request = REQ_VIEW_MAIN;
7690 const char *subcommand;
7691 bool seen_dashdash = FALSE;
7692 /* XXX: This is vulnerable to the user overriding options
7693 * required for the main view parser. */
7694 const char *custom_argv[SIZEOF_ARG] = {
7695 "git", "log", "--no-color", "--pretty=raw", "--parents",
7696 "--topo-order", NULL
7697 };
7698 int i, j = 6;
7700 if (!isatty(STDIN_FILENO)) {
7701 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7702 return REQ_VIEW_PAGER;
7703 }
7705 if (argc <= 1)
7706 return REQ_NONE;
7708 subcommand = argv[1];
7709 if (!strcmp(subcommand, "status")) {
7710 if (argc > 2)
7711 warn("ignoring arguments after `%s'", subcommand);
7712 return REQ_VIEW_STATUS;
7714 } else if (!strcmp(subcommand, "blame")) {
7715 if (argc <= 2 || argc > 4)
7716 die("invalid number of options to blame\n\n%s", usage);
7718 i = 2;
7719 if (argc == 4) {
7720 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7721 i++;
7722 }
7724 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7725 return REQ_VIEW_BLAME;
7727 } else if (!strcmp(subcommand, "show")) {
7728 request = REQ_VIEW_DIFF;
7730 } else {
7731 subcommand = NULL;
7732 }
7734 if (subcommand) {
7735 custom_argv[1] = subcommand;
7736 j = 2;
7737 }
7739 for (i = 1 + !!subcommand; i < argc; i++) {
7740 const char *opt = argv[i];
7742 if (seen_dashdash || !strcmp(opt, "--")) {
7743 seen_dashdash = TRUE;
7745 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7746 printf("tig version %s\n", TIG_VERSION);
7747 quit(0);
7749 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7750 printf("%s\n", usage);
7751 quit(0);
7752 }
7754 custom_argv[j++] = opt;
7755 if (j >= ARRAY_SIZE(custom_argv))
7756 die("command too long");
7757 }
7759 if (!prepare_update(VIEW(request), custom_argv, NULL))
7760 die("Failed to format arguments");
7762 return request;
7763 }
7765 int
7766 main(int argc, const char *argv[])
7767 {
7768 const char *codeset = "UTF-8";
7769 enum request request = parse_options(argc, argv);
7770 struct view *view;
7771 size_t i;
7773 signal(SIGINT, quit);
7774 signal(SIGPIPE, SIG_IGN);
7776 if (setlocale(LC_ALL, "")) {
7777 codeset = nl_langinfo(CODESET);
7778 }
7780 if (load_repo_info() == ERR)
7781 die("Failed to load repo info.");
7783 if (load_options() == ERR)
7784 die("Failed to load user config.");
7786 if (load_git_config() == ERR)
7787 die("Failed to load repo config.");
7789 /* Require a git repository unless when running in pager mode. */
7790 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7791 die("Not a git repository");
7793 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7794 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7795 if (opt_iconv_in == ICONV_NONE)
7796 die("Failed to initialize character set conversion");
7797 }
7799 if (codeset && strcmp(codeset, "UTF-8")) {
7800 opt_iconv_out = iconv_open(codeset, "UTF-8");
7801 if (opt_iconv_out == ICONV_NONE)
7802 die("Failed to initialize character set conversion");
7803 }
7805 if (load_refs() == ERR)
7806 die("Failed to load refs.");
7808 foreach_view (view, i)
7809 if (!argv_from_env(view->ops->argv, view->cmd_env))
7810 die("Too many arguments in the `%s` environment variable",
7811 view->cmd_env);
7813 init_display();
7815 if (request != REQ_NONE)
7816 open_view(NULL, request, OPEN_PREPARED);
7817 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7819 while (view_driver(display[current_view], request)) {
7820 int key = get_input(0);
7822 view = display[current_view];
7823 request = get_keybinding(view->keymap, key);
7825 /* Some low-level request handling. This keeps access to
7826 * status_win restricted. */
7827 switch (request) {
7828 case REQ_NONE:
7829 report("Unknown key, press %s for help",
7830 get_key(view->keymap, REQ_VIEW_HELP));
7831 break;
7832 case REQ_PROMPT:
7833 {
7834 char *cmd = read_prompt(":");
7836 if (cmd && isdigit(*cmd)) {
7837 int lineno = view->lineno + 1;
7839 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7840 select_view_line(view, lineno - 1);
7841 report("");
7842 } else {
7843 report("Unable to parse '%s' as a line number", cmd);
7844 }
7846 } else if (cmd) {
7847 struct view *next = VIEW(REQ_VIEW_PAGER);
7848 const char *argv[SIZEOF_ARG] = { "git" };
7849 int argc = 1;
7851 /* When running random commands, initially show the
7852 * command in the title. However, it maybe later be
7853 * overwritten if a commit line is selected. */
7854 string_ncopy(next->ref, cmd, strlen(cmd));
7856 if (!argv_from_string(argv, &argc, cmd)) {
7857 report("Too many arguments");
7858 } else if (!prepare_update(next, argv, NULL)) {
7859 report("Failed to format command");
7860 } else {
7861 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7862 }
7863 }
7865 request = REQ_NONE;
7866 break;
7867 }
7868 case REQ_SEARCH:
7869 case REQ_SEARCH_BACK:
7870 {
7871 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7872 char *search = read_prompt(prompt);
7874 if (search)
7875 string_ncopy(opt_search, search, strlen(search));
7876 else if (*opt_search)
7877 request = request == REQ_SEARCH ?
7878 REQ_FIND_NEXT :
7879 REQ_FIND_PREV;
7880 else
7881 request = REQ_NONE;
7882 break;
7883 }
7884 default:
7885 break;
7886 }
7887 }
7889 quit(0);
7891 return 0;
7892 }