argv: refactor argv_from_env to return an error state
[tig.git] / tig.c
blob2c353d51ff075ef9e2d72519273243fc7aa2beae
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
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.
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.
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. */
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. */
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164 int hotkey;
165 const char *text;
166 void *data;
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181 type *tmp = *mem; \
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185 if (tmp) \
186 *mem = tmp; \
189 return tmp; \
193 * String helpers
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223 size_t size, pos;
225 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226 if (src[pos] == '\t') {
227 size_t expanded = tabsize - (size % tabsize);
229 if (expanded + size >= dstlen - 1)
230 expanded = dstlen - size - 1;
231 memcpy(dst + size, " ", expanded);
232 size += expanded;
233 } else {
234 dst[size++] = src[pos];
238 dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244 int namelen;
246 while (isspace(*name))
247 name++;
249 namelen = strlen(name) - 1;
250 while (namelen > 0 && isspace(name[namelen]))
251 name[namelen--] = 0;
253 return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259 va_list args;
260 size_t pos = bufpos ? *bufpos : 0;
262 va_start(args, fmt);
263 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264 va_end(args);
266 if (bufpos)
267 *bufpos = pos;
269 return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281 size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i = 0; i < len; i++) {
287 if (toupper(str1[i]) == toupper(str2[i]))
288 continue;
290 if (string_enum_sep(str1[i]) &&
291 string_enum_sep(str2[i]))
292 continue;
294 return str1[i] - str2[i];
297 return 0;
300 #define enum_equals(entry, str, len) \
301 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304 const char *name;
305 int namelen;
306 int value;
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314 static char buf[SIZEOF_STR];
315 int bufpos;
317 for (bufpos = 0; bufpos <= namelen; bufpos++) {
318 buf[bufpos] = tolower(name[bufpos]);
319 if (buf[bufpos] == '_')
320 buf[bufpos] = '-';
323 buf[bufpos] = 0;
324 return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332 size_t namelen = strlen(name);
333 int i;
335 for (i = 0; i < map_size; i++)
336 if (enum_equals(map[i], name, namelen)) {
337 *value = map[i].value;
338 return TRUE;
341 return FALSE;
344 #define map_enum(attr, map, name) \
345 map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348 strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353 size_t len = slen >= 0 ? slen : strlen(str);
354 size_t suffixlen = strlen(suffix);
356 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
361 * Unicode / UTF-8 handling
363 * NOTE: Much of the following code for dealing with Unicode is derived from
364 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
365 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
368 static inline int
369 unicode_width(unsigned long c, int tab_size)
371 if (c >= 0x1100 &&
372 (c <= 0x115f /* Hangul Jamo */
373 || c == 0x2329
374 || c == 0x232a
375 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
376 /* CJK ... Yi */
377 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
378 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
379 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
380 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
381 || (c >= 0xffe0 && c <= 0xffe6)
382 || (c >= 0x20000 && c <= 0x2fffd)
383 || (c >= 0x30000 && c <= 0x3fffd)))
384 return 2;
386 if (c == '\t')
387 return tab_size;
389 return 1;
392 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
393 * Illegal bytes are set one. */
394 static const unsigned char utf8_bytes[256] = {
395 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400 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,
401 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,
402 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,
405 static inline unsigned char
406 utf8_char_length(const char *string, const char *end)
408 int c = *(unsigned char *) string;
410 return utf8_bytes[c];
413 /* Decode UTF-8 multi-byte representation into a Unicode character. */
414 static inline unsigned long
415 utf8_to_unicode(const char *string, size_t length)
417 unsigned long unicode;
419 switch (length) {
420 case 1:
421 unicode = string[0];
422 break;
423 case 2:
424 unicode = (string[0] & 0x1f) << 6;
425 unicode += (string[1] & 0x3f);
426 break;
427 case 3:
428 unicode = (string[0] & 0x0f) << 12;
429 unicode += ((string[1] & 0x3f) << 6);
430 unicode += (string[2] & 0x3f);
431 break;
432 case 4:
433 unicode = (string[0] & 0x0f) << 18;
434 unicode += ((string[1] & 0x3f) << 12);
435 unicode += ((string[2] & 0x3f) << 6);
436 unicode += (string[3] & 0x3f);
437 break;
438 case 5:
439 unicode = (string[0] & 0x0f) << 24;
440 unicode += ((string[1] & 0x3f) << 18);
441 unicode += ((string[2] & 0x3f) << 12);
442 unicode += ((string[3] & 0x3f) << 6);
443 unicode += (string[4] & 0x3f);
444 break;
445 case 6:
446 unicode = (string[0] & 0x01) << 30;
447 unicode += ((string[1] & 0x3f) << 24);
448 unicode += ((string[2] & 0x3f) << 18);
449 unicode += ((string[3] & 0x3f) << 12);
450 unicode += ((string[4] & 0x3f) << 6);
451 unicode += (string[5] & 0x3f);
452 break;
453 default:
454 return 0;
457 /* Invalid characters could return the special 0xfffd value but NUL
458 * should be just as good. */
459 return unicode > 0xffff ? 0 : unicode;
462 /* Calculates how much of string can be shown within the given maximum width
463 * and sets trimmed parameter to non-zero value if all of string could not be
464 * shown. If the reserve flag is TRUE, it will reserve at least one
465 * trailing character, which can be useful when drawing a delimiter.
467 * Returns the number of bytes to output from string to satisfy max_width. */
468 static size_t
469 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
471 const char *string = *start;
472 const char *end = strchr(string, '\0');
473 unsigned char last_bytes = 0;
474 size_t last_ucwidth = 0;
476 *width = 0;
477 *trimmed = 0;
479 while (string < end) {
480 unsigned char bytes = utf8_char_length(string, end);
481 size_t ucwidth;
482 unsigned long unicode;
484 if (string + bytes > end)
485 break;
487 /* Change representation to figure out whether
488 * it is a single- or double-width character. */
490 unicode = utf8_to_unicode(string, bytes);
491 /* FIXME: Graceful handling of invalid Unicode character. */
492 if (!unicode)
493 break;
495 ucwidth = unicode_width(unicode, tab_size);
496 if (skip > 0) {
497 skip -= ucwidth <= skip ? ucwidth : skip;
498 *start += bytes;
500 *width += ucwidth;
501 if (*width > max_width) {
502 *trimmed = 1;
503 *width -= ucwidth;
504 if (reserve && *width == max_width) {
505 string -= last_bytes;
506 *width -= last_ucwidth;
508 break;
511 string += bytes;
512 last_bytes = ucwidth ? bytes : 0;
513 last_ucwidth = ucwidth;
516 return string - *start;
520 #define DATE_INFO \
521 DATE_(NO), \
522 DATE_(DEFAULT), \
523 DATE_(RELATIVE), \
524 DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528 DATE_INFO
529 #undef DATE_
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534 DATE_INFO
535 #undef DATE_
538 struct time {
539 time_t sec;
540 int tz;
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545 return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551 static char buf[DATE_COLS + 1];
552 static const struct enum_map reldate[] = {
553 { "second", 1, 60 * 2 },
554 { "minute", 60, 60 * 60 * 2 },
555 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
556 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
557 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
558 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
560 struct tm tm;
562 if (!date || !time || !time->sec)
563 return "";
565 if (date == DATE_RELATIVE) {
566 struct timeval now;
567 time_t date = time->sec + time->tz;
568 time_t seconds;
569 int i;
571 gettimeofday(&now, NULL);
572 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574 if (seconds >= reldate[i].value)
575 continue;
577 seconds /= reldate[i].namelen;
578 if (!string_format(buf, "%ld %s%s %s",
579 seconds, reldate[i].name,
580 seconds > 1 ? "s" : "",
581 now.tv_sec >= date ? "ago" : "ahead"))
582 break;
583 return buf;
587 gmtime_r(&time->sec, &tm);
588 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593 AUTHOR_(NO), \
594 AUTHOR_(FULL), \
595 AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599 AUTHOR_VALUES,
600 #undef AUTHOR_
601 AUTHOR_DEFAULT = AUTHOR_FULL
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606 AUTHOR_VALUES
607 #undef AUTHOR_
610 static const char *
611 get_author_initials(const char *author)
613 static char initials[AUTHOR_COLS * 6 + 1];
614 size_t pos = 0;
615 const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619 memset(initials, 0, sizeof(initials));
620 while (author < end) {
621 unsigned char bytes;
622 size_t i;
624 while (is_initial_sep(*author))
625 author++;
627 bytes = utf8_char_length(author, end);
628 if (bytes < sizeof(initials) - 1 - pos) {
629 while (bytes--) {
630 initials[pos++] = *author++;
634 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635 if (i < sizeof(initials) - 1)
636 initials[i++] = *author;
639 initials[i++] = 0;
642 return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649 int valuelen;
651 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652 bool advance = cmd[valuelen] != 0;
654 cmd[valuelen] = 0;
655 argv[(*argc)++] = chomp_string(cmd);
656 cmd = chomp_string(cmd + valuelen + advance);
659 if (*argc < SIZEOF_ARG)
660 argv[*argc] = NULL;
661 return *argc < SIZEOF_ARG;
664 static bool
665 argv_from_env(const char **argv, const char *name)
667 char *env = argv ? getenv(name) : NULL;
668 int argc = 0;
670 if (env && *env)
671 env = strdup(env);
672 return !env || argv_from_string(argv, &argc, env);
677 * Executing external commands.
680 enum io_type {
681 IO_FD, /* File descriptor based IO. */
682 IO_BG, /* Execute command in the background. */
683 IO_FG, /* Execute command with same std{in,out,err}. */
684 IO_RD, /* Read only fork+exec IO. */
685 IO_WR, /* Write only fork+exec IO. */
686 IO_AP, /* Append fork+exec output to file. */
689 struct io {
690 enum io_type type; /* The requested type of pipe. */
691 const char *dir; /* Directory from which to execute. */
692 pid_t pid; /* PID of spawned process. */
693 int pipe; /* Pipe end for reading or writing. */
694 int error; /* Error status. */
695 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
696 char *buf; /* Read buffer. */
697 size_t bufalloc; /* Allocated buffer size. */
698 size_t bufsize; /* Buffer content size. */
699 char *bufpos; /* Current buffer position. */
700 unsigned int eof:1; /* Has end of file been reached. */
703 static void
704 io_reset(struct io *io)
706 io->pipe = -1;
707 io->pid = 0;
708 io->buf = io->bufpos = NULL;
709 io->bufalloc = io->bufsize = 0;
710 io->error = 0;
711 io->eof = 0;
714 static void
715 io_init(struct io *io, const char *dir, enum io_type type)
717 io_reset(io);
718 io->type = type;
719 io->dir = dir;
722 static bool
723 io_format(struct io *io, const char *dir, enum io_type type,
724 const char *argv[], enum format_flags flags)
726 io_init(io, dir, type);
727 return format_argv(io->argv, argv, flags);
730 static bool
731 io_open(struct io *io, const char *fmt, ...)
733 char name[SIZEOF_STR] = "";
734 bool fits;
735 va_list args;
737 io_init(io, NULL, IO_FD);
739 va_start(args, fmt);
740 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
741 va_end(args);
743 if (!fits) {
744 io->error = ENAMETOOLONG;
745 return FALSE;
747 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
748 if (io->pipe == -1)
749 io->error = errno;
750 return io->pipe != -1;
753 static bool
754 io_kill(struct io *io)
756 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
759 static bool
760 io_done(struct io *io)
762 pid_t pid = io->pid;
764 if (io->pipe != -1)
765 close(io->pipe);
766 free(io->buf);
767 io_reset(io);
769 while (pid > 0) {
770 int status;
771 pid_t waiting = waitpid(pid, &status, 0);
773 if (waiting < 0) {
774 if (errno == EINTR)
775 continue;
776 io->error = errno;
777 return FALSE;
780 return waiting == pid &&
781 !WIFSIGNALED(status) &&
782 WIFEXITED(status) &&
783 !WEXITSTATUS(status);
786 return TRUE;
789 static bool
790 io_start(struct io *io)
792 int pipefds[2] = { -1, -1 };
794 if (io->type == IO_FD)
795 return TRUE;
797 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
798 io->error = errno;
799 return FALSE;
800 } else if (io->type == IO_AP) {
801 pipefds[1] = io->pipe;
804 if ((io->pid = fork())) {
805 if (io->pid == -1)
806 io->error = errno;
807 if (pipefds[!(io->type == IO_WR)] != -1)
808 close(pipefds[!(io->type == IO_WR)]);
809 if (io->pid != -1) {
810 io->pipe = pipefds[!!(io->type == IO_WR)];
811 return TRUE;
814 } else {
815 if (io->type != IO_FG) {
816 int devnull = open("/dev/null", O_RDWR);
817 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
818 int writefd = (io->type == IO_RD || io->type == IO_AP)
819 ? pipefds[1] : devnull;
821 dup2(readfd, STDIN_FILENO);
822 dup2(writefd, STDOUT_FILENO);
823 dup2(devnull, STDERR_FILENO);
825 close(devnull);
826 if (pipefds[0] != -1)
827 close(pipefds[0]);
828 if (pipefds[1] != -1)
829 close(pipefds[1]);
832 if (io->dir && *io->dir && chdir(io->dir) == -1)
833 exit(errno);
835 execvp(io->argv[0], (char *const*) io->argv);
836 exit(errno);
839 if (pipefds[!!(io->type == IO_WR)] != -1)
840 close(pipefds[!!(io->type == IO_WR)]);
841 return FALSE;
844 static bool
845 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
847 io_init(io, dir, type);
848 if (!format_argv(io->argv, argv, FORMAT_NONE))
849 return FALSE;
850 return io_start(io);
853 static int
854 io_complete(struct io *io)
856 return io_start(io) && io_done(io);
859 static int
860 io_run_bg(const char **argv)
862 struct io io = {};
864 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
865 return FALSE;
866 return io_complete(&io);
869 static bool
870 io_run_fg(const char **argv, const char *dir)
872 struct io io = {};
874 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
875 return FALSE;
876 return io_complete(&io);
879 static bool
880 io_run_append(const char **argv, enum format_flags flags, int fd)
882 struct io io = {};
884 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
885 close(fd);
886 return FALSE;
889 io.pipe = fd;
890 return io_complete(&io);
893 static bool
894 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
896 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
899 static bool
900 io_eof(struct io *io)
902 return io->eof;
905 static int
906 io_error(struct io *io)
908 return io->error;
911 static char *
912 io_strerror(struct io *io)
914 return strerror(io->error);
917 static bool
918 io_can_read(struct io *io)
920 struct timeval tv = { 0, 500 };
921 fd_set fds;
923 FD_ZERO(&fds);
924 FD_SET(io->pipe, &fds);
926 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
929 static ssize_t
930 io_read(struct io *io, void *buf, size_t bufsize)
932 do {
933 ssize_t readsize = read(io->pipe, buf, bufsize);
935 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
936 continue;
937 else if (readsize == -1)
938 io->error = errno;
939 else if (readsize == 0)
940 io->eof = 1;
941 return readsize;
942 } while (1);
945 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
947 static char *
948 io_get(struct io *io, int c, bool can_read)
950 char *eol;
951 ssize_t readsize;
953 while (TRUE) {
954 if (io->bufsize > 0) {
955 eol = memchr(io->bufpos, c, io->bufsize);
956 if (eol) {
957 char *line = io->bufpos;
959 *eol = 0;
960 io->bufpos = eol + 1;
961 io->bufsize -= io->bufpos - line;
962 return line;
966 if (io_eof(io)) {
967 if (io->bufsize) {
968 io->bufpos[io->bufsize] = 0;
969 io->bufsize = 0;
970 return io->bufpos;
972 return NULL;
975 if (!can_read)
976 return NULL;
978 if (io->bufsize > 0 && io->bufpos > io->buf)
979 memmove(io->buf, io->bufpos, io->bufsize);
981 if (io->bufalloc == io->bufsize) {
982 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
983 return NULL;
984 io->bufalloc += BUFSIZ;
987 io->bufpos = io->buf;
988 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
989 if (io_error(io))
990 return NULL;
991 io->bufsize += readsize;
995 static bool
996 io_write(struct io *io, const void *buf, size_t bufsize)
998 size_t written = 0;
1000 while (!io_error(io) && written < bufsize) {
1001 ssize_t size;
1003 size = write(io->pipe, buf + written, bufsize - written);
1004 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1005 continue;
1006 else if (size == -1)
1007 io->error = errno;
1008 else
1009 written += size;
1012 return written == bufsize;
1015 static bool
1016 io_read_buf(struct io *io, char buf[], size_t bufsize)
1018 char *result = io_get(io, '\n', TRUE);
1020 if (result) {
1021 result = chomp_string(result);
1022 string_ncopy_do(buf, bufsize, result, strlen(result));
1025 return io_done(io) && result;
1028 static bool
1029 io_run_buf(const char **argv, char buf[], size_t bufsize)
1031 struct io io = {};
1033 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1034 && io_read_buf(&io, buf, bufsize);
1037 static int
1038 io_load(struct io *io, const char *separators,
1039 int (*read_property)(char *, size_t, char *, size_t))
1041 char *name;
1042 int state = OK;
1044 if (!io_start(io))
1045 return ERR;
1047 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1048 char *value;
1049 size_t namelen;
1050 size_t valuelen;
1052 name = chomp_string(name);
1053 namelen = strcspn(name, separators);
1055 if (name[namelen]) {
1056 name[namelen] = 0;
1057 value = chomp_string(name + namelen + 1);
1058 valuelen = strlen(value);
1060 } else {
1061 value = "";
1062 valuelen = 0;
1065 state = read_property(name, namelen, value, valuelen);
1068 if (state != ERR && io_error(io))
1069 state = ERR;
1070 io_done(io);
1072 return state;
1075 static int
1076 io_run_load(const char **argv, const char *separators,
1077 int (*read_property)(char *, size_t, char *, size_t))
1079 struct io io = {};
1081 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1082 ? io_load(&io, separators, read_property) : ERR;
1087 * User requests
1090 #define REQ_INFO \
1091 /* XXX: Keep the view request first and in sync with views[]. */ \
1092 REQ_GROUP("View switching") \
1093 REQ_(VIEW_MAIN, "Show main view"), \
1094 REQ_(VIEW_DIFF, "Show diff view"), \
1095 REQ_(VIEW_LOG, "Show log view"), \
1096 REQ_(VIEW_TREE, "Show tree view"), \
1097 REQ_(VIEW_BLOB, "Show blob view"), \
1098 REQ_(VIEW_BLAME, "Show blame view"), \
1099 REQ_(VIEW_BRANCH, "Show branch view"), \
1100 REQ_(VIEW_HELP, "Show help page"), \
1101 REQ_(VIEW_PAGER, "Show pager view"), \
1102 REQ_(VIEW_STATUS, "Show status view"), \
1103 REQ_(VIEW_STAGE, "Show stage view"), \
1105 REQ_GROUP("View manipulation") \
1106 REQ_(ENTER, "Enter current line and scroll"), \
1107 REQ_(NEXT, "Move to next"), \
1108 REQ_(PREVIOUS, "Move to previous"), \
1109 REQ_(PARENT, "Move to parent"), \
1110 REQ_(VIEW_NEXT, "Move focus to next view"), \
1111 REQ_(REFRESH, "Reload and refresh"), \
1112 REQ_(MAXIMIZE, "Maximize the current view"), \
1113 REQ_(VIEW_CLOSE, "Close the current view"), \
1114 REQ_(QUIT, "Close all views and quit"), \
1116 REQ_GROUP("View specific requests") \
1117 REQ_(STATUS_UPDATE, "Update file status"), \
1118 REQ_(STATUS_REVERT, "Revert file changes"), \
1119 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1120 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1122 REQ_GROUP("Cursor navigation") \
1123 REQ_(MOVE_UP, "Move cursor one line up"), \
1124 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1125 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1126 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1127 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1128 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1130 REQ_GROUP("Scrolling") \
1131 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1132 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1133 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1134 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1135 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1136 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1138 REQ_GROUP("Searching") \
1139 REQ_(SEARCH, "Search the view"), \
1140 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1141 REQ_(FIND_NEXT, "Find next search match"), \
1142 REQ_(FIND_PREV, "Find previous search match"), \
1144 REQ_GROUP("Option manipulation") \
1145 REQ_(OPTIONS, "Open option menu"), \
1146 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1147 REQ_(TOGGLE_DATE, "Toggle date display"), \
1148 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1149 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1150 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1151 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1152 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1153 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1155 REQ_GROUP("Misc") \
1156 REQ_(PROMPT, "Bring up the prompt"), \
1157 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1158 REQ_(SHOW_VERSION, "Show version information"), \
1159 REQ_(STOP_LOADING, "Stop all loading views"), \
1160 REQ_(EDIT, "Open in editor"), \
1161 REQ_(NONE, "Do nothing")
1164 /* User action requests. */
1165 enum request {
1166 #define REQ_GROUP(help)
1167 #define REQ_(req, help) REQ_##req
1169 /* Offset all requests to avoid conflicts with ncurses getch values. */
1170 REQ_OFFSET = KEY_MAX + 1,
1171 REQ_INFO
1173 #undef REQ_GROUP
1174 #undef REQ_
1177 struct request_info {
1178 enum request request;
1179 const char *name;
1180 int namelen;
1181 const char *help;
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187 REQ_INFO
1188 #undef REQ_GROUP
1189 #undef REQ_
1192 static enum request
1193 get_request(const char *name)
1195 int namelen = strlen(name);
1196 int i;
1198 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199 if (enum_equals(req_info[i], name, namelen))
1200 return req_info[i].request;
1202 return REQ_NONE;
1207 * Options
1210 /* Option and state variables. */
1211 static enum date opt_date = DATE_DEFAULT;
1212 static enum author opt_author = AUTHOR_DEFAULT;
1213 static bool opt_line_number = FALSE;
1214 static bool opt_line_graphics = TRUE;
1215 static bool opt_rev_graph = FALSE;
1216 static bool opt_show_refs = TRUE;
1217 static int opt_num_interval = 5;
1218 static double opt_hscroll = 0.50;
1219 static double opt_scale_split_view = 2.0 / 3.0;
1220 static int opt_tab_size = 8;
1221 static int opt_author_cols = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR] = "";
1223 static char opt_file[SIZEOF_STR] = "";
1224 static char opt_ref[SIZEOF_REF] = "";
1225 static char opt_head[SIZEOF_REF] = "";
1226 static char opt_remote[SIZEOF_REF] = "";
1227 static char opt_encoding[20] = "UTF-8";
1228 static iconv_t opt_iconv_in = ICONV_NONE;
1229 static iconv_t opt_iconv_out = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR] = "";
1231 static char opt_cdup[SIZEOF_STR] = "";
1232 static char opt_prefix[SIZEOF_STR] = "";
1233 static char opt_git_dir[SIZEOF_STR] = "";
1234 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR] = "";
1236 static FILE *opt_tty = NULL;
1238 #define is_initial_commit() (!get_ref_head())
1239 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1243 * Line-oriented content detection.
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1261 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1262 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1263 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1268 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1269 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1272 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1273 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1276 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1277 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1278 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1279 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1280 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1281 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1282 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1283 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1284 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1285 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1287 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1289 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1290 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1293 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1294 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1295 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1296 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1299 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1302 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1303 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1305 enum line_type {
1306 #define LINE(type, line, fg, bg, attr) \
1307 LINE_##type
1308 LINE_INFO,
1309 LINE_NONE
1310 #undef LINE
1313 struct line_info {
1314 const char *name; /* Option name. */
1315 int namelen; /* Size of option name. */
1316 const char *line; /* The start of line to match. */
1317 int linelen; /* Size of string to match. */
1318 int fg, bg, attr; /* Color and text attributes for the lines. */
1321 static struct line_info line_info[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1324 LINE_INFO
1325 #undef LINE
1328 static enum line_type
1329 get_line_type(const char *line)
1331 int linelen = strlen(line);
1332 enum line_type type;
1334 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1335 /* Case insensitive search matches Signed-off-by lines better. */
1336 if (linelen >= line_info[type].linelen &&
1337 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1338 return type;
1340 return LINE_DEFAULT;
1343 static inline int
1344 get_line_attr(enum line_type type)
1346 assert(type < ARRAY_SIZE(line_info));
1347 return COLOR_PAIR(type) | line_info[type].attr;
1350 static struct line_info *
1351 get_line_info(const char *name)
1353 size_t namelen = strlen(name);
1354 enum line_type type;
1356 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1357 if (enum_equals(line_info[type], name, namelen))
1358 return &line_info[type];
1360 return NULL;
1363 static void
1364 init_colors(void)
1366 int default_bg = line_info[LINE_DEFAULT].bg;
1367 int default_fg = line_info[LINE_DEFAULT].fg;
1368 enum line_type type;
1370 start_color();
1372 if (assume_default_colors(default_fg, default_bg) == ERR) {
1373 default_bg = COLOR_BLACK;
1374 default_fg = COLOR_WHITE;
1377 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1378 struct line_info *info = &line_info[type];
1379 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1380 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1382 init_pair(type, fg, bg);
1386 struct line {
1387 enum line_type type;
1389 /* State flags */
1390 unsigned int selected:1;
1391 unsigned int dirty:1;
1392 unsigned int cleareol:1;
1393 unsigned int other:16;
1395 void *data; /* User data */
1400 * Keys
1403 struct keybinding {
1404 int alias;
1405 enum request request;
1408 static const struct keybinding default_keybindings[] = {
1409 /* View switching */
1410 { 'm', REQ_VIEW_MAIN },
1411 { 'd', REQ_VIEW_DIFF },
1412 { 'l', REQ_VIEW_LOG },
1413 { 't', REQ_VIEW_TREE },
1414 { 'f', REQ_VIEW_BLOB },
1415 { 'B', REQ_VIEW_BLAME },
1416 { 'H', REQ_VIEW_BRANCH },
1417 { 'p', REQ_VIEW_PAGER },
1418 { 'h', REQ_VIEW_HELP },
1419 { 'S', REQ_VIEW_STATUS },
1420 { 'c', REQ_VIEW_STAGE },
1422 /* View manipulation */
1423 { 'q', REQ_VIEW_CLOSE },
1424 { KEY_TAB, REQ_VIEW_NEXT },
1425 { KEY_RETURN, REQ_ENTER },
1426 { KEY_UP, REQ_PREVIOUS },
1427 { KEY_DOWN, REQ_NEXT },
1428 { 'R', REQ_REFRESH },
1429 { KEY_F(5), REQ_REFRESH },
1430 { 'O', REQ_MAXIMIZE },
1432 /* Cursor navigation */
1433 { 'k', REQ_MOVE_UP },
1434 { 'j', REQ_MOVE_DOWN },
1435 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1436 { KEY_END, REQ_MOVE_LAST_LINE },
1437 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1438 { ' ', REQ_MOVE_PAGE_DOWN },
1439 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1440 { 'b', REQ_MOVE_PAGE_UP },
1441 { '-', REQ_MOVE_PAGE_UP },
1443 /* Scrolling */
1444 { KEY_LEFT, REQ_SCROLL_LEFT },
1445 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1446 { KEY_IC, REQ_SCROLL_LINE_UP },
1447 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1448 { 'w', REQ_SCROLL_PAGE_UP },
1449 { 's', REQ_SCROLL_PAGE_DOWN },
1451 /* Searching */
1452 { '/', REQ_SEARCH },
1453 { '?', REQ_SEARCH_BACK },
1454 { 'n', REQ_FIND_NEXT },
1455 { 'N', REQ_FIND_PREV },
1457 /* Misc */
1458 { 'Q', REQ_QUIT },
1459 { 'z', REQ_STOP_LOADING },
1460 { 'v', REQ_SHOW_VERSION },
1461 { 'r', REQ_SCREEN_REDRAW },
1462 { 'o', REQ_OPTIONS },
1463 { '.', REQ_TOGGLE_LINENO },
1464 { 'D', REQ_TOGGLE_DATE },
1465 { 'A', REQ_TOGGLE_AUTHOR },
1466 { 'g', REQ_TOGGLE_REV_GRAPH },
1467 { 'F', REQ_TOGGLE_REFS },
1468 { 'I', REQ_TOGGLE_SORT_ORDER },
1469 { 'i', REQ_TOGGLE_SORT_FIELD },
1470 { ':', REQ_PROMPT },
1471 { 'u', REQ_STATUS_UPDATE },
1472 { '!', REQ_STATUS_REVERT },
1473 { 'M', REQ_STATUS_MERGE },
1474 { '@', REQ_STAGE_NEXT },
1475 { ',', REQ_PARENT },
1476 { 'e', REQ_EDIT },
1479 #define KEYMAP_INFO \
1480 KEYMAP_(GENERIC), \
1481 KEYMAP_(MAIN), \
1482 KEYMAP_(DIFF), \
1483 KEYMAP_(LOG), \
1484 KEYMAP_(TREE), \
1485 KEYMAP_(BLOB), \
1486 KEYMAP_(BLAME), \
1487 KEYMAP_(BRANCH), \
1488 KEYMAP_(PAGER), \
1489 KEYMAP_(HELP), \
1490 KEYMAP_(STATUS), \
1491 KEYMAP_(STAGE)
1493 enum keymap {
1494 #define KEYMAP_(name) KEYMAP_##name
1495 KEYMAP_INFO
1496 #undef KEYMAP_
1499 static const struct enum_map keymap_table[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1501 KEYMAP_INFO
1502 #undef KEYMAP_
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table {
1508 struct keybinding *data;
1509 size_t size;
1512 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1514 static void
1515 add_keybinding(enum keymap keymap, enum request request, int key)
1517 struct keybinding_table *table = &keybindings[keymap];
1519 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1520 if (!table->data)
1521 die("Failed to allocate keybinding");
1522 table->data[table->size].alias = key;
1523 table->data[table->size++].request = request;
1526 /* Looks for a key binding first in the given map, then in the generic map, and
1527 * lastly in the default keybindings. */
1528 static enum request
1529 get_keybinding(enum keymap keymap, int key)
1531 size_t i;
1533 for (i = 0; i < keybindings[keymap].size; i++)
1534 if (keybindings[keymap].data[i].alias == key)
1535 return keybindings[keymap].data[i].request;
1537 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1538 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1539 return keybindings[KEYMAP_GENERIC].data[i].request;
1541 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542 if (default_keybindings[i].alias == key)
1543 return default_keybindings[i].request;
1545 return (enum request) key;
1549 struct key {
1550 const char *name;
1551 int value;
1554 static const struct key key_table[] = {
1555 { "Enter", KEY_RETURN },
1556 { "Space", ' ' },
1557 { "Backspace", KEY_BACKSPACE },
1558 { "Tab", KEY_TAB },
1559 { "Escape", KEY_ESC },
1560 { "Left", KEY_LEFT },
1561 { "Right", KEY_RIGHT },
1562 { "Up", KEY_UP },
1563 { "Down", KEY_DOWN },
1564 { "Insert", KEY_IC },
1565 { "Delete", KEY_DC },
1566 { "Hash", '#' },
1567 { "Home", KEY_HOME },
1568 { "End", KEY_END },
1569 { "PageUp", KEY_PPAGE },
1570 { "PageDown", KEY_NPAGE },
1571 { "F1", KEY_F(1) },
1572 { "F2", KEY_F(2) },
1573 { "F3", KEY_F(3) },
1574 { "F4", KEY_F(4) },
1575 { "F5", KEY_F(5) },
1576 { "F6", KEY_F(6) },
1577 { "F7", KEY_F(7) },
1578 { "F8", KEY_F(8) },
1579 { "F9", KEY_F(9) },
1580 { "F10", KEY_F(10) },
1581 { "F11", KEY_F(11) },
1582 { "F12", KEY_F(12) },
1585 static int
1586 get_key_value(const char *name)
1588 int i;
1590 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1591 if (!strcasecmp(key_table[i].name, name))
1592 return key_table[i].value;
1594 if (strlen(name) == 1 && isprint(*name))
1595 return (int) *name;
1597 return ERR;
1600 static const char *
1601 get_key_name(int key_value)
1603 static char key_char[] = "'X'";
1604 const char *seq = NULL;
1605 int key;
1607 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1608 if (key_table[key].value == key_value)
1609 seq = key_table[key].name;
1611 if (seq == NULL &&
1612 key_value < 127 &&
1613 isprint(key_value)) {
1614 key_char[1] = (char) key_value;
1615 seq = key_char;
1618 return seq ? seq : "(no key)";
1621 static bool
1622 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1624 const char *sep = *pos > 0 ? ", " : "";
1625 const char *keyname = get_key_name(keybinding->alias);
1627 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1630 static bool
1631 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1632 enum keymap keymap, bool all)
1634 int i;
1636 for (i = 0; i < keybindings[keymap].size; i++) {
1637 if (keybindings[keymap].data[i].request == request) {
1638 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1639 return FALSE;
1640 if (!all)
1641 break;
1645 return TRUE;
1648 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1650 static const char *
1651 get_keys(enum keymap keymap, enum request request, bool all)
1653 static char buf[BUFSIZ];
1654 size_t pos = 0;
1655 int i;
1657 buf[pos] = 0;
1659 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1660 return "Too many keybindings!";
1661 if (pos > 0 && !all)
1662 return buf;
1664 if (keymap != KEYMAP_GENERIC) {
1665 /* Only the generic keymap includes the default keybindings when
1666 * listing all keys. */
1667 if (all)
1668 return buf;
1670 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1671 return "Too many keybindings!";
1672 if (pos)
1673 return buf;
1676 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1677 if (default_keybindings[i].request == request) {
1678 if (!append_key(buf, &pos, &default_keybindings[i]))
1679 return "Too many keybindings!";
1680 if (!all)
1681 return buf;
1685 return buf;
1688 struct run_request {
1689 enum keymap keymap;
1690 int key;
1691 const char *argv[SIZEOF_ARG];
1694 static struct run_request *run_request;
1695 static size_t run_requests;
1697 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1699 static enum request
1700 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1702 struct run_request *req;
1704 if (argc >= ARRAY_SIZE(req->argv) - 1)
1705 return REQ_NONE;
1707 if (!realloc_run_requests(&run_request, run_requests, 1))
1708 return REQ_NONE;
1710 req = &run_request[run_requests];
1711 req->keymap = keymap;
1712 req->key = key;
1713 req->argv[0] = NULL;
1715 if (!format_argv(req->argv, argv, FORMAT_NONE))
1716 return REQ_NONE;
1718 return REQ_NONE + ++run_requests;
1721 static struct run_request *
1722 get_run_request(enum request request)
1724 if (request <= REQ_NONE)
1725 return NULL;
1726 return &run_request[request - REQ_NONE - 1];
1729 static void
1730 add_builtin_run_requests(void)
1732 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1733 const char *commit[] = { "git", "commit", NULL };
1734 const char *gc[] = { "git", "gc", NULL };
1735 struct {
1736 enum keymap keymap;
1737 int key;
1738 int argc;
1739 const char **argv;
1740 } reqs[] = {
1741 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1742 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1743 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1745 int i;
1747 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1748 enum request req;
1750 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1751 if (req != REQ_NONE)
1752 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1757 * User config file handling.
1760 static int config_lineno;
1761 static bool config_errors;
1762 static const char *config_msg;
1764 static const struct enum_map color_map[] = {
1765 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1766 COLOR_MAP(DEFAULT),
1767 COLOR_MAP(BLACK),
1768 COLOR_MAP(BLUE),
1769 COLOR_MAP(CYAN),
1770 COLOR_MAP(GREEN),
1771 COLOR_MAP(MAGENTA),
1772 COLOR_MAP(RED),
1773 COLOR_MAP(WHITE),
1774 COLOR_MAP(YELLOW),
1777 static const struct enum_map attr_map[] = {
1778 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1779 ATTR_MAP(NORMAL),
1780 ATTR_MAP(BLINK),
1781 ATTR_MAP(BOLD),
1782 ATTR_MAP(DIM),
1783 ATTR_MAP(REVERSE),
1784 ATTR_MAP(STANDOUT),
1785 ATTR_MAP(UNDERLINE),
1788 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1790 static int parse_step(double *opt, const char *arg)
1792 *opt = atoi(arg);
1793 if (!strchr(arg, '%'))
1794 return OK;
1796 /* "Shift down" so 100% and 1 does not conflict. */
1797 *opt = (*opt - 1) / 100;
1798 if (*opt >= 1.0) {
1799 *opt = 0.99;
1800 config_msg = "Step value larger than 100%";
1801 return ERR;
1803 if (*opt < 0.0) {
1804 *opt = 1;
1805 config_msg = "Invalid step value";
1806 return ERR;
1808 return OK;
1811 static int
1812 parse_int(int *opt, const char *arg, int min, int max)
1814 int value = atoi(arg);
1816 if (min <= value && value <= max) {
1817 *opt = value;
1818 return OK;
1821 config_msg = "Integer value out of bound";
1822 return ERR;
1825 static bool
1826 set_color(int *color, const char *name)
1828 if (map_enum(color, color_map, name))
1829 return TRUE;
1830 if (!prefixcmp(name, "color"))
1831 return parse_int(color, name + 5, 0, 255) == OK;
1832 return FALSE;
1835 /* Wants: object fgcolor bgcolor [attribute] */
1836 static int
1837 option_color_command(int argc, const char *argv[])
1839 struct line_info *info;
1841 if (argc < 3) {
1842 config_msg = "Wrong number of arguments given to color command";
1843 return ERR;
1846 info = get_line_info(argv[0]);
1847 if (!info) {
1848 static const struct enum_map obsolete[] = {
1849 ENUM_MAP("main-delim", LINE_DELIMITER),
1850 ENUM_MAP("main-date", LINE_DATE),
1851 ENUM_MAP("main-author", LINE_AUTHOR),
1853 int index;
1855 if (!map_enum(&index, obsolete, argv[0])) {
1856 config_msg = "Unknown color name";
1857 return ERR;
1859 info = &line_info[index];
1862 if (!set_color(&info->fg, argv[1]) ||
1863 !set_color(&info->bg, argv[2])) {
1864 config_msg = "Unknown color";
1865 return ERR;
1868 info->attr = 0;
1869 while (argc-- > 3) {
1870 int attr;
1872 if (!set_attribute(&attr, argv[argc])) {
1873 config_msg = "Unknown attribute";
1874 return ERR;
1876 info->attr |= attr;
1879 return OK;
1882 static int parse_bool(bool *opt, const char *arg)
1884 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1885 ? TRUE : FALSE;
1886 return OK;
1889 static int parse_enum_do(unsigned int *opt, const char *arg,
1890 const struct enum_map *map, size_t map_size)
1892 bool is_true;
1894 assert(map_size > 1);
1896 if (map_enum_do(map, map_size, (int *) opt, arg))
1897 return OK;
1899 if (parse_bool(&is_true, arg) != OK)
1900 return ERR;
1902 *opt = is_true ? map[1].value : map[0].value;
1903 return OK;
1906 #define parse_enum(opt, arg, map) \
1907 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1909 static int
1910 parse_string(char *opt, const char *arg, size_t optsize)
1912 int arglen = strlen(arg);
1914 switch (arg[0]) {
1915 case '\"':
1916 case '\'':
1917 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1918 config_msg = "Unmatched quotation";
1919 return ERR;
1921 arg += 1; arglen -= 2;
1922 default:
1923 string_ncopy_do(opt, optsize, arg, arglen);
1924 return OK;
1928 /* Wants: name = value */
1929 static int
1930 option_set_command(int argc, const char *argv[])
1932 if (argc != 3) {
1933 config_msg = "Wrong number of arguments given to set command";
1934 return ERR;
1937 if (strcmp(argv[1], "=")) {
1938 config_msg = "No value assigned";
1939 return ERR;
1942 if (!strcmp(argv[0], "show-author"))
1943 return parse_enum(&opt_author, argv[2], author_map);
1945 if (!strcmp(argv[0], "show-date"))
1946 return parse_enum(&opt_date, argv[2], date_map);
1948 if (!strcmp(argv[0], "show-rev-graph"))
1949 return parse_bool(&opt_rev_graph, argv[2]);
1951 if (!strcmp(argv[0], "show-refs"))
1952 return parse_bool(&opt_show_refs, argv[2]);
1954 if (!strcmp(argv[0], "show-line-numbers"))
1955 return parse_bool(&opt_line_number, argv[2]);
1957 if (!strcmp(argv[0], "line-graphics"))
1958 return parse_bool(&opt_line_graphics, argv[2]);
1960 if (!strcmp(argv[0], "line-number-interval"))
1961 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1963 if (!strcmp(argv[0], "author-width"))
1964 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1966 if (!strcmp(argv[0], "horizontal-scroll"))
1967 return parse_step(&opt_hscroll, argv[2]);
1969 if (!strcmp(argv[0], "split-view-height"))
1970 return parse_step(&opt_scale_split_view, argv[2]);
1972 if (!strcmp(argv[0], "tab-size"))
1973 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1975 if (!strcmp(argv[0], "commit-encoding"))
1976 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1978 config_msg = "Unknown variable name";
1979 return ERR;
1982 /* Wants: mode request key */
1983 static int
1984 option_bind_command(int argc, const char *argv[])
1986 enum request request;
1987 int keymap = -1;
1988 int key;
1990 if (argc < 3) {
1991 config_msg = "Wrong number of arguments given to bind command";
1992 return ERR;
1995 if (set_keymap(&keymap, argv[0]) == ERR) {
1996 config_msg = "Unknown key map";
1997 return ERR;
2000 key = get_key_value(argv[1]);
2001 if (key == ERR) {
2002 config_msg = "Unknown key";
2003 return ERR;
2006 request = get_request(argv[2]);
2007 if (request == REQ_NONE) {
2008 static const struct enum_map obsolete[] = {
2009 ENUM_MAP("cherry-pick", REQ_NONE),
2010 ENUM_MAP("screen-resize", REQ_NONE),
2011 ENUM_MAP("tree-parent", REQ_PARENT),
2013 int alias;
2015 if (map_enum(&alias, obsolete, argv[2])) {
2016 if (alias != REQ_NONE)
2017 add_keybinding(keymap, alias, key);
2018 config_msg = "Obsolete request name";
2019 return ERR;
2022 if (request == REQ_NONE && *argv[2]++ == '!')
2023 request = add_run_request(keymap, key, argc - 2, argv + 2);
2024 if (request == REQ_NONE) {
2025 config_msg = "Unknown request name";
2026 return ERR;
2029 add_keybinding(keymap, request, key);
2031 return OK;
2034 static int
2035 set_option(const char *opt, char *value)
2037 const char *argv[SIZEOF_ARG];
2038 int argc = 0;
2040 if (!argv_from_string(argv, &argc, value)) {
2041 config_msg = "Too many option arguments";
2042 return ERR;
2045 if (!strcmp(opt, "color"))
2046 return option_color_command(argc, argv);
2048 if (!strcmp(opt, "set"))
2049 return option_set_command(argc, argv);
2051 if (!strcmp(opt, "bind"))
2052 return option_bind_command(argc, argv);
2054 config_msg = "Unknown option command";
2055 return ERR;
2058 static int
2059 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2061 int status = OK;
2063 config_lineno++;
2064 config_msg = "Internal error";
2066 /* Check for comment markers, since read_properties() will
2067 * only ensure opt and value are split at first " \t". */
2068 optlen = strcspn(opt, "#");
2069 if (optlen == 0)
2070 return OK;
2072 if (opt[optlen] != 0) {
2073 config_msg = "No option value";
2074 status = ERR;
2076 } else {
2077 /* Look for comment endings in the value. */
2078 size_t len = strcspn(value, "#");
2080 if (len < valuelen) {
2081 valuelen = len;
2082 value[valuelen] = 0;
2085 status = set_option(opt, value);
2088 if (status == ERR) {
2089 warn("Error on line %d, near '%.*s': %s",
2090 config_lineno, (int) optlen, opt, config_msg);
2091 config_errors = TRUE;
2094 /* Always keep going if errors are encountered. */
2095 return OK;
2098 static void
2099 load_option_file(const char *path)
2101 struct io io = {};
2103 /* It's OK that the file doesn't exist. */
2104 if (!io_open(&io, "%s", path))
2105 return;
2107 config_lineno = 0;
2108 config_errors = FALSE;
2110 if (io_load(&io, " \t", read_option) == ERR ||
2111 config_errors == TRUE)
2112 warn("Errors while loading %s.", path);
2115 static int
2116 load_options(void)
2118 const char *home = getenv("HOME");
2119 const char *tigrc_user = getenv("TIGRC_USER");
2120 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2121 char buf[SIZEOF_STR];
2123 add_builtin_run_requests();
2125 if (!tigrc_system)
2126 tigrc_system = SYSCONFDIR "/tigrc";
2127 load_option_file(tigrc_system);
2129 if (!tigrc_user) {
2130 if (!home || !string_format(buf, "%s/.tigrc", home))
2131 return ERR;
2132 tigrc_user = buf;
2134 load_option_file(tigrc_user);
2136 return OK;
2141 * The viewer
2144 struct view;
2145 struct view_ops;
2147 /* The display array of active views and the index of the current view. */
2148 static struct view *display[2];
2149 static unsigned int current_view;
2151 #define foreach_displayed_view(view, i) \
2152 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2154 #define displayed_views() (display[1] != NULL ? 2 : 1)
2156 /* Current head and commit ID */
2157 static char ref_blob[SIZEOF_REF] = "";
2158 static char ref_commit[SIZEOF_REF] = "HEAD";
2159 static char ref_head[SIZEOF_REF] = "HEAD";
2161 struct view {
2162 const char *name; /* View name */
2163 const char *cmd_env; /* Command line set via environment */
2164 const char *id; /* Points to either of ref_{head,commit,blob} */
2166 struct view_ops *ops; /* View operations */
2168 enum keymap keymap; /* What keymap does this view have */
2169 bool git_dir; /* Whether the view requires a git directory. */
2171 char ref[SIZEOF_REF]; /* Hovered commit reference */
2172 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2174 int height, width; /* The width and height of the main window */
2175 WINDOW *win; /* The main window */
2176 WINDOW *title; /* The title window living below the main window */
2178 /* Navigation */
2179 unsigned long offset; /* Offset of the window top */
2180 unsigned long yoffset; /* Offset from the window side. */
2181 unsigned long lineno; /* Current line number */
2182 unsigned long p_offset; /* Previous offset of the window top */
2183 unsigned long p_yoffset;/* Previous offset from the window side */
2184 unsigned long p_lineno; /* Previous current line number */
2185 bool p_restore; /* Should the previous position be restored. */
2187 /* Searching */
2188 char grep[SIZEOF_STR]; /* Search string */
2189 regex_t *regex; /* Pre-compiled regexp */
2191 /* If non-NULL, points to the view that opened this view. If this view
2192 * is closed tig will switch back to the parent view. */
2193 struct view *parent;
2195 /* Buffering */
2196 size_t lines; /* Total number of lines */
2197 struct line *line; /* Line index */
2198 unsigned int digits; /* Number of digits in the lines member. */
2200 /* Drawing */
2201 struct line *curline; /* Line currently being drawn. */
2202 enum line_type curtype; /* Attribute currently used for drawing. */
2203 unsigned long col; /* Column when drawing. */
2204 bool has_scrolled; /* View was scrolled. */
2206 /* Loading */
2207 struct io io;
2208 struct io *pipe;
2209 time_t start_time;
2210 time_t update_secs;
2213 struct view_ops {
2214 /* What type of content being displayed. Used in the title bar. */
2215 const char *type;
2216 /* Default command arguments. */
2217 const char **argv;
2218 /* Open and reads in all view content. */
2219 bool (*open)(struct view *view);
2220 /* Read one line; updates view->line. */
2221 bool (*read)(struct view *view, char *data);
2222 /* Draw one line; @lineno must be < view->height. */
2223 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2224 /* Depending on view handle a special requests. */
2225 enum request (*request)(struct view *view, enum request request, struct line *line);
2226 /* Search for regexp in a line. */
2227 bool (*grep)(struct view *view, struct line *line);
2228 /* Select line */
2229 void (*select)(struct view *view, struct line *line);
2230 /* Prepare view for loading */
2231 bool (*prepare)(struct view *view);
2234 static struct view_ops blame_ops;
2235 static struct view_ops blob_ops;
2236 static struct view_ops diff_ops;
2237 static struct view_ops help_ops;
2238 static struct view_ops log_ops;
2239 static struct view_ops main_ops;
2240 static struct view_ops pager_ops;
2241 static struct view_ops stage_ops;
2242 static struct view_ops status_ops;
2243 static struct view_ops tree_ops;
2244 static struct view_ops branch_ops;
2246 #define VIEW_STR(name, env, ref, ops, map, git) \
2247 { name, #env, ref, ops, map, git }
2249 #define VIEW_(id, name, ops, git, ref) \
2250 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2253 static struct view views[] = {
2254 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2255 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2256 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2257 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2258 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2259 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2260 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2261 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2262 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2263 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2264 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2267 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2268 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2270 #define foreach_view(view, i) \
2271 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2273 #define view_is_displayed(view) \
2274 (view == display[0] || view == display[1])
2277 static inline void
2278 set_view_attr(struct view *view, enum line_type type)
2280 if (!view->curline->selected && view->curtype != type) {
2281 (void) wattrset(view->win, get_line_attr(type));
2282 wchgat(view->win, -1, 0, type, NULL);
2283 view->curtype = type;
2287 static int
2288 draw_chars(struct view *view, enum line_type type, const char *string,
2289 int max_len, bool use_tilde)
2291 static char out_buffer[BUFSIZ * 2];
2292 int len = 0;
2293 int col = 0;
2294 int trimmed = FALSE;
2295 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2297 if (max_len <= 0)
2298 return 0;
2300 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2302 set_view_attr(view, type);
2303 if (len > 0) {
2304 if (opt_iconv_out != ICONV_NONE) {
2305 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2306 size_t inlen = len + 1;
2308 char *outbuf = out_buffer;
2309 size_t outlen = sizeof(out_buffer);
2311 size_t ret;
2313 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2314 if (ret != (size_t) -1) {
2315 string = out_buffer;
2316 len = sizeof(out_buffer) - outlen;
2320 waddnstr(view->win, string, len);
2322 if (trimmed && use_tilde) {
2323 set_view_attr(view, LINE_DELIMITER);
2324 waddch(view->win, '~');
2325 col++;
2328 return col;
2331 static int
2332 draw_space(struct view *view, enum line_type type, int max, int spaces)
2334 static char space[] = " ";
2335 int col = 0;
2337 spaces = MIN(max, spaces);
2339 while (spaces > 0) {
2340 int len = MIN(spaces, sizeof(space) - 1);
2342 col += draw_chars(view, type, space, len, FALSE);
2343 spaces -= len;
2346 return col;
2349 static bool
2350 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2352 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2353 return view->width + view->yoffset <= view->col;
2356 static bool
2357 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2359 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2360 int max = view->width + view->yoffset - view->col;
2361 int i;
2363 if (max < size)
2364 size = max;
2366 set_view_attr(view, type);
2367 /* Using waddch() instead of waddnstr() ensures that
2368 * they'll be rendered correctly for the cursor line. */
2369 for (i = skip; i < size; i++)
2370 waddch(view->win, graphic[i]);
2372 view->col += size;
2373 if (size < max && skip <= size)
2374 waddch(view->win, ' ');
2375 view->col++;
2377 return view->width + view->yoffset <= view->col;
2380 static bool
2381 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2383 int max = MIN(view->width + view->yoffset - view->col, len);
2384 int col;
2386 if (text)
2387 col = draw_chars(view, type, text, max - 1, trim);
2388 else
2389 col = draw_space(view, type, max - 1, max - 1);
2391 view->col += col;
2392 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2393 return view->width + view->yoffset <= view->col;
2396 static bool
2397 draw_date(struct view *view, struct time *time)
2399 const char *date = mkdate(time, opt_date);
2400 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2402 return draw_field(view, LINE_DATE, date, cols, FALSE);
2405 static bool
2406 draw_author(struct view *view, const char *author)
2408 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2409 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2411 if (abbreviate && author)
2412 author = get_author_initials(author);
2414 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2417 static bool
2418 draw_mode(struct view *view, mode_t mode)
2420 const char *str;
2422 if (S_ISDIR(mode))
2423 str = "drwxr-xr-x";
2424 else if (S_ISLNK(mode))
2425 str = "lrwxrwxrwx";
2426 else if (S_ISGITLINK(mode))
2427 str = "m---------";
2428 else if (S_ISREG(mode) && mode & S_IXUSR)
2429 str = "-rwxr-xr-x";
2430 else if (S_ISREG(mode))
2431 str = "-rw-r--r--";
2432 else
2433 str = "----------";
2435 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2438 static bool
2439 draw_lineno(struct view *view, unsigned int lineno)
2441 char number[10];
2442 int digits3 = view->digits < 3 ? 3 : view->digits;
2443 int max = MIN(view->width + view->yoffset - view->col, digits3);
2444 char *text = NULL;
2445 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2447 lineno += view->offset + 1;
2448 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2449 static char fmt[] = "%1ld";
2451 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2452 if (string_format(number, fmt, lineno))
2453 text = number;
2455 if (text)
2456 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2457 else
2458 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2459 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2462 static bool
2463 draw_view_line(struct view *view, unsigned int lineno)
2465 struct line *line;
2466 bool selected = (view->offset + lineno == view->lineno);
2468 assert(view_is_displayed(view));
2470 if (view->offset + lineno >= view->lines)
2471 return FALSE;
2473 line = &view->line[view->offset + lineno];
2475 wmove(view->win, lineno, 0);
2476 if (line->cleareol)
2477 wclrtoeol(view->win);
2478 view->col = 0;
2479 view->curline = line;
2480 view->curtype = LINE_NONE;
2481 line->selected = FALSE;
2482 line->dirty = line->cleareol = 0;
2484 if (selected) {
2485 set_view_attr(view, LINE_CURSOR);
2486 line->selected = TRUE;
2487 view->ops->select(view, line);
2490 return view->ops->draw(view, line, lineno);
2493 static void
2494 redraw_view_dirty(struct view *view)
2496 bool dirty = FALSE;
2497 int lineno;
2499 for (lineno = 0; lineno < view->height; lineno++) {
2500 if (view->offset + lineno >= view->lines)
2501 break;
2502 if (!view->line[view->offset + lineno].dirty)
2503 continue;
2504 dirty = TRUE;
2505 if (!draw_view_line(view, lineno))
2506 break;
2509 if (!dirty)
2510 return;
2511 wnoutrefresh(view->win);
2514 static void
2515 redraw_view_from(struct view *view, int lineno)
2517 assert(0 <= lineno && lineno < view->height);
2519 for (; lineno < view->height; lineno++) {
2520 if (!draw_view_line(view, lineno))
2521 break;
2524 wnoutrefresh(view->win);
2527 static void
2528 redraw_view(struct view *view)
2530 werase(view->win);
2531 redraw_view_from(view, 0);
2535 static void
2536 update_view_title(struct view *view)
2538 char buf[SIZEOF_STR];
2539 char state[SIZEOF_STR];
2540 size_t bufpos = 0, statelen = 0;
2542 assert(view_is_displayed(view));
2544 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2545 unsigned int view_lines = view->offset + view->height;
2546 unsigned int lines = view->lines
2547 ? MIN(view_lines, view->lines) * 100 / view->lines
2548 : 0;
2550 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2551 view->ops->type,
2552 view->lineno + 1,
2553 view->lines,
2554 lines);
2558 if (view->pipe) {
2559 time_t secs = time(NULL) - view->start_time;
2561 /* Three git seconds are a long time ... */
2562 if (secs > 2)
2563 string_format_from(state, &statelen, " loading %lds", secs);
2566 string_format_from(buf, &bufpos, "[%s]", view->name);
2567 if (*view->ref && bufpos < view->width) {
2568 size_t refsize = strlen(view->ref);
2569 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2571 if (minsize < view->width)
2572 refsize = view->width - minsize + 7;
2573 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2576 if (statelen && bufpos < view->width) {
2577 string_format_from(buf, &bufpos, "%s", state);
2580 if (view == display[current_view])
2581 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2582 else
2583 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2585 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2586 wclrtoeol(view->title);
2587 wnoutrefresh(view->title);
2590 static int
2591 apply_step(double step, int value)
2593 if (step >= 1)
2594 return (int) step;
2595 value *= step + 0.01;
2596 return value ? value : 1;
2599 static void
2600 resize_display(void)
2602 int offset, i;
2603 struct view *base = display[0];
2604 struct view *view = display[1] ? display[1] : display[0];
2606 /* Setup window dimensions */
2608 getmaxyx(stdscr, base->height, base->width);
2610 /* Make room for the status window. */
2611 base->height -= 1;
2613 if (view != base) {
2614 /* Horizontal split. */
2615 view->width = base->width;
2616 view->height = apply_step(opt_scale_split_view, base->height);
2617 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2618 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2619 base->height -= view->height;
2621 /* Make room for the title bar. */
2622 view->height -= 1;
2625 /* Make room for the title bar. */
2626 base->height -= 1;
2628 offset = 0;
2630 foreach_displayed_view (view, i) {
2631 if (!view->win) {
2632 view->win = newwin(view->height, 0, offset, 0);
2633 if (!view->win)
2634 die("Failed to create %s view", view->name);
2636 scrollok(view->win, FALSE);
2638 view->title = newwin(1, 0, offset + view->height, 0);
2639 if (!view->title)
2640 die("Failed to create title window");
2642 } else {
2643 wresize(view->win, view->height, view->width);
2644 mvwin(view->win, offset, 0);
2645 mvwin(view->title, offset + view->height, 0);
2648 offset += view->height + 1;
2652 static void
2653 redraw_display(bool clear)
2655 struct view *view;
2656 int i;
2658 foreach_displayed_view (view, i) {
2659 if (clear)
2660 wclear(view->win);
2661 redraw_view(view);
2662 update_view_title(view);
2666 static void
2667 toggle_enum_option_do(unsigned int *opt, const char *help,
2668 const struct enum_map *map, size_t size)
2670 *opt = (*opt + 1) % size;
2671 redraw_display(FALSE);
2672 report("Displaying %s %s", enum_name(map[*opt]), help);
2675 #define toggle_enum_option(opt, help, map) \
2676 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2678 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2679 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2681 static void
2682 toggle_view_option(bool *option, const char *help)
2684 *option = !*option;
2685 redraw_display(FALSE);
2686 report("%sabling %s", *option ? "En" : "Dis", help);
2689 static void
2690 open_option_menu(void)
2692 const struct menu_item menu[] = {
2693 { '.', "line numbers", &opt_line_number },
2694 { 'D', "date display", &opt_date },
2695 { 'A', "author display", &opt_author },
2696 { 'g', "revision graph display", &opt_rev_graph },
2697 { 'F', "reference display", &opt_show_refs },
2698 { 0 }
2700 int selected = 0;
2702 if (prompt_menu("Toggle option", menu, &selected)) {
2703 if (menu[selected].data == &opt_date)
2704 toggle_date();
2705 else if (menu[selected].data == &opt_author)
2706 toggle_author();
2707 else
2708 toggle_view_option(menu[selected].data, menu[selected].text);
2712 static void
2713 maximize_view(struct view *view)
2715 memset(display, 0, sizeof(display));
2716 current_view = 0;
2717 display[current_view] = view;
2718 resize_display();
2719 redraw_display(FALSE);
2720 report("");
2725 * Navigation
2728 static bool
2729 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2731 if (lineno >= view->lines)
2732 lineno = view->lines > 0 ? view->lines - 1 : 0;
2734 if (offset > lineno || offset + view->height <= lineno) {
2735 unsigned long half = view->height / 2;
2737 if (lineno > half)
2738 offset = lineno - half;
2739 else
2740 offset = 0;
2743 if (offset != view->offset || lineno != view->lineno) {
2744 view->offset = offset;
2745 view->lineno = lineno;
2746 return TRUE;
2749 return FALSE;
2752 /* Scrolling backend */
2753 static void
2754 do_scroll_view(struct view *view, int lines)
2756 bool redraw_current_line = FALSE;
2758 /* The rendering expects the new offset. */
2759 view->offset += lines;
2761 assert(0 <= view->offset && view->offset < view->lines);
2762 assert(lines);
2764 /* Move current line into the view. */
2765 if (view->lineno < view->offset) {
2766 view->lineno = view->offset;
2767 redraw_current_line = TRUE;
2768 } else if (view->lineno >= view->offset + view->height) {
2769 view->lineno = view->offset + view->height - 1;
2770 redraw_current_line = TRUE;
2773 assert(view->offset <= view->lineno && view->lineno < view->lines);
2775 /* Redraw the whole screen if scrolling is pointless. */
2776 if (view->height < ABS(lines)) {
2777 redraw_view(view);
2779 } else {
2780 int line = lines > 0 ? view->height - lines : 0;
2781 int end = line + ABS(lines);
2783 scrollok(view->win, TRUE);
2784 wscrl(view->win, lines);
2785 scrollok(view->win, FALSE);
2787 while (line < end && draw_view_line(view, line))
2788 line++;
2790 if (redraw_current_line)
2791 draw_view_line(view, view->lineno - view->offset);
2792 wnoutrefresh(view->win);
2795 view->has_scrolled = TRUE;
2796 report("");
2799 /* Scroll frontend */
2800 static void
2801 scroll_view(struct view *view, enum request request)
2803 int lines = 1;
2805 assert(view_is_displayed(view));
2807 switch (request) {
2808 case REQ_SCROLL_LEFT:
2809 if (view->yoffset == 0) {
2810 report("Cannot scroll beyond the first column");
2811 return;
2813 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2814 view->yoffset = 0;
2815 else
2816 view->yoffset -= apply_step(opt_hscroll, view->width);
2817 redraw_view_from(view, 0);
2818 report("");
2819 return;
2820 case REQ_SCROLL_RIGHT:
2821 view->yoffset += apply_step(opt_hscroll, view->width);
2822 redraw_view(view);
2823 report("");
2824 return;
2825 case REQ_SCROLL_PAGE_DOWN:
2826 lines = view->height;
2827 case REQ_SCROLL_LINE_DOWN:
2828 if (view->offset + lines > view->lines)
2829 lines = view->lines - view->offset;
2831 if (lines == 0 || view->offset + view->height >= view->lines) {
2832 report("Cannot scroll beyond the last line");
2833 return;
2835 break;
2837 case REQ_SCROLL_PAGE_UP:
2838 lines = view->height;
2839 case REQ_SCROLL_LINE_UP:
2840 if (lines > view->offset)
2841 lines = view->offset;
2843 if (lines == 0) {
2844 report("Cannot scroll beyond the first line");
2845 return;
2848 lines = -lines;
2849 break;
2851 default:
2852 die("request %d not handled in switch", request);
2855 do_scroll_view(view, lines);
2858 /* Cursor moving */
2859 static void
2860 move_view(struct view *view, enum request request)
2862 int scroll_steps = 0;
2863 int steps;
2865 switch (request) {
2866 case REQ_MOVE_FIRST_LINE:
2867 steps = -view->lineno;
2868 break;
2870 case REQ_MOVE_LAST_LINE:
2871 steps = view->lines - view->lineno - 1;
2872 break;
2874 case REQ_MOVE_PAGE_UP:
2875 steps = view->height > view->lineno
2876 ? -view->lineno : -view->height;
2877 break;
2879 case REQ_MOVE_PAGE_DOWN:
2880 steps = view->lineno + view->height >= view->lines
2881 ? view->lines - view->lineno - 1 : view->height;
2882 break;
2884 case REQ_MOVE_UP:
2885 steps = -1;
2886 break;
2888 case REQ_MOVE_DOWN:
2889 steps = 1;
2890 break;
2892 default:
2893 die("request %d not handled in switch", request);
2896 if (steps <= 0 && view->lineno == 0) {
2897 report("Cannot move beyond the first line");
2898 return;
2900 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2901 report("Cannot move beyond the last line");
2902 return;
2905 /* Move the current line */
2906 view->lineno += steps;
2907 assert(0 <= view->lineno && view->lineno < view->lines);
2909 /* Check whether the view needs to be scrolled */
2910 if (view->lineno < view->offset ||
2911 view->lineno >= view->offset + view->height) {
2912 scroll_steps = steps;
2913 if (steps < 0 && -steps > view->offset) {
2914 scroll_steps = -view->offset;
2916 } else if (steps > 0) {
2917 if (view->lineno == view->lines - 1 &&
2918 view->lines > view->height) {
2919 scroll_steps = view->lines - view->offset - 1;
2920 if (scroll_steps >= view->height)
2921 scroll_steps -= view->height - 1;
2926 if (!view_is_displayed(view)) {
2927 view->offset += scroll_steps;
2928 assert(0 <= view->offset && view->offset < view->lines);
2929 view->ops->select(view, &view->line[view->lineno]);
2930 return;
2933 /* Repaint the old "current" line if we be scrolling */
2934 if (ABS(steps) < view->height)
2935 draw_view_line(view, view->lineno - steps - view->offset);
2937 if (scroll_steps) {
2938 do_scroll_view(view, scroll_steps);
2939 return;
2942 /* Draw the current line */
2943 draw_view_line(view, view->lineno - view->offset);
2945 wnoutrefresh(view->win);
2946 report("");
2951 * Searching
2954 static void search_view(struct view *view, enum request request);
2956 static bool
2957 grep_text(struct view *view, const char *text[])
2959 regmatch_t pmatch;
2960 size_t i;
2962 for (i = 0; text[i]; i++)
2963 if (*text[i] &&
2964 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2965 return TRUE;
2966 return FALSE;
2969 static void
2970 select_view_line(struct view *view, unsigned long lineno)
2972 unsigned long old_lineno = view->lineno;
2973 unsigned long old_offset = view->offset;
2975 if (goto_view_line(view, view->offset, lineno)) {
2976 if (view_is_displayed(view)) {
2977 if (old_offset != view->offset) {
2978 redraw_view(view);
2979 } else {
2980 draw_view_line(view, old_lineno - view->offset);
2981 draw_view_line(view, view->lineno - view->offset);
2982 wnoutrefresh(view->win);
2984 } else {
2985 view->ops->select(view, &view->line[view->lineno]);
2990 static void
2991 find_next(struct view *view, enum request request)
2993 unsigned long lineno = view->lineno;
2994 int direction;
2996 if (!*view->grep) {
2997 if (!*opt_search)
2998 report("No previous search");
2999 else
3000 search_view(view, request);
3001 return;
3004 switch (request) {
3005 case REQ_SEARCH:
3006 case REQ_FIND_NEXT:
3007 direction = 1;
3008 break;
3010 case REQ_SEARCH_BACK:
3011 case REQ_FIND_PREV:
3012 direction = -1;
3013 break;
3015 default:
3016 return;
3019 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3020 lineno += direction;
3022 /* Note, lineno is unsigned long so will wrap around in which case it
3023 * will become bigger than view->lines. */
3024 for (; lineno < view->lines; lineno += direction) {
3025 if (view->ops->grep(view, &view->line[lineno])) {
3026 select_view_line(view, lineno);
3027 report("Line %ld matches '%s'", lineno + 1, view->grep);
3028 return;
3032 report("No match found for '%s'", view->grep);
3035 static void
3036 search_view(struct view *view, enum request request)
3038 int regex_err;
3040 if (view->regex) {
3041 regfree(view->regex);
3042 *view->grep = 0;
3043 } else {
3044 view->regex = calloc(1, sizeof(*view->regex));
3045 if (!view->regex)
3046 return;
3049 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3050 if (regex_err != 0) {
3051 char buf[SIZEOF_STR] = "unknown error";
3053 regerror(regex_err, view->regex, buf, sizeof(buf));
3054 report("Search failed: %s", buf);
3055 return;
3058 string_copy(view->grep, opt_search);
3060 find_next(view, request);
3064 * Incremental updating
3067 static void
3068 reset_view(struct view *view)
3070 int i;
3072 for (i = 0; i < view->lines; i++)
3073 free(view->line[i].data);
3074 free(view->line);
3076 view->p_offset = view->offset;
3077 view->p_yoffset = view->yoffset;
3078 view->p_lineno = view->lineno;
3080 view->line = NULL;
3081 view->offset = 0;
3082 view->yoffset = 0;
3083 view->lines = 0;
3084 view->lineno = 0;
3085 view->vid[0] = 0;
3086 view->update_secs = 0;
3089 static void
3090 free_argv(const char *argv[])
3092 int argc;
3094 for (argc = 0; argv[argc]; argc++)
3095 free((void *) argv[argc]);
3098 static const char *
3099 format_arg(const char *name)
3101 static struct {
3102 const char *name;
3103 size_t namelen;
3104 const char *value;
3105 const char *value_if_empty;
3106 } vars[] = {
3107 #define FORMAT_VAR(name, value, value_if_empty) \
3108 { name, STRING_SIZE(name), value, value_if_empty }
3109 FORMAT_VAR("%(directory)", opt_path, ""),
3110 FORMAT_VAR("%(file)", opt_file, ""),
3111 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3112 FORMAT_VAR("%(head)", ref_head, ""),
3113 FORMAT_VAR("%(commit)", ref_commit, ""),
3114 FORMAT_VAR("%(blob)", ref_blob, ""),
3116 int i;
3118 for (i = 0; i < ARRAY_SIZE(vars); i++)
3119 if (!strncmp(name, vars[i].name, vars[i].namelen))
3120 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3122 return NULL;
3125 static bool
3126 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3128 char buf[SIZEOF_STR];
3129 int argc;
3130 bool noreplace = flags == FORMAT_NONE;
3132 free_argv(dst_argv);
3134 for (argc = 0; src_argv[argc]; argc++) {
3135 const char *arg = src_argv[argc];
3136 size_t bufpos = 0;
3138 while (arg) {
3139 char *next = strstr(arg, "%(");
3140 int len = next - arg;
3141 const char *value;
3143 if (!next || noreplace) {
3144 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3145 noreplace = TRUE;
3146 len = strlen(arg);
3147 value = "";
3149 } else {
3150 value = format_arg(next);
3152 if (!value) {
3153 report("Unknown replacement: `%s`", next);
3154 return FALSE;
3158 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3159 return FALSE;
3161 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3164 dst_argv[argc] = strdup(buf);
3165 if (!dst_argv[argc])
3166 break;
3169 dst_argv[argc] = NULL;
3171 return src_argv[argc] == NULL;
3174 static bool
3175 restore_view_position(struct view *view)
3177 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3178 return FALSE;
3180 /* Changing the view position cancels the restoring. */
3181 /* FIXME: Changing back to the first line is not detected. */
3182 if (view->offset != 0 || view->lineno != 0) {
3183 view->p_restore = FALSE;
3184 return FALSE;
3187 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3188 view_is_displayed(view))
3189 werase(view->win);
3191 view->yoffset = view->p_yoffset;
3192 view->p_restore = FALSE;
3194 return TRUE;
3197 static void
3198 end_update(struct view *view, bool force)
3200 if (!view->pipe)
3201 return;
3202 while (!view->ops->read(view, NULL))
3203 if (!force)
3204 return;
3205 if (force)
3206 io_kill(view->pipe);
3207 io_done(view->pipe);
3208 view->pipe = NULL;
3211 static void
3212 setup_update(struct view *view, const char *vid)
3214 reset_view(view);
3215 string_copy_rev(view->vid, vid);
3216 view->pipe = &view->io;
3217 view->start_time = time(NULL);
3220 static bool
3221 prepare_update(struct view *view, const char *argv[], const char *dir,
3222 enum format_flags flags)
3224 if (view->pipe)
3225 end_update(view, TRUE);
3226 return io_format(&view->io, dir, IO_RD, argv, flags);
3229 static bool
3230 prepare_update_file(struct view *view, const char *name)
3232 if (view->pipe)
3233 end_update(view, TRUE);
3234 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3237 static bool
3238 begin_update(struct view *view, bool refresh)
3240 if (view->pipe)
3241 end_update(view, TRUE);
3243 if (!refresh) {
3244 if (view->ops->prepare) {
3245 if (!view->ops->prepare(view))
3246 return FALSE;
3247 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3248 return FALSE;
3251 /* Put the current ref_* value to the view title ref
3252 * member. This is needed by the blob view. Most other
3253 * views sets it automatically after loading because the
3254 * first line is a commit line. */
3255 string_copy_rev(view->ref, view->id);
3258 if (!io_start(&view->io))
3259 return FALSE;
3261 setup_update(view, view->id);
3263 return TRUE;
3266 static bool
3267 update_view(struct view *view)
3269 char out_buffer[BUFSIZ * 2];
3270 char *line;
3271 /* Clear the view and redraw everything since the tree sorting
3272 * might have rearranged things. */
3273 bool redraw = view->lines == 0;
3274 bool can_read = TRUE;
3276 if (!view->pipe)
3277 return TRUE;
3279 if (!io_can_read(view->pipe)) {
3280 if (view->lines == 0 && view_is_displayed(view)) {
3281 time_t secs = time(NULL) - view->start_time;
3283 if (secs > 1 && secs > view->update_secs) {
3284 if (view->update_secs == 0)
3285 redraw_view(view);
3286 update_view_title(view);
3287 view->update_secs = secs;
3290 return TRUE;
3293 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3294 if (opt_iconv_in != ICONV_NONE) {
3295 ICONV_CONST char *inbuf = line;
3296 size_t inlen = strlen(line) + 1;
3298 char *outbuf = out_buffer;
3299 size_t outlen = sizeof(out_buffer);
3301 size_t ret;
3303 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3304 if (ret != (size_t) -1)
3305 line = out_buffer;
3308 if (!view->ops->read(view, line)) {
3309 report("Allocation failure");
3310 end_update(view, TRUE);
3311 return FALSE;
3316 unsigned long lines = view->lines;
3317 int digits;
3319 for (digits = 0; lines; digits++)
3320 lines /= 10;
3322 /* Keep the displayed view in sync with line number scaling. */
3323 if (digits != view->digits) {
3324 view->digits = digits;
3325 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3326 redraw = TRUE;
3330 if (io_error(view->pipe)) {
3331 report("Failed to read: %s", io_strerror(view->pipe));
3332 end_update(view, TRUE);
3334 } else if (io_eof(view->pipe)) {
3335 report("");
3336 end_update(view, FALSE);
3339 if (restore_view_position(view))
3340 redraw = TRUE;
3342 if (!view_is_displayed(view))
3343 return TRUE;
3345 if (redraw)
3346 redraw_view_from(view, 0);
3347 else
3348 redraw_view_dirty(view);
3350 /* Update the title _after_ the redraw so that if the redraw picks up a
3351 * commit reference in view->ref it'll be available here. */
3352 update_view_title(view);
3353 return TRUE;
3356 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3358 static struct line *
3359 add_line_data(struct view *view, void *data, enum line_type type)
3361 struct line *line;
3363 if (!realloc_lines(&view->line, view->lines, 1))
3364 return NULL;
3366 line = &view->line[view->lines++];
3367 memset(line, 0, sizeof(*line));
3368 line->type = type;
3369 line->data = data;
3370 line->dirty = 1;
3372 return line;
3375 static struct line *
3376 add_line_text(struct view *view, const char *text, enum line_type type)
3378 char *data = text ? strdup(text) : NULL;
3380 return data ? add_line_data(view, data, type) : NULL;
3383 static struct line *
3384 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3386 char buf[SIZEOF_STR];
3387 va_list args;
3389 va_start(args, fmt);
3390 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3391 buf[0] = 0;
3392 va_end(args);
3394 return buf[0] ? add_line_text(view, buf, type) : NULL;
3398 * View opening
3401 enum open_flags {
3402 OPEN_DEFAULT = 0, /* Use default view switching. */
3403 OPEN_SPLIT = 1, /* Split current view. */
3404 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3405 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3406 OPEN_PREPARED = 32, /* Open already prepared command. */
3409 static void
3410 open_view(struct view *prev, enum request request, enum open_flags flags)
3412 bool split = !!(flags & OPEN_SPLIT);
3413 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3414 bool nomaximize = !!(flags & OPEN_REFRESH);
3415 struct view *view = VIEW(request);
3416 int nviews = displayed_views();
3417 struct view *base_view = display[0];
3419 if (view == prev && nviews == 1 && !reload) {
3420 report("Already in %s view", view->name);
3421 return;
3424 if (view->git_dir && !opt_git_dir[0]) {
3425 report("The %s view is disabled in pager view", view->name);
3426 return;
3429 if (split) {
3430 display[1] = view;
3431 current_view = 1;
3432 } else if (!nomaximize) {
3433 /* Maximize the current view. */
3434 memset(display, 0, sizeof(display));
3435 current_view = 0;
3436 display[current_view] = view;
3439 /* No parent signals that this is the first loaded view. */
3440 if (prev && view != prev) {
3441 view->parent = prev;
3444 /* Resize the view when switching between split- and full-screen,
3445 * or when switching between two different full-screen views. */
3446 if (nviews != displayed_views() ||
3447 (nviews == 1 && base_view != display[0]))
3448 resize_display();
3450 if (view->ops->open) {
3451 if (view->pipe)
3452 end_update(view, TRUE);
3453 if (!view->ops->open(view)) {
3454 report("Failed to load %s view", view->name);
3455 return;
3457 restore_view_position(view);
3459 } else if ((reload || strcmp(view->vid, view->id)) &&
3460 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3461 report("Failed to load %s view", view->name);
3462 return;
3465 if (split && prev->lineno - prev->offset >= prev->height) {
3466 /* Take the title line into account. */
3467 int lines = prev->lineno - prev->offset - prev->height + 1;
3469 /* Scroll the view that was split if the current line is
3470 * outside the new limited view. */
3471 do_scroll_view(prev, lines);
3474 if (prev && view != prev && split && view_is_displayed(prev)) {
3475 /* "Blur" the previous view. */
3476 update_view_title(prev);
3479 if (view->pipe && view->lines == 0) {
3480 /* Clear the old view and let the incremental updating refill
3481 * the screen. */
3482 werase(view->win);
3483 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3484 report("");
3485 } else if (view_is_displayed(view)) {
3486 redraw_view(view);
3487 report("");
3491 static void
3492 open_external_viewer(const char *argv[], const char *dir)
3494 def_prog_mode(); /* save current tty modes */
3495 endwin(); /* restore original tty modes */
3496 io_run_fg(argv, dir);
3497 fprintf(stderr, "Press Enter to continue");
3498 getc(opt_tty);
3499 reset_prog_mode();
3500 redraw_display(TRUE);
3503 static void
3504 open_mergetool(const char *file)
3506 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3508 open_external_viewer(mergetool_argv, opt_cdup);
3511 static void
3512 open_editor(const char *file)
3514 const char *editor_argv[] = { "vi", file, NULL };
3515 const char *editor;
3517 editor = getenv("GIT_EDITOR");
3518 if (!editor && *opt_editor)
3519 editor = opt_editor;
3520 if (!editor)
3521 editor = getenv("VISUAL");
3522 if (!editor)
3523 editor = getenv("EDITOR");
3524 if (!editor)
3525 editor = "vi";
3527 editor_argv[0] = editor;
3528 open_external_viewer(editor_argv, opt_cdup);
3531 static void
3532 open_run_request(enum request request)
3534 struct run_request *req = get_run_request(request);
3535 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3537 if (!req) {
3538 report("Unknown run request");
3539 return;
3542 if (format_argv(argv, req->argv, FORMAT_ALL))
3543 open_external_viewer(argv, NULL);
3544 free_argv(argv);
3548 * User request switch noodle
3551 static int
3552 view_driver(struct view *view, enum request request)
3554 int i;
3556 if (request == REQ_NONE)
3557 return TRUE;
3559 if (request > REQ_NONE) {
3560 open_run_request(request);
3561 /* FIXME: When all views can refresh always do this. */
3562 if (view == VIEW(REQ_VIEW_STATUS) ||
3563 view == VIEW(REQ_VIEW_MAIN) ||
3564 view == VIEW(REQ_VIEW_LOG) ||
3565 view == VIEW(REQ_VIEW_BRANCH) ||
3566 view == VIEW(REQ_VIEW_STAGE))
3567 request = REQ_REFRESH;
3568 else
3569 return TRUE;
3572 if (view && view->lines) {
3573 request = view->ops->request(view, request, &view->line[view->lineno]);
3574 if (request == REQ_NONE)
3575 return TRUE;
3578 switch (request) {
3579 case REQ_MOVE_UP:
3580 case REQ_MOVE_DOWN:
3581 case REQ_MOVE_PAGE_UP:
3582 case REQ_MOVE_PAGE_DOWN:
3583 case REQ_MOVE_FIRST_LINE:
3584 case REQ_MOVE_LAST_LINE:
3585 move_view(view, request);
3586 break;
3588 case REQ_SCROLL_LEFT:
3589 case REQ_SCROLL_RIGHT:
3590 case REQ_SCROLL_LINE_DOWN:
3591 case REQ_SCROLL_LINE_UP:
3592 case REQ_SCROLL_PAGE_DOWN:
3593 case REQ_SCROLL_PAGE_UP:
3594 scroll_view(view, request);
3595 break;
3597 case REQ_VIEW_BLAME:
3598 if (!opt_file[0]) {
3599 report("No file chosen, press %s to open tree view",
3600 get_key(view->keymap, REQ_VIEW_TREE));
3601 break;
3603 open_view(view, request, OPEN_DEFAULT);
3604 break;
3606 case REQ_VIEW_BLOB:
3607 if (!ref_blob[0]) {
3608 report("No file chosen, press %s to open tree view",
3609 get_key(view->keymap, REQ_VIEW_TREE));
3610 break;
3612 open_view(view, request, OPEN_DEFAULT);
3613 break;
3615 case REQ_VIEW_PAGER:
3616 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3617 report("No pager content, press %s to run command from prompt",
3618 get_key(view->keymap, REQ_PROMPT));
3619 break;
3621 open_view(view, request, OPEN_DEFAULT);
3622 break;
3624 case REQ_VIEW_STAGE:
3625 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3626 report("No stage content, press %s to open the status view and choose file",
3627 get_key(view->keymap, REQ_VIEW_STATUS));
3628 break;
3630 open_view(view, request, OPEN_DEFAULT);
3631 break;
3633 case REQ_VIEW_STATUS:
3634 if (opt_is_inside_work_tree == FALSE) {
3635 report("The status view requires a working tree");
3636 break;
3638 open_view(view, request, OPEN_DEFAULT);
3639 break;
3641 case REQ_VIEW_MAIN:
3642 case REQ_VIEW_DIFF:
3643 case REQ_VIEW_LOG:
3644 case REQ_VIEW_TREE:
3645 case REQ_VIEW_HELP:
3646 case REQ_VIEW_BRANCH:
3647 open_view(view, request, OPEN_DEFAULT);
3648 break;
3650 case REQ_NEXT:
3651 case REQ_PREVIOUS:
3652 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3654 if ((view == VIEW(REQ_VIEW_DIFF) &&
3655 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3656 (view == VIEW(REQ_VIEW_DIFF) &&
3657 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3658 (view == VIEW(REQ_VIEW_STAGE) &&
3659 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3660 (view == VIEW(REQ_VIEW_BLOB) &&
3661 view->parent == VIEW(REQ_VIEW_TREE)) ||
3662 (view == VIEW(REQ_VIEW_MAIN) &&
3663 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3664 int line;
3666 view = view->parent;
3667 line = view->lineno;
3668 move_view(view, request);
3669 if (view_is_displayed(view))
3670 update_view_title(view);
3671 if (line != view->lineno)
3672 view->ops->request(view, REQ_ENTER,
3673 &view->line[view->lineno]);
3675 } else {
3676 move_view(view, request);
3678 break;
3680 case REQ_VIEW_NEXT:
3682 int nviews = displayed_views();
3683 int next_view = (current_view + 1) % nviews;
3685 if (next_view == current_view) {
3686 report("Only one view is displayed");
3687 break;
3690 current_view = next_view;
3691 /* Blur out the title of the previous view. */
3692 update_view_title(view);
3693 report("");
3694 break;
3696 case REQ_REFRESH:
3697 report("Refreshing is not yet supported for the %s view", view->name);
3698 break;
3700 case REQ_MAXIMIZE:
3701 if (displayed_views() == 2)
3702 maximize_view(view);
3703 break;
3705 case REQ_OPTIONS:
3706 open_option_menu();
3707 break;
3709 case REQ_TOGGLE_LINENO:
3710 toggle_view_option(&opt_line_number, "line numbers");
3711 break;
3713 case REQ_TOGGLE_DATE:
3714 toggle_date();
3715 break;
3717 case REQ_TOGGLE_AUTHOR:
3718 toggle_author();
3719 break;
3721 case REQ_TOGGLE_REV_GRAPH:
3722 toggle_view_option(&opt_rev_graph, "revision graph display");
3723 break;
3725 case REQ_TOGGLE_REFS:
3726 toggle_view_option(&opt_show_refs, "reference display");
3727 break;
3729 case REQ_TOGGLE_SORT_FIELD:
3730 case REQ_TOGGLE_SORT_ORDER:
3731 report("Sorting is not yet supported for the %s view", view->name);
3732 break;
3734 case REQ_SEARCH:
3735 case REQ_SEARCH_BACK:
3736 search_view(view, request);
3737 break;
3739 case REQ_FIND_NEXT:
3740 case REQ_FIND_PREV:
3741 find_next(view, request);
3742 break;
3744 case REQ_STOP_LOADING:
3745 for (i = 0; i < ARRAY_SIZE(views); i++) {
3746 view = &views[i];
3747 if (view->pipe)
3748 report("Stopped loading the %s view", view->name),
3749 end_update(view, TRUE);
3751 break;
3753 case REQ_SHOW_VERSION:
3754 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3755 return TRUE;
3757 case REQ_SCREEN_REDRAW:
3758 redraw_display(TRUE);
3759 break;
3761 case REQ_EDIT:
3762 report("Nothing to edit");
3763 break;
3765 case REQ_ENTER:
3766 report("Nothing to enter");
3767 break;
3769 case REQ_VIEW_CLOSE:
3770 /* XXX: Mark closed views by letting view->parent point to the
3771 * view itself. Parents to closed view should never be
3772 * followed. */
3773 if (view->parent &&
3774 view->parent->parent != view->parent) {
3775 maximize_view(view->parent);
3776 view->parent = view;
3777 break;
3779 /* Fall-through */
3780 case REQ_QUIT:
3781 return FALSE;
3783 default:
3784 report("Unknown key, press %s for help",
3785 get_key(view->keymap, REQ_VIEW_HELP));
3786 return TRUE;
3789 return TRUE;
3794 * View backend utilities
3797 enum sort_field {
3798 ORDERBY_NAME,
3799 ORDERBY_DATE,
3800 ORDERBY_AUTHOR,
3803 struct sort_state {
3804 const enum sort_field *fields;
3805 size_t size, current;
3806 bool reverse;
3809 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3810 #define get_sort_field(state) ((state).fields[(state).current])
3811 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3813 static void
3814 sort_view(struct view *view, enum request request, struct sort_state *state,
3815 int (*compare)(const void *, const void *))
3817 switch (request) {
3818 case REQ_TOGGLE_SORT_FIELD:
3819 state->current = (state->current + 1) % state->size;
3820 break;
3822 case REQ_TOGGLE_SORT_ORDER:
3823 state->reverse = !state->reverse;
3824 break;
3825 default:
3826 die("Not a sort request");
3829 qsort(view->line, view->lines, sizeof(*view->line), compare);
3830 redraw_view(view);
3833 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3835 /* Small author cache to reduce memory consumption. It uses binary
3836 * search to lookup or find place to position new entries. No entries
3837 * are ever freed. */
3838 static const char *
3839 get_author(const char *name)
3841 static const char **authors;
3842 static size_t authors_size;
3843 int from = 0, to = authors_size - 1;
3845 while (from <= to) {
3846 size_t pos = (to + from) / 2;
3847 int cmp = strcmp(name, authors[pos]);
3849 if (!cmp)
3850 return authors[pos];
3852 if (cmp < 0)
3853 to = pos - 1;
3854 else
3855 from = pos + 1;
3858 if (!realloc_authors(&authors, authors_size, 1))
3859 return NULL;
3860 name = strdup(name);
3861 if (!name)
3862 return NULL;
3864 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3865 authors[from] = name;
3866 authors_size++;
3868 return name;
3871 static void
3872 parse_timesec(struct time *time, const char *sec)
3874 time->sec = (time_t) atol(sec);
3877 static void
3878 parse_timezone(struct time *time, const char *zone)
3880 long tz;
3882 tz = ('0' - zone[1]) * 60 * 60 * 10;
3883 tz += ('0' - zone[2]) * 60 * 60;
3884 tz += ('0' - zone[3]) * 60;
3885 tz += ('0' - zone[4]);
3887 if (zone[0] == '-')
3888 tz = -tz;
3890 time->tz = tz;
3891 time->sec -= tz;
3894 /* Parse author lines where the name may be empty:
3895 * author <email@address.tld> 1138474660 +0100
3897 static void
3898 parse_author_line(char *ident, const char **author, struct time *time)
3900 char *nameend = strchr(ident, '<');
3901 char *emailend = strchr(ident, '>');
3903 if (nameend && emailend)
3904 *nameend = *emailend = 0;
3905 ident = chomp_string(ident);
3906 if (!*ident) {
3907 if (nameend)
3908 ident = chomp_string(nameend + 1);
3909 if (!*ident)
3910 ident = "Unknown";
3913 *author = get_author(ident);
3915 /* Parse epoch and timezone */
3916 if (emailend && emailend[1] == ' ') {
3917 char *secs = emailend + 2;
3918 char *zone = strchr(secs, ' ');
3920 parse_timesec(time, secs);
3922 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3923 parse_timezone(time, zone + 1);
3927 static bool
3928 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3930 char rev[SIZEOF_REV];
3931 const char *revlist_argv[] = {
3932 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3934 struct menu_item *items;
3935 char text[SIZEOF_STR];
3936 bool ok = TRUE;
3937 int i;
3939 items = calloc(*parents + 1, sizeof(*items));
3940 if (!items)
3941 return FALSE;
3943 for (i = 0; i < *parents; i++) {
3944 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3945 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3946 !(items[i].text = strdup(text))) {
3947 ok = FALSE;
3948 break;
3952 if (ok) {
3953 *parents = 0;
3954 ok = prompt_menu("Select parent", items, parents);
3956 for (i = 0; items[i].text; i++)
3957 free((char *) items[i].text);
3958 free(items);
3959 return ok;
3962 static bool
3963 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3965 char buf[SIZEOF_STR * 4];
3966 const char *revlist_argv[] = {
3967 "git", "log", "--no-color", "-1",
3968 "--pretty=format:%P", id, "--", path, NULL
3970 int parents;
3972 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3973 (parents = strlen(buf) / 40) < 0) {
3974 report("Failed to get parent information");
3975 return FALSE;
3977 } else if (parents == 0) {
3978 if (path)
3979 report("Path '%s' does not exist in the parent", path);
3980 else
3981 report("The selected commit has no parents");
3982 return FALSE;
3985 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3986 return FALSE;
3988 string_copy_rev(rev, &buf[41 * parents]);
3989 return TRUE;
3993 * Pager backend
3996 static bool
3997 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3999 char text[SIZEOF_STR];
4001 if (opt_line_number && draw_lineno(view, lineno))
4002 return TRUE;
4004 string_expand(text, sizeof(text), line->data, opt_tab_size);
4005 draw_text(view, line->type, text, TRUE);
4006 return TRUE;
4009 static bool
4010 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4012 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4013 char ref[SIZEOF_STR];
4015 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4016 return TRUE;
4018 /* This is the only fatal call, since it can "corrupt" the buffer. */
4019 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4020 return FALSE;
4022 return TRUE;
4025 static void
4026 add_pager_refs(struct view *view, struct line *line)
4028 char buf[SIZEOF_STR];
4029 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4030 struct ref_list *list;
4031 size_t bufpos = 0, i;
4032 const char *sep = "Refs: ";
4033 bool is_tag = FALSE;
4035 assert(line->type == LINE_COMMIT);
4037 list = get_ref_list(commit_id);
4038 if (!list) {
4039 if (view == VIEW(REQ_VIEW_DIFF))
4040 goto try_add_describe_ref;
4041 return;
4044 for (i = 0; i < list->size; i++) {
4045 struct ref *ref = list->refs[i];
4046 const char *fmt = ref->tag ? "%s[%s]" :
4047 ref->remote ? "%s<%s>" : "%s%s";
4049 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4050 return;
4051 sep = ", ";
4052 if (ref->tag)
4053 is_tag = TRUE;
4056 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4057 try_add_describe_ref:
4058 /* Add <tag>-g<commit_id> "fake" reference. */
4059 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4060 return;
4063 if (bufpos == 0)
4064 return;
4066 add_line_text(view, buf, LINE_PP_REFS);
4069 static bool
4070 pager_read(struct view *view, char *data)
4072 struct line *line;
4074 if (!data)
4075 return TRUE;
4077 line = add_line_text(view, data, get_line_type(data));
4078 if (!line)
4079 return FALSE;
4081 if (line->type == LINE_COMMIT &&
4082 (view == VIEW(REQ_VIEW_DIFF) ||
4083 view == VIEW(REQ_VIEW_LOG)))
4084 add_pager_refs(view, line);
4086 return TRUE;
4089 static enum request
4090 pager_request(struct view *view, enum request request, struct line *line)
4092 int split = 0;
4094 if (request != REQ_ENTER)
4095 return request;
4097 if (line->type == LINE_COMMIT &&
4098 (view == VIEW(REQ_VIEW_LOG) ||
4099 view == VIEW(REQ_VIEW_PAGER))) {
4100 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4101 split = 1;
4104 /* Always scroll the view even if it was split. That way
4105 * you can use Enter to scroll through the log view and
4106 * split open each commit diff. */
4107 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4109 /* FIXME: A minor workaround. Scrolling the view will call report("")
4110 * but if we are scrolling a non-current view this won't properly
4111 * update the view title. */
4112 if (split)
4113 update_view_title(view);
4115 return REQ_NONE;
4118 static bool
4119 pager_grep(struct view *view, struct line *line)
4121 const char *text[] = { line->data, NULL };
4123 return grep_text(view, text);
4126 static void
4127 pager_select(struct view *view, struct line *line)
4129 if (line->type == LINE_COMMIT) {
4130 char *text = (char *)line->data + STRING_SIZE("commit ");
4132 if (view != VIEW(REQ_VIEW_PAGER))
4133 string_copy_rev(view->ref, text);
4134 string_copy_rev(ref_commit, text);
4138 static struct view_ops pager_ops = {
4139 "line",
4140 NULL,
4141 NULL,
4142 pager_read,
4143 pager_draw,
4144 pager_request,
4145 pager_grep,
4146 pager_select,
4149 static const char *log_argv[SIZEOF_ARG] = {
4150 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4153 static enum request
4154 log_request(struct view *view, enum request request, struct line *line)
4156 switch (request) {
4157 case REQ_REFRESH:
4158 load_refs();
4159 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4160 return REQ_NONE;
4161 default:
4162 return pager_request(view, request, line);
4166 static struct view_ops log_ops = {
4167 "line",
4168 log_argv,
4169 NULL,
4170 pager_read,
4171 pager_draw,
4172 log_request,
4173 pager_grep,
4174 pager_select,
4177 static const char *diff_argv[SIZEOF_ARG] = {
4178 "git", "show", "--pretty=fuller", "--no-color", "--root",
4179 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4182 static struct view_ops diff_ops = {
4183 "line",
4184 diff_argv,
4185 NULL,
4186 pager_read,
4187 pager_draw,
4188 pager_request,
4189 pager_grep,
4190 pager_select,
4194 * Help backend
4197 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4199 static bool
4200 help_open_keymap_title(struct view *view, enum keymap keymap)
4202 struct line *line;
4204 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4205 help_keymap_hidden[keymap] ? '+' : '-',
4206 enum_name(keymap_table[keymap]));
4207 if (line)
4208 line->other = keymap;
4210 return help_keymap_hidden[keymap];
4213 static void
4214 help_open_keymap(struct view *view, enum keymap keymap)
4216 const char *group = NULL;
4217 char buf[SIZEOF_STR];
4218 size_t bufpos;
4219 bool add_title = TRUE;
4220 int i;
4222 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4223 const char *key = NULL;
4225 if (req_info[i].request == REQ_NONE)
4226 continue;
4228 if (!req_info[i].request) {
4229 group = req_info[i].help;
4230 continue;
4233 key = get_keys(keymap, req_info[i].request, TRUE);
4234 if (!key || !*key)
4235 continue;
4237 if (add_title && help_open_keymap_title(view, keymap))
4238 return;
4239 add_title = FALSE;
4241 if (group) {
4242 add_line_text(view, group, LINE_HELP_GROUP);
4243 group = NULL;
4246 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4247 enum_name(req_info[i]), req_info[i].help);
4250 group = "External commands:";
4252 for (i = 0; i < run_requests; i++) {
4253 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4254 const char *key;
4255 int argc;
4257 if (!req || req->keymap != keymap)
4258 continue;
4260 key = get_key_name(req->key);
4261 if (!*key)
4262 key = "(no key defined)";
4264 if (add_title && help_open_keymap_title(view, keymap))
4265 return;
4266 if (group) {
4267 add_line_text(view, group, LINE_HELP_GROUP);
4268 group = NULL;
4271 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4272 if (!string_format_from(buf, &bufpos, "%s%s",
4273 argc ? " " : "", req->argv[argc]))
4274 return;
4276 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4280 static bool
4281 help_open(struct view *view)
4283 enum keymap keymap;
4285 reset_view(view);
4286 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4287 add_line_text(view, "", LINE_DEFAULT);
4289 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4290 help_open_keymap(view, keymap);
4292 return TRUE;
4295 static enum request
4296 help_request(struct view *view, enum request request, struct line *line)
4298 switch (request) {
4299 case REQ_ENTER:
4300 if (line->type == LINE_HELP_KEYMAP) {
4301 help_keymap_hidden[line->other] =
4302 !help_keymap_hidden[line->other];
4303 view->p_restore = TRUE;
4304 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4307 return REQ_NONE;
4308 default:
4309 return pager_request(view, request, line);
4313 static struct view_ops help_ops = {
4314 "line",
4315 NULL,
4316 help_open,
4317 NULL,
4318 pager_draw,
4319 help_request,
4320 pager_grep,
4321 pager_select,
4326 * Tree backend
4329 struct tree_stack_entry {
4330 struct tree_stack_entry *prev; /* Entry below this in the stack */
4331 unsigned long lineno; /* Line number to restore */
4332 char *name; /* Position of name in opt_path */
4335 /* The top of the path stack. */
4336 static struct tree_stack_entry *tree_stack = NULL;
4337 unsigned long tree_lineno = 0;
4339 static void
4340 pop_tree_stack_entry(void)
4342 struct tree_stack_entry *entry = tree_stack;
4344 tree_lineno = entry->lineno;
4345 entry->name[0] = 0;
4346 tree_stack = entry->prev;
4347 free(entry);
4350 static void
4351 push_tree_stack_entry(const char *name, unsigned long lineno)
4353 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4354 size_t pathlen = strlen(opt_path);
4356 if (!entry)
4357 return;
4359 entry->prev = tree_stack;
4360 entry->name = opt_path + pathlen;
4361 tree_stack = entry;
4363 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4364 pop_tree_stack_entry();
4365 return;
4368 /* Move the current line to the first tree entry. */
4369 tree_lineno = 1;
4370 entry->lineno = lineno;
4373 /* Parse output from git-ls-tree(1):
4375 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4378 #define SIZEOF_TREE_ATTR \
4379 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4381 #define SIZEOF_TREE_MODE \
4382 STRING_SIZE("100644 ")
4384 #define TREE_ID_OFFSET \
4385 STRING_SIZE("100644 blob ")
4387 struct tree_entry {
4388 char id[SIZEOF_REV];
4389 mode_t mode;
4390 struct time time; /* Date from the author ident. */
4391 const char *author; /* Author of the commit. */
4392 char name[1];
4395 static const char *
4396 tree_path(const struct line *line)
4398 return ((struct tree_entry *) line->data)->name;
4401 static int
4402 tree_compare_entry(const struct line *line1, const struct line *line2)
4404 if (line1->type != line2->type)
4405 return line1->type == LINE_TREE_DIR ? -1 : 1;
4406 return strcmp(tree_path(line1), tree_path(line2));
4409 static const enum sort_field tree_sort_fields[] = {
4410 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4412 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4414 static int
4415 tree_compare(const void *l1, const void *l2)
4417 const struct line *line1 = (const struct line *) l1;
4418 const struct line *line2 = (const struct line *) l2;
4419 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4420 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4422 if (line1->type == LINE_TREE_HEAD)
4423 return -1;
4424 if (line2->type == LINE_TREE_HEAD)
4425 return 1;
4427 switch (get_sort_field(tree_sort_state)) {
4428 case ORDERBY_DATE:
4429 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4431 case ORDERBY_AUTHOR:
4432 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4434 case ORDERBY_NAME:
4435 default:
4436 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4441 static struct line *
4442 tree_entry(struct view *view, enum line_type type, const char *path,
4443 const char *mode, const char *id)
4445 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4446 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4448 if (!entry || !line) {
4449 free(entry);
4450 return NULL;
4453 strncpy(entry->name, path, strlen(path));
4454 if (mode)
4455 entry->mode = strtoul(mode, NULL, 8);
4456 if (id)
4457 string_copy_rev(entry->id, id);
4459 return line;
4462 static bool
4463 tree_read_date(struct view *view, char *text, bool *read_date)
4465 static const char *author_name;
4466 static struct time author_time;
4468 if (!text && *read_date) {
4469 *read_date = FALSE;
4470 return TRUE;
4472 } else if (!text) {
4473 char *path = *opt_path ? opt_path : ".";
4474 /* Find next entry to process */
4475 const char *log_file[] = {
4476 "git", "log", "--no-color", "--pretty=raw",
4477 "--cc", "--raw", view->id, "--", path, NULL
4479 struct io io = {};
4481 if (!view->lines) {
4482 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4483 report("Tree is empty");
4484 return TRUE;
4487 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4488 report("Failed to load tree data");
4489 return TRUE;
4492 io_done(view->pipe);
4493 view->io = io;
4494 *read_date = TRUE;
4495 return FALSE;
4497 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4498 parse_author_line(text + STRING_SIZE("author "),
4499 &author_name, &author_time);
4501 } else if (*text == ':') {
4502 char *pos;
4503 size_t annotated = 1;
4504 size_t i;
4506 pos = strchr(text, '\t');
4507 if (!pos)
4508 return TRUE;
4509 text = pos + 1;
4510 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4511 text += strlen(opt_path);
4512 pos = strchr(text, '/');
4513 if (pos)
4514 *pos = 0;
4516 for (i = 1; i < view->lines; i++) {
4517 struct line *line = &view->line[i];
4518 struct tree_entry *entry = line->data;
4520 annotated += !!entry->author;
4521 if (entry->author || strcmp(entry->name, text))
4522 continue;
4524 entry->author = author_name;
4525 entry->time = author_time;
4526 line->dirty = 1;
4527 break;
4530 if (annotated == view->lines)
4531 io_kill(view->pipe);
4533 return TRUE;
4536 static bool
4537 tree_read(struct view *view, char *text)
4539 static bool read_date = FALSE;
4540 struct tree_entry *data;
4541 struct line *entry, *line;
4542 enum line_type type;
4543 size_t textlen = text ? strlen(text) : 0;
4544 char *path = text + SIZEOF_TREE_ATTR;
4546 if (read_date || !text)
4547 return tree_read_date(view, text, &read_date);
4549 if (textlen <= SIZEOF_TREE_ATTR)
4550 return FALSE;
4551 if (view->lines == 0 &&
4552 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4553 return FALSE;
4555 /* Strip the path part ... */
4556 if (*opt_path) {
4557 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4558 size_t striplen = strlen(opt_path);
4560 if (pathlen > striplen)
4561 memmove(path, path + striplen,
4562 pathlen - striplen + 1);
4564 /* Insert "link" to parent directory. */
4565 if (view->lines == 1 &&
4566 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4567 return FALSE;
4570 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4571 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4572 if (!entry)
4573 return FALSE;
4574 data = entry->data;
4576 /* Skip "Directory ..." and ".." line. */
4577 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4578 if (tree_compare_entry(line, entry) <= 0)
4579 continue;
4581 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4583 line->data = data;
4584 line->type = type;
4585 for (; line <= entry; line++)
4586 line->dirty = line->cleareol = 1;
4587 return TRUE;
4590 if (tree_lineno > view->lineno) {
4591 view->lineno = tree_lineno;
4592 tree_lineno = 0;
4595 return TRUE;
4598 static bool
4599 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4601 struct tree_entry *entry = line->data;
4603 if (line->type == LINE_TREE_HEAD) {
4604 if (draw_text(view, line->type, "Directory path /", TRUE))
4605 return TRUE;
4606 } else {
4607 if (draw_mode(view, entry->mode))
4608 return TRUE;
4610 if (opt_author && draw_author(view, entry->author))
4611 return TRUE;
4613 if (opt_date && draw_date(view, &entry->time))
4614 return TRUE;
4616 if (draw_text(view, line->type, entry->name, TRUE))
4617 return TRUE;
4618 return TRUE;
4621 static void
4622 open_blob_editor()
4624 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4625 int fd = mkstemp(file);
4627 if (fd == -1)
4628 report("Failed to create temporary file");
4629 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4630 report("Failed to save blob data to file");
4631 else
4632 open_editor(file);
4633 if (fd != -1)
4634 unlink(file);
4637 static enum request
4638 tree_request(struct view *view, enum request request, struct line *line)
4640 enum open_flags flags;
4642 switch (request) {
4643 case REQ_VIEW_BLAME:
4644 if (line->type != LINE_TREE_FILE) {
4645 report("Blame only supported for files");
4646 return REQ_NONE;
4649 string_copy(opt_ref, view->vid);
4650 return request;
4652 case REQ_EDIT:
4653 if (line->type != LINE_TREE_FILE) {
4654 report("Edit only supported for files");
4655 } else if (!is_head_commit(view->vid)) {
4656 open_blob_editor();
4657 } else {
4658 open_editor(opt_file);
4660 return REQ_NONE;
4662 case REQ_TOGGLE_SORT_FIELD:
4663 case REQ_TOGGLE_SORT_ORDER:
4664 sort_view(view, request, &tree_sort_state, tree_compare);
4665 return REQ_NONE;
4667 case REQ_PARENT:
4668 if (!*opt_path) {
4669 /* quit view if at top of tree */
4670 return REQ_VIEW_CLOSE;
4672 /* fake 'cd ..' */
4673 line = &view->line[1];
4674 break;
4676 case REQ_ENTER:
4677 break;
4679 default:
4680 return request;
4683 /* Cleanup the stack if the tree view is at a different tree. */
4684 while (!*opt_path && tree_stack)
4685 pop_tree_stack_entry();
4687 switch (line->type) {
4688 case LINE_TREE_DIR:
4689 /* Depending on whether it is a subdirectory or parent link
4690 * mangle the path buffer. */
4691 if (line == &view->line[1] && *opt_path) {
4692 pop_tree_stack_entry();
4694 } else {
4695 const char *basename = tree_path(line);
4697 push_tree_stack_entry(basename, view->lineno);
4700 /* Trees and subtrees share the same ID, so they are not not
4701 * unique like blobs. */
4702 flags = OPEN_RELOAD;
4703 request = REQ_VIEW_TREE;
4704 break;
4706 case LINE_TREE_FILE:
4707 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4708 request = REQ_VIEW_BLOB;
4709 break;
4711 default:
4712 return REQ_NONE;
4715 open_view(view, request, flags);
4716 if (request == REQ_VIEW_TREE)
4717 view->lineno = tree_lineno;
4719 return REQ_NONE;
4722 static bool
4723 tree_grep(struct view *view, struct line *line)
4725 struct tree_entry *entry = line->data;
4726 const char *text[] = {
4727 entry->name,
4728 opt_author ? entry->author : "",
4729 mkdate(&entry->time, opt_date),
4730 NULL
4733 return grep_text(view, text);
4736 static void
4737 tree_select(struct view *view, struct line *line)
4739 struct tree_entry *entry = line->data;
4741 if (line->type == LINE_TREE_FILE) {
4742 string_copy_rev(ref_blob, entry->id);
4743 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4745 } else if (line->type != LINE_TREE_DIR) {
4746 return;
4749 string_copy_rev(view->ref, entry->id);
4752 static bool
4753 tree_prepare(struct view *view)
4755 if (view->lines == 0 && opt_prefix[0]) {
4756 char *pos = opt_prefix;
4758 while (pos && *pos) {
4759 char *end = strchr(pos, '/');
4761 if (end)
4762 *end = 0;
4763 push_tree_stack_entry(pos, 0);
4764 pos = end;
4765 if (end) {
4766 *end = '/';
4767 pos++;
4771 } else if (strcmp(view->vid, view->id)) {
4772 opt_path[0] = 0;
4775 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4778 static const char *tree_argv[SIZEOF_ARG] = {
4779 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4782 static struct view_ops tree_ops = {
4783 "file",
4784 tree_argv,
4785 NULL,
4786 tree_read,
4787 tree_draw,
4788 tree_request,
4789 tree_grep,
4790 tree_select,
4791 tree_prepare,
4794 static bool
4795 blob_read(struct view *view, char *line)
4797 if (!line)
4798 return TRUE;
4799 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4802 static enum request
4803 blob_request(struct view *view, enum request request, struct line *line)
4805 switch (request) {
4806 case REQ_EDIT:
4807 open_blob_editor();
4808 return REQ_NONE;
4809 default:
4810 return pager_request(view, request, line);
4814 static const char *blob_argv[SIZEOF_ARG] = {
4815 "git", "cat-file", "blob", "%(blob)", NULL
4818 static struct view_ops blob_ops = {
4819 "line",
4820 blob_argv,
4821 NULL,
4822 blob_read,
4823 pager_draw,
4824 blob_request,
4825 pager_grep,
4826 pager_select,
4830 * Blame backend
4832 * Loading the blame view is a two phase job:
4834 * 1. File content is read either using opt_file from the
4835 * filesystem or using git-cat-file.
4836 * 2. Then blame information is incrementally added by
4837 * reading output from git-blame.
4840 static const char *blame_head_argv[] = {
4841 "git", "blame", "--incremental", "--", "%(file)", NULL
4844 static const char *blame_ref_argv[] = {
4845 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4848 static const char *blame_cat_file_argv[] = {
4849 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4852 struct blame_commit {
4853 char id[SIZEOF_REV]; /* SHA1 ID. */
4854 char title[128]; /* First line of the commit message. */
4855 const char *author; /* Author of the commit. */
4856 struct time time; /* Date from the author ident. */
4857 char filename[128]; /* Name of file. */
4858 bool has_previous; /* Was a "previous" line detected. */
4861 struct blame {
4862 struct blame_commit *commit;
4863 unsigned long lineno;
4864 char text[1];
4867 static bool
4868 blame_open(struct view *view)
4870 char path[SIZEOF_STR];
4872 if (!view->parent && *opt_prefix) {
4873 string_copy(path, opt_file);
4874 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4875 return FALSE;
4878 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4879 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4880 return FALSE;
4883 setup_update(view, opt_file);
4884 string_format(view->ref, "%s ...", opt_file);
4886 return TRUE;
4889 static struct blame_commit *
4890 get_blame_commit(struct view *view, const char *id)
4892 size_t i;
4894 for (i = 0; i < view->lines; i++) {
4895 struct blame *blame = view->line[i].data;
4897 if (!blame->commit)
4898 continue;
4900 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4901 return blame->commit;
4905 struct blame_commit *commit = calloc(1, sizeof(*commit));
4907 if (commit)
4908 string_ncopy(commit->id, id, SIZEOF_REV);
4909 return commit;
4913 static bool
4914 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4916 const char *pos = *posref;
4918 *posref = NULL;
4919 pos = strchr(pos + 1, ' ');
4920 if (!pos || !isdigit(pos[1]))
4921 return FALSE;
4922 *number = atoi(pos + 1);
4923 if (*number < min || *number > max)
4924 return FALSE;
4926 *posref = pos;
4927 return TRUE;
4930 static struct blame_commit *
4931 parse_blame_commit(struct view *view, const char *text, int *blamed)
4933 struct blame_commit *commit;
4934 struct blame *blame;
4935 const char *pos = text + SIZEOF_REV - 2;
4936 size_t orig_lineno = 0;
4937 size_t lineno;
4938 size_t group;
4940 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4941 return NULL;
4943 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4944 !parse_number(&pos, &lineno, 1, view->lines) ||
4945 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4946 return NULL;
4948 commit = get_blame_commit(view, text);
4949 if (!commit)
4950 return NULL;
4952 *blamed += group;
4953 while (group--) {
4954 struct line *line = &view->line[lineno + group - 1];
4956 blame = line->data;
4957 blame->commit = commit;
4958 blame->lineno = orig_lineno + group - 1;
4959 line->dirty = 1;
4962 return commit;
4965 static bool
4966 blame_read_file(struct view *view, const char *line, bool *read_file)
4968 if (!line) {
4969 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4970 struct io io = {};
4972 if (view->lines == 0 && !view->parent)
4973 die("No blame exist for %s", view->vid);
4975 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4976 report("Failed to load blame data");
4977 return TRUE;
4980 io_done(view->pipe);
4981 view->io = io;
4982 *read_file = FALSE;
4983 return FALSE;
4985 } else {
4986 size_t linelen = strlen(line);
4987 struct blame *blame = malloc(sizeof(*blame) + linelen);
4989 if (!blame)
4990 return FALSE;
4992 blame->commit = NULL;
4993 strncpy(blame->text, line, linelen);
4994 blame->text[linelen] = 0;
4995 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4999 static bool
5000 match_blame_header(const char *name, char **line)
5002 size_t namelen = strlen(name);
5003 bool matched = !strncmp(name, *line, namelen);
5005 if (matched)
5006 *line += namelen;
5008 return matched;
5011 static bool
5012 blame_read(struct view *view, char *line)
5014 static struct blame_commit *commit = NULL;
5015 static int blamed = 0;
5016 static bool read_file = TRUE;
5018 if (read_file)
5019 return blame_read_file(view, line, &read_file);
5021 if (!line) {
5022 /* Reset all! */
5023 commit = NULL;
5024 blamed = 0;
5025 read_file = TRUE;
5026 string_format(view->ref, "%s", view->vid);
5027 if (view_is_displayed(view)) {
5028 update_view_title(view);
5029 redraw_view_from(view, 0);
5031 return TRUE;
5034 if (!commit) {
5035 commit = parse_blame_commit(view, line, &blamed);
5036 string_format(view->ref, "%s %2d%%", view->vid,
5037 view->lines ? blamed * 100 / view->lines : 0);
5039 } else if (match_blame_header("author ", &line)) {
5040 commit->author = get_author(line);
5042 } else if (match_blame_header("author-time ", &line)) {
5043 parse_timesec(&commit->time, line);
5045 } else if (match_blame_header("author-tz ", &line)) {
5046 parse_timezone(&commit->time, line);
5048 } else if (match_blame_header("summary ", &line)) {
5049 string_ncopy(commit->title, line, strlen(line));
5051 } else if (match_blame_header("previous ", &line)) {
5052 commit->has_previous = TRUE;
5054 } else if (match_blame_header("filename ", &line)) {
5055 string_ncopy(commit->filename, line, strlen(line));
5056 commit = NULL;
5059 return TRUE;
5062 static bool
5063 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5065 struct blame *blame = line->data;
5066 struct time *time = NULL;
5067 const char *id = NULL, *author = NULL;
5068 char text[SIZEOF_STR];
5070 if (blame->commit && *blame->commit->filename) {
5071 id = blame->commit->id;
5072 author = blame->commit->author;
5073 time = &blame->commit->time;
5076 if (opt_date && draw_date(view, time))
5077 return TRUE;
5079 if (opt_author && draw_author(view, author))
5080 return TRUE;
5082 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5083 return TRUE;
5085 if (draw_lineno(view, lineno))
5086 return TRUE;
5088 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5089 draw_text(view, LINE_DEFAULT, text, TRUE);
5090 return TRUE;
5093 static bool
5094 check_blame_commit(struct blame *blame, bool check_null_id)
5096 if (!blame->commit)
5097 report("Commit data not loaded yet");
5098 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5099 report("No commit exist for the selected line");
5100 else
5101 return TRUE;
5102 return FALSE;
5105 static void
5106 setup_blame_parent_line(struct view *view, struct blame *blame)
5108 const char *diff_tree_argv[] = {
5109 "git", "diff-tree", "-U0", blame->commit->id,
5110 "--", blame->commit->filename, NULL
5112 struct io io = {};
5113 int parent_lineno = -1;
5114 int blamed_lineno = -1;
5115 char *line;
5117 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5118 return;
5120 while ((line = io_get(&io, '\n', TRUE))) {
5121 if (*line == '@') {
5122 char *pos = strchr(line, '+');
5124 parent_lineno = atoi(line + 4);
5125 if (pos)
5126 blamed_lineno = atoi(pos + 1);
5128 } else if (*line == '+' && parent_lineno != -1) {
5129 if (blame->lineno == blamed_lineno - 1 &&
5130 !strcmp(blame->text, line + 1)) {
5131 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5132 break;
5134 blamed_lineno++;
5138 io_done(&io);
5141 static enum request
5142 blame_request(struct view *view, enum request request, struct line *line)
5144 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5145 struct blame *blame = line->data;
5147 switch (request) {
5148 case REQ_VIEW_BLAME:
5149 if (check_blame_commit(blame, TRUE)) {
5150 string_copy(opt_ref, blame->commit->id);
5151 string_copy(opt_file, blame->commit->filename);
5152 if (blame->lineno)
5153 view->lineno = blame->lineno;
5154 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5156 break;
5158 case REQ_PARENT:
5159 if (check_blame_commit(blame, TRUE) &&
5160 select_commit_parent(blame->commit->id, opt_ref,
5161 blame->commit->filename)) {
5162 string_copy(opt_file, blame->commit->filename);
5163 setup_blame_parent_line(view, blame);
5164 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5166 break;
5168 case REQ_ENTER:
5169 if (!check_blame_commit(blame, FALSE))
5170 break;
5172 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5173 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5174 break;
5176 if (!strcmp(blame->commit->id, NULL_ID)) {
5177 struct view *diff = VIEW(REQ_VIEW_DIFF);
5178 const char *diff_index_argv[] = {
5179 "git", "diff-index", "--root", "--patch-with-stat",
5180 "-C", "-M", "HEAD", "--", view->vid, NULL
5183 if (!blame->commit->has_previous) {
5184 diff_index_argv[1] = "diff";
5185 diff_index_argv[2] = "--no-color";
5186 diff_index_argv[6] = "--";
5187 diff_index_argv[7] = "/dev/null";
5190 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5191 report("Failed to allocate diff command");
5192 break;
5194 flags |= OPEN_PREPARED;
5197 open_view(view, REQ_VIEW_DIFF, flags);
5198 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5199 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5200 break;
5202 default:
5203 return request;
5206 return REQ_NONE;
5209 static bool
5210 blame_grep(struct view *view, struct line *line)
5212 struct blame *blame = line->data;
5213 struct blame_commit *commit = blame->commit;
5214 const char *text[] = {
5215 blame->text,
5216 commit ? commit->title : "",
5217 commit ? commit->id : "",
5218 commit && opt_author ? commit->author : "",
5219 commit ? mkdate(&commit->time, opt_date) : "",
5220 NULL
5223 return grep_text(view, text);
5226 static void
5227 blame_select(struct view *view, struct line *line)
5229 struct blame *blame = line->data;
5230 struct blame_commit *commit = blame->commit;
5232 if (!commit)
5233 return;
5235 if (!strcmp(commit->id, NULL_ID))
5236 string_ncopy(ref_commit, "HEAD", 4);
5237 else
5238 string_copy_rev(ref_commit, commit->id);
5241 static struct view_ops blame_ops = {
5242 "line",
5243 NULL,
5244 blame_open,
5245 blame_read,
5246 blame_draw,
5247 blame_request,
5248 blame_grep,
5249 blame_select,
5253 * Branch backend
5256 struct branch {
5257 const char *author; /* Author of the last commit. */
5258 struct time time; /* Date of the last activity. */
5259 const struct ref *ref; /* Name and commit ID information. */
5262 static const struct ref branch_all;
5264 static const enum sort_field branch_sort_fields[] = {
5265 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5267 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5269 static int
5270 branch_compare(const void *l1, const void *l2)
5272 const struct branch *branch1 = ((const struct line *) l1)->data;
5273 const struct branch *branch2 = ((const struct line *) l2)->data;
5275 switch (get_sort_field(branch_sort_state)) {
5276 case ORDERBY_DATE:
5277 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5279 case ORDERBY_AUTHOR:
5280 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5282 case ORDERBY_NAME:
5283 default:
5284 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5288 static bool
5289 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5291 struct branch *branch = line->data;
5292 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5294 if (opt_date && draw_date(view, &branch->time))
5295 return TRUE;
5297 if (opt_author && draw_author(view, branch->author))
5298 return TRUE;
5300 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5301 return TRUE;
5304 static enum request
5305 branch_request(struct view *view, enum request request, struct line *line)
5307 struct branch *branch = line->data;
5309 switch (request) {
5310 case REQ_REFRESH:
5311 load_refs();
5312 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5313 return REQ_NONE;
5315 case REQ_TOGGLE_SORT_FIELD:
5316 case REQ_TOGGLE_SORT_ORDER:
5317 sort_view(view, request, &branch_sort_state, branch_compare);
5318 return REQ_NONE;
5320 case REQ_ENTER:
5321 if (branch->ref == &branch_all) {
5322 const char *all_branches_argv[] = {
5323 "git", "log", "--no-color", "--pretty=raw", "--parents",
5324 "--topo-order", "--all", NULL
5326 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5328 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5329 report("Failed to load view of all branches");
5330 return REQ_NONE;
5332 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5333 } else {
5334 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5336 return REQ_NONE;
5338 default:
5339 return request;
5343 static bool
5344 branch_read(struct view *view, char *line)
5346 static char id[SIZEOF_REV];
5347 struct branch *reference;
5348 size_t i;
5350 if (!line)
5351 return TRUE;
5353 switch (get_line_type(line)) {
5354 case LINE_COMMIT:
5355 string_copy_rev(id, line + STRING_SIZE("commit "));
5356 return TRUE;
5358 case LINE_AUTHOR:
5359 for (i = 0, reference = NULL; i < view->lines; i++) {
5360 struct branch *branch = view->line[i].data;
5362 if (strcmp(branch->ref->id, id))
5363 continue;
5365 view->line[i].dirty = TRUE;
5366 if (reference) {
5367 branch->author = reference->author;
5368 branch->time = reference->time;
5369 continue;
5372 parse_author_line(line + STRING_SIZE("author "),
5373 &branch->author, &branch->time);
5374 reference = branch;
5376 return TRUE;
5378 default:
5379 return TRUE;
5384 static bool
5385 branch_open_visitor(void *data, const struct ref *ref)
5387 struct view *view = data;
5388 struct branch *branch;
5390 if (ref->tag || ref->ltag || ref->remote)
5391 return TRUE;
5393 branch = calloc(1, sizeof(*branch));
5394 if (!branch)
5395 return FALSE;
5397 branch->ref = ref;
5398 return !!add_line_data(view, branch, LINE_DEFAULT);
5401 static bool
5402 branch_open(struct view *view)
5404 const char *branch_log[] = {
5405 "git", "log", "--no-color", "--pretty=raw",
5406 "--simplify-by-decoration", "--all", NULL
5409 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5410 report("Failed to load branch data");
5411 return TRUE;
5414 setup_update(view, view->id);
5415 branch_open_visitor(view, &branch_all);
5416 foreach_ref(branch_open_visitor, view);
5417 view->p_restore = TRUE;
5419 return TRUE;
5422 static bool
5423 branch_grep(struct view *view, struct line *line)
5425 struct branch *branch = line->data;
5426 const char *text[] = {
5427 branch->ref->name,
5428 branch->author,
5429 NULL
5432 return grep_text(view, text);
5435 static void
5436 branch_select(struct view *view, struct line *line)
5438 struct branch *branch = line->data;
5440 string_copy_rev(view->ref, branch->ref->id);
5441 string_copy_rev(ref_commit, branch->ref->id);
5442 string_copy_rev(ref_head, branch->ref->id);
5445 static struct view_ops branch_ops = {
5446 "branch",
5447 NULL,
5448 branch_open,
5449 branch_read,
5450 branch_draw,
5451 branch_request,
5452 branch_grep,
5453 branch_select,
5457 * Status backend
5460 struct status {
5461 char status;
5462 struct {
5463 mode_t mode;
5464 char rev[SIZEOF_REV];
5465 char name[SIZEOF_STR];
5466 } old;
5467 struct {
5468 mode_t mode;
5469 char rev[SIZEOF_REV];
5470 char name[SIZEOF_STR];
5471 } new;
5474 static char status_onbranch[SIZEOF_STR];
5475 static struct status stage_status;
5476 static enum line_type stage_line_type;
5477 static size_t stage_chunks;
5478 static int *stage_chunk;
5480 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5482 /* This should work even for the "On branch" line. */
5483 static inline bool
5484 status_has_none(struct view *view, struct line *line)
5486 return line < view->line + view->lines && !line[1].data;
5489 /* Get fields from the diff line:
5490 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5492 static inline bool
5493 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5495 const char *old_mode = buf + 1;
5496 const char *new_mode = buf + 8;
5497 const char *old_rev = buf + 15;
5498 const char *new_rev = buf + 56;
5499 const char *status = buf + 97;
5501 if (bufsize < 98 ||
5502 old_mode[-1] != ':' ||
5503 new_mode[-1] != ' ' ||
5504 old_rev[-1] != ' ' ||
5505 new_rev[-1] != ' ' ||
5506 status[-1] != ' ')
5507 return FALSE;
5509 file->status = *status;
5511 string_copy_rev(file->old.rev, old_rev);
5512 string_copy_rev(file->new.rev, new_rev);
5514 file->old.mode = strtoul(old_mode, NULL, 8);
5515 file->new.mode = strtoul(new_mode, NULL, 8);
5517 file->old.name[0] = file->new.name[0] = 0;
5519 return TRUE;
5522 static bool
5523 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5525 struct status *unmerged = NULL;
5526 char *buf;
5527 struct io io = {};
5529 if (!io_run(&io, argv, opt_cdup, IO_RD))
5530 return FALSE;
5532 add_line_data(view, NULL, type);
5534 while ((buf = io_get(&io, 0, TRUE))) {
5535 struct status *file = unmerged;
5537 if (!file) {
5538 file = calloc(1, sizeof(*file));
5539 if (!file || !add_line_data(view, file, type))
5540 goto error_out;
5543 /* Parse diff info part. */
5544 if (status) {
5545 file->status = status;
5546 if (status == 'A')
5547 string_copy(file->old.rev, NULL_ID);
5549 } else if (!file->status || file == unmerged) {
5550 if (!status_get_diff(file, buf, strlen(buf)))
5551 goto error_out;
5553 buf = io_get(&io, 0, TRUE);
5554 if (!buf)
5555 break;
5557 /* Collapse all modified entries that follow an
5558 * associated unmerged entry. */
5559 if (unmerged == file) {
5560 unmerged->status = 'U';
5561 unmerged = NULL;
5562 } else if (file->status == 'U') {
5563 unmerged = file;
5567 /* Grab the old name for rename/copy. */
5568 if (!*file->old.name &&
5569 (file->status == 'R' || file->status == 'C')) {
5570 string_ncopy(file->old.name, buf, strlen(buf));
5572 buf = io_get(&io, 0, TRUE);
5573 if (!buf)
5574 break;
5577 /* git-ls-files just delivers a NUL separated list of
5578 * file names similar to the second half of the
5579 * git-diff-* output. */
5580 string_ncopy(file->new.name, buf, strlen(buf));
5581 if (!*file->old.name)
5582 string_copy(file->old.name, file->new.name);
5583 file = NULL;
5586 if (io_error(&io)) {
5587 error_out:
5588 io_done(&io);
5589 return FALSE;
5592 if (!view->line[view->lines - 1].data)
5593 add_line_data(view, NULL, LINE_STAT_NONE);
5595 io_done(&io);
5596 return TRUE;
5599 /* Don't show unmerged entries in the staged section. */
5600 static const char *status_diff_index_argv[] = {
5601 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5602 "--cached", "-M", "HEAD", NULL
5605 static const char *status_diff_files_argv[] = {
5606 "git", "diff-files", "-z", NULL
5609 static const char *status_list_other_argv[] = {
5610 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5613 static const char *status_list_no_head_argv[] = {
5614 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5617 static const char *update_index_argv[] = {
5618 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5621 /* Restore the previous line number to stay in the context or select a
5622 * line with something that can be updated. */
5623 static void
5624 status_restore(struct view *view)
5626 if (view->p_lineno >= view->lines)
5627 view->p_lineno = view->lines - 1;
5628 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5629 view->p_lineno++;
5630 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5631 view->p_lineno--;
5633 /* If the above fails, always skip the "On branch" line. */
5634 if (view->p_lineno < view->lines)
5635 view->lineno = view->p_lineno;
5636 else
5637 view->lineno = 1;
5639 if (view->lineno < view->offset)
5640 view->offset = view->lineno;
5641 else if (view->offset + view->height <= view->lineno)
5642 view->offset = view->lineno - view->height + 1;
5644 view->p_restore = FALSE;
5647 static void
5648 status_update_onbranch(void)
5650 static const char *paths[][2] = {
5651 { "rebase-apply/rebasing", "Rebasing" },
5652 { "rebase-apply/applying", "Applying mailbox" },
5653 { "rebase-apply/", "Rebasing mailbox" },
5654 { "rebase-merge/interactive", "Interactive rebase" },
5655 { "rebase-merge/", "Rebase merge" },
5656 { "MERGE_HEAD", "Merging" },
5657 { "BISECT_LOG", "Bisecting" },
5658 { "HEAD", "On branch" },
5660 char buf[SIZEOF_STR];
5661 struct stat stat;
5662 int i;
5664 if (is_initial_commit()) {
5665 string_copy(status_onbranch, "Initial commit");
5666 return;
5669 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5670 char *head = opt_head;
5672 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5673 lstat(buf, &stat) < 0)
5674 continue;
5676 if (!*opt_head) {
5677 struct io io = {};
5679 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5680 io_read_buf(&io, buf, sizeof(buf))) {
5681 head = buf;
5682 if (!prefixcmp(head, "refs/heads/"))
5683 head += STRING_SIZE("refs/heads/");
5687 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5688 string_copy(status_onbranch, opt_head);
5689 return;
5692 string_copy(status_onbranch, "Not currently on any branch");
5695 /* First parse staged info using git-diff-index(1), then parse unstaged
5696 * info using git-diff-files(1), and finally untracked files using
5697 * git-ls-files(1). */
5698 static bool
5699 status_open(struct view *view)
5701 reset_view(view);
5703 add_line_data(view, NULL, LINE_STAT_HEAD);
5704 status_update_onbranch();
5706 io_run_bg(update_index_argv);
5708 if (is_initial_commit()) {
5709 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5710 return FALSE;
5711 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5712 return FALSE;
5715 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5716 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5717 return FALSE;
5719 /* Restore the exact position or use the specialized restore
5720 * mode? */
5721 if (!view->p_restore)
5722 status_restore(view);
5723 return TRUE;
5726 static bool
5727 status_draw(struct view *view, struct line *line, unsigned int lineno)
5729 struct status *status = line->data;
5730 enum line_type type;
5731 const char *text;
5733 if (!status) {
5734 switch (line->type) {
5735 case LINE_STAT_STAGED:
5736 type = LINE_STAT_SECTION;
5737 text = "Changes to be committed:";
5738 break;
5740 case LINE_STAT_UNSTAGED:
5741 type = LINE_STAT_SECTION;
5742 text = "Changed but not updated:";
5743 break;
5745 case LINE_STAT_UNTRACKED:
5746 type = LINE_STAT_SECTION;
5747 text = "Untracked files:";
5748 break;
5750 case LINE_STAT_NONE:
5751 type = LINE_DEFAULT;
5752 text = " (no files)";
5753 break;
5755 case LINE_STAT_HEAD:
5756 type = LINE_STAT_HEAD;
5757 text = status_onbranch;
5758 break;
5760 default:
5761 return FALSE;
5763 } else {
5764 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5766 buf[0] = status->status;
5767 if (draw_text(view, line->type, buf, TRUE))
5768 return TRUE;
5769 type = LINE_DEFAULT;
5770 text = status->new.name;
5773 draw_text(view, type, text, TRUE);
5774 return TRUE;
5777 static enum request
5778 status_load_error(struct view *view, struct view *stage, const char *path)
5780 if (displayed_views() == 2 || display[current_view] != view)
5781 maximize_view(view);
5782 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5783 return REQ_NONE;
5786 static enum request
5787 status_enter(struct view *view, struct line *line)
5789 struct status *status = line->data;
5790 const char *oldpath = status ? status->old.name : NULL;
5791 /* Diffs for unmerged entries are empty when passing the new
5792 * path, so leave it empty. */
5793 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5794 const char *info;
5795 enum open_flags split;
5796 struct view *stage = VIEW(REQ_VIEW_STAGE);
5798 if (line->type == LINE_STAT_NONE ||
5799 (!status && line[1].type == LINE_STAT_NONE)) {
5800 report("No file to diff");
5801 return REQ_NONE;
5804 switch (line->type) {
5805 case LINE_STAT_STAGED:
5806 if (is_initial_commit()) {
5807 const char *no_head_diff_argv[] = {
5808 "git", "diff", "--no-color", "--patch-with-stat",
5809 "--", "/dev/null", newpath, NULL
5812 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5813 return status_load_error(view, stage, newpath);
5814 } else {
5815 const char *index_show_argv[] = {
5816 "git", "diff-index", "--root", "--patch-with-stat",
5817 "-C", "-M", "--cached", "HEAD", "--",
5818 oldpath, newpath, NULL
5821 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5822 return status_load_error(view, stage, newpath);
5825 if (status)
5826 info = "Staged changes to %s";
5827 else
5828 info = "Staged changes";
5829 break;
5831 case LINE_STAT_UNSTAGED:
5833 const char *files_show_argv[] = {
5834 "git", "diff-files", "--root", "--patch-with-stat",
5835 "-C", "-M", "--", oldpath, newpath, NULL
5838 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5839 return status_load_error(view, stage, newpath);
5840 if (status)
5841 info = "Unstaged changes to %s";
5842 else
5843 info = "Unstaged changes";
5844 break;
5846 case LINE_STAT_UNTRACKED:
5847 if (!newpath) {
5848 report("No file to show");
5849 return REQ_NONE;
5852 if (!suffixcmp(status->new.name, -1, "/")) {
5853 report("Cannot display a directory");
5854 return REQ_NONE;
5857 if (!prepare_update_file(stage, newpath))
5858 return status_load_error(view, stage, newpath);
5859 info = "Untracked file %s";
5860 break;
5862 case LINE_STAT_HEAD:
5863 return REQ_NONE;
5865 default:
5866 die("line type %d not handled in switch", line->type);
5869 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5870 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5871 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5872 if (status) {
5873 stage_status = *status;
5874 } else {
5875 memset(&stage_status, 0, sizeof(stage_status));
5878 stage_line_type = line->type;
5879 stage_chunks = 0;
5880 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5883 return REQ_NONE;
5886 static bool
5887 status_exists(struct status *status, enum line_type type)
5889 struct view *view = VIEW(REQ_VIEW_STATUS);
5890 unsigned long lineno;
5892 for (lineno = 0; lineno < view->lines; lineno++) {
5893 struct line *line = &view->line[lineno];
5894 struct status *pos = line->data;
5896 if (line->type != type)
5897 continue;
5898 if (!pos && (!status || !status->status) && line[1].data) {
5899 select_view_line(view, lineno);
5900 return TRUE;
5902 if (pos && !strcmp(status->new.name, pos->new.name)) {
5903 select_view_line(view, lineno);
5904 return TRUE;
5908 return FALSE;
5912 static bool
5913 status_update_prepare(struct io *io, enum line_type type)
5915 const char *staged_argv[] = {
5916 "git", "update-index", "-z", "--index-info", NULL
5918 const char *others_argv[] = {
5919 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5922 switch (type) {
5923 case LINE_STAT_STAGED:
5924 return io_run(io, staged_argv, opt_cdup, IO_WR);
5926 case LINE_STAT_UNSTAGED:
5927 case LINE_STAT_UNTRACKED:
5928 return io_run(io, others_argv, opt_cdup, IO_WR);
5930 default:
5931 die("line type %d not handled in switch", type);
5932 return FALSE;
5936 static bool
5937 status_update_write(struct io *io, struct status *status, enum line_type type)
5939 char buf[SIZEOF_STR];
5940 size_t bufsize = 0;
5942 switch (type) {
5943 case LINE_STAT_STAGED:
5944 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5945 status->old.mode,
5946 status->old.rev,
5947 status->old.name, 0))
5948 return FALSE;
5949 break;
5951 case LINE_STAT_UNSTAGED:
5952 case LINE_STAT_UNTRACKED:
5953 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5954 return FALSE;
5955 break;
5957 default:
5958 die("line type %d not handled in switch", type);
5961 return io_write(io, buf, bufsize);
5964 static bool
5965 status_update_file(struct status *status, enum line_type type)
5967 struct io io = {};
5968 bool result;
5970 if (!status_update_prepare(&io, type))
5971 return FALSE;
5973 result = status_update_write(&io, status, type);
5974 return io_done(&io) && result;
5977 static bool
5978 status_update_files(struct view *view, struct line *line)
5980 char buf[sizeof(view->ref)];
5981 struct io io = {};
5982 bool result = TRUE;
5983 struct line *pos = view->line + view->lines;
5984 int files = 0;
5985 int file, done;
5986 int cursor_y = -1, cursor_x = -1;
5988 if (!status_update_prepare(&io, line->type))
5989 return FALSE;
5991 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5992 files++;
5994 string_copy(buf, view->ref);
5995 getsyx(cursor_y, cursor_x);
5996 for (file = 0, done = 5; result && file < files; line++, file++) {
5997 int almost_done = file * 100 / files;
5999 if (almost_done > done) {
6000 done = almost_done;
6001 string_format(view->ref, "updating file %u of %u (%d%% done)",
6002 file, files, done);
6003 update_view_title(view);
6004 setsyx(cursor_y, cursor_x);
6005 doupdate();
6007 result = status_update_write(&io, line->data, line->type);
6009 string_copy(view->ref, buf);
6011 return io_done(&io) && result;
6014 static bool
6015 status_update(struct view *view)
6017 struct line *line = &view->line[view->lineno];
6019 assert(view->lines);
6021 if (!line->data) {
6022 /* This should work even for the "On branch" line. */
6023 if (line < view->line + view->lines && !line[1].data) {
6024 report("Nothing to update");
6025 return FALSE;
6028 if (!status_update_files(view, line + 1)) {
6029 report("Failed to update file status");
6030 return FALSE;
6033 } else if (!status_update_file(line->data, line->type)) {
6034 report("Failed to update file status");
6035 return FALSE;
6038 return TRUE;
6041 static bool
6042 status_revert(struct status *status, enum line_type type, bool has_none)
6044 if (!status || type != LINE_STAT_UNSTAGED) {
6045 if (type == LINE_STAT_STAGED) {
6046 report("Cannot revert changes to staged files");
6047 } else if (type == LINE_STAT_UNTRACKED) {
6048 report("Cannot revert changes to untracked files");
6049 } else if (has_none) {
6050 report("Nothing to revert");
6051 } else {
6052 report("Cannot revert changes to multiple files");
6055 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6056 char mode[10] = "100644";
6057 const char *reset_argv[] = {
6058 "git", "update-index", "--cacheinfo", mode,
6059 status->old.rev, status->old.name, NULL
6061 const char *checkout_argv[] = {
6062 "git", "checkout", "--", status->old.name, NULL
6065 if (status->status == 'U') {
6066 string_format(mode, "%5o", status->old.mode);
6068 if (status->old.mode == 0 && status->new.mode == 0) {
6069 reset_argv[2] = "--force-remove";
6070 reset_argv[3] = status->old.name;
6071 reset_argv[4] = NULL;
6074 if (!io_run_fg(reset_argv, opt_cdup))
6075 return FALSE;
6076 if (status->old.mode == 0 && status->new.mode == 0)
6077 return TRUE;
6080 return io_run_fg(checkout_argv, opt_cdup);
6083 return FALSE;
6086 static enum request
6087 status_request(struct view *view, enum request request, struct line *line)
6089 struct status *status = line->data;
6091 switch (request) {
6092 case REQ_STATUS_UPDATE:
6093 if (!status_update(view))
6094 return REQ_NONE;
6095 break;
6097 case REQ_STATUS_REVERT:
6098 if (!status_revert(status, line->type, status_has_none(view, line)))
6099 return REQ_NONE;
6100 break;
6102 case REQ_STATUS_MERGE:
6103 if (!status || status->status != 'U') {
6104 report("Merging only possible for files with unmerged status ('U').");
6105 return REQ_NONE;
6107 open_mergetool(status->new.name);
6108 break;
6110 case REQ_EDIT:
6111 if (!status)
6112 return request;
6113 if (status->status == 'D') {
6114 report("File has been deleted.");
6115 return REQ_NONE;
6118 open_editor(status->new.name);
6119 break;
6121 case REQ_VIEW_BLAME:
6122 if (status)
6123 opt_ref[0] = 0;
6124 return request;
6126 case REQ_ENTER:
6127 /* After returning the status view has been split to
6128 * show the stage view. No further reloading is
6129 * necessary. */
6130 return status_enter(view, line);
6132 case REQ_REFRESH:
6133 /* Simply reload the view. */
6134 break;
6136 default:
6137 return request;
6140 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6142 return REQ_NONE;
6145 static void
6146 status_select(struct view *view, struct line *line)
6148 struct status *status = line->data;
6149 char file[SIZEOF_STR] = "all files";
6150 const char *text;
6151 const char *key;
6153 if (status && !string_format(file, "'%s'", status->new.name))
6154 return;
6156 if (!status && line[1].type == LINE_STAT_NONE)
6157 line++;
6159 switch (line->type) {
6160 case LINE_STAT_STAGED:
6161 text = "Press %s to unstage %s for commit";
6162 break;
6164 case LINE_STAT_UNSTAGED:
6165 text = "Press %s to stage %s for commit";
6166 break;
6168 case LINE_STAT_UNTRACKED:
6169 text = "Press %s to stage %s for addition";
6170 break;
6172 case LINE_STAT_HEAD:
6173 case LINE_STAT_NONE:
6174 text = "Nothing to update";
6175 break;
6177 default:
6178 die("line type %d not handled in switch", line->type);
6181 if (status && status->status == 'U') {
6182 text = "Press %s to resolve conflict in %s";
6183 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6185 } else {
6186 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6189 string_format(view->ref, text, key, file);
6190 if (status)
6191 string_copy(opt_file, status->new.name);
6194 static bool
6195 status_grep(struct view *view, struct line *line)
6197 struct status *status = line->data;
6199 if (status) {
6200 const char buf[2] = { status->status, 0 };
6201 const char *text[] = { status->new.name, buf, NULL };
6203 return grep_text(view, text);
6206 return FALSE;
6209 static struct view_ops status_ops = {
6210 "file",
6211 NULL,
6212 status_open,
6213 NULL,
6214 status_draw,
6215 status_request,
6216 status_grep,
6217 status_select,
6221 static bool
6222 stage_diff_write(struct io *io, struct line *line, struct line *end)
6224 while (line < end) {
6225 if (!io_write(io, line->data, strlen(line->data)) ||
6226 !io_write(io, "\n", 1))
6227 return FALSE;
6228 line++;
6229 if (line->type == LINE_DIFF_CHUNK ||
6230 line->type == LINE_DIFF_HEADER)
6231 break;
6234 return TRUE;
6237 static struct line *
6238 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6240 for (; view->line < line; line--)
6241 if (line->type == type)
6242 return line;
6244 return NULL;
6247 static bool
6248 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6250 const char *apply_argv[SIZEOF_ARG] = {
6251 "git", "apply", "--whitespace=nowarn", NULL
6253 struct line *diff_hdr;
6254 struct io io = {};
6255 int argc = 3;
6257 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6258 if (!diff_hdr)
6259 return FALSE;
6261 if (!revert)
6262 apply_argv[argc++] = "--cached";
6263 if (revert || stage_line_type == LINE_STAT_STAGED)
6264 apply_argv[argc++] = "-R";
6265 apply_argv[argc++] = "-";
6266 apply_argv[argc++] = NULL;
6267 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6268 return FALSE;
6270 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6271 !stage_diff_write(&io, chunk, view->line + view->lines))
6272 chunk = NULL;
6274 io_done(&io);
6275 io_run_bg(update_index_argv);
6277 return chunk ? TRUE : FALSE;
6280 static bool
6281 stage_update(struct view *view, struct line *line)
6283 struct line *chunk = NULL;
6285 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6286 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6288 if (chunk) {
6289 if (!stage_apply_chunk(view, chunk, FALSE)) {
6290 report("Failed to apply chunk");
6291 return FALSE;
6294 } else if (!stage_status.status) {
6295 view = VIEW(REQ_VIEW_STATUS);
6297 for (line = view->line; line < view->line + view->lines; line++)
6298 if (line->type == stage_line_type)
6299 break;
6301 if (!status_update_files(view, line + 1)) {
6302 report("Failed to update files");
6303 return FALSE;
6306 } else if (!status_update_file(&stage_status, stage_line_type)) {
6307 report("Failed to update file");
6308 return FALSE;
6311 return TRUE;
6314 static bool
6315 stage_revert(struct view *view, struct line *line)
6317 struct line *chunk = NULL;
6319 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6320 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6322 if (chunk) {
6323 if (!prompt_yesno("Are you sure you want to revert changes?"))
6324 return FALSE;
6326 if (!stage_apply_chunk(view, chunk, TRUE)) {
6327 report("Failed to revert chunk");
6328 return FALSE;
6330 return TRUE;
6332 } else {
6333 return status_revert(stage_status.status ? &stage_status : NULL,
6334 stage_line_type, FALSE);
6339 static void
6340 stage_next(struct view *view, struct line *line)
6342 int i;
6344 if (!stage_chunks) {
6345 for (line = view->line; line < view->line + view->lines; line++) {
6346 if (line->type != LINE_DIFF_CHUNK)
6347 continue;
6349 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6350 report("Allocation failure");
6351 return;
6354 stage_chunk[stage_chunks++] = line - view->line;
6358 for (i = 0; i < stage_chunks; i++) {
6359 if (stage_chunk[i] > view->lineno) {
6360 do_scroll_view(view, stage_chunk[i] - view->lineno);
6361 report("Chunk %d of %d", i + 1, stage_chunks);
6362 return;
6366 report("No next chunk found");
6369 static enum request
6370 stage_request(struct view *view, enum request request, struct line *line)
6372 switch (request) {
6373 case REQ_STATUS_UPDATE:
6374 if (!stage_update(view, line))
6375 return REQ_NONE;
6376 break;
6378 case REQ_STATUS_REVERT:
6379 if (!stage_revert(view, line))
6380 return REQ_NONE;
6381 break;
6383 case REQ_STAGE_NEXT:
6384 if (stage_line_type == LINE_STAT_UNTRACKED) {
6385 report("File is untracked; press %s to add",
6386 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6387 return REQ_NONE;
6389 stage_next(view, line);
6390 return REQ_NONE;
6392 case REQ_EDIT:
6393 if (!stage_status.new.name[0])
6394 return request;
6395 if (stage_status.status == 'D') {
6396 report("File has been deleted.");
6397 return REQ_NONE;
6400 open_editor(stage_status.new.name);
6401 break;
6403 case REQ_REFRESH:
6404 /* Reload everything ... */
6405 break;
6407 case REQ_VIEW_BLAME:
6408 if (stage_status.new.name[0]) {
6409 string_copy(opt_file, stage_status.new.name);
6410 opt_ref[0] = 0;
6412 return request;
6414 case REQ_ENTER:
6415 return pager_request(view, request, line);
6417 default:
6418 return request;
6421 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6422 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6424 /* Check whether the staged entry still exists, and close the
6425 * stage view if it doesn't. */
6426 if (!status_exists(&stage_status, stage_line_type)) {
6427 status_restore(VIEW(REQ_VIEW_STATUS));
6428 return REQ_VIEW_CLOSE;
6431 if (stage_line_type == LINE_STAT_UNTRACKED) {
6432 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6433 report("Cannot display a directory");
6434 return REQ_NONE;
6437 if (!prepare_update_file(view, stage_status.new.name)) {
6438 report("Failed to open file: %s", strerror(errno));
6439 return REQ_NONE;
6442 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6444 return REQ_NONE;
6447 static struct view_ops stage_ops = {
6448 "line",
6449 NULL,
6450 NULL,
6451 pager_read,
6452 pager_draw,
6453 stage_request,
6454 pager_grep,
6455 pager_select,
6460 * Revision graph
6463 struct commit {
6464 char id[SIZEOF_REV]; /* SHA1 ID. */
6465 char title[128]; /* First line of the commit message. */
6466 const char *author; /* Author of the commit. */
6467 struct time time; /* Date from the author ident. */
6468 struct ref_list *refs; /* Repository references. */
6469 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6470 size_t graph_size; /* The width of the graph array. */
6471 bool has_parents; /* Rewritten --parents seen. */
6474 /* Size of rev graph with no "padding" columns */
6475 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6477 struct rev_graph {
6478 struct rev_graph *prev, *next, *parents;
6479 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6480 size_t size;
6481 struct commit *commit;
6482 size_t pos;
6483 unsigned int boundary:1;
6486 /* Parents of the commit being visualized. */
6487 static struct rev_graph graph_parents[4];
6489 /* The current stack of revisions on the graph. */
6490 static struct rev_graph graph_stacks[4] = {
6491 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6492 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6493 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6494 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6497 static inline bool
6498 graph_parent_is_merge(struct rev_graph *graph)
6500 return graph->parents->size > 1;
6503 static inline void
6504 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6506 struct commit *commit = graph->commit;
6508 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6509 commit->graph[commit->graph_size++] = symbol;
6512 static void
6513 clear_rev_graph(struct rev_graph *graph)
6515 graph->boundary = 0;
6516 graph->size = graph->pos = 0;
6517 graph->commit = NULL;
6518 memset(graph->parents, 0, sizeof(*graph->parents));
6521 static void
6522 done_rev_graph(struct rev_graph *graph)
6524 if (graph_parent_is_merge(graph) &&
6525 graph->pos < graph->size - 1 &&
6526 graph->next->size == graph->size + graph->parents->size - 1) {
6527 size_t i = graph->pos + graph->parents->size - 1;
6529 graph->commit->graph_size = i * 2;
6530 while (i < graph->next->size - 1) {
6531 append_to_rev_graph(graph, ' ');
6532 append_to_rev_graph(graph, '\\');
6533 i++;
6537 clear_rev_graph(graph);
6540 static void
6541 push_rev_graph(struct rev_graph *graph, const char *parent)
6543 int i;
6545 /* "Collapse" duplicate parents lines.
6547 * FIXME: This needs to also update update the drawn graph but
6548 * for now it just serves as a method for pruning graph lines. */
6549 for (i = 0; i < graph->size; i++)
6550 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6551 return;
6553 if (graph->size < SIZEOF_REVITEMS) {
6554 string_copy_rev(graph->rev[graph->size++], parent);
6558 static chtype
6559 get_rev_graph_symbol(struct rev_graph *graph)
6561 chtype symbol;
6563 if (graph->boundary)
6564 symbol = REVGRAPH_BOUND;
6565 else if (graph->parents->size == 0)
6566 symbol = REVGRAPH_INIT;
6567 else if (graph_parent_is_merge(graph))
6568 symbol = REVGRAPH_MERGE;
6569 else if (graph->pos >= graph->size)
6570 symbol = REVGRAPH_BRANCH;
6571 else
6572 symbol = REVGRAPH_COMMIT;
6574 return symbol;
6577 static void
6578 draw_rev_graph(struct rev_graph *graph)
6580 struct rev_filler {
6581 chtype separator, line;
6583 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6584 static struct rev_filler fillers[] = {
6585 { ' ', '|' },
6586 { '`', '.' },
6587 { '\'', ' ' },
6588 { '/', ' ' },
6590 chtype symbol = get_rev_graph_symbol(graph);
6591 struct rev_filler *filler;
6592 size_t i;
6594 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6595 filler = &fillers[DEFAULT];
6597 for (i = 0; i < graph->pos; i++) {
6598 append_to_rev_graph(graph, filler->line);
6599 if (graph_parent_is_merge(graph->prev) &&
6600 graph->prev->pos == i)
6601 filler = &fillers[RSHARP];
6603 append_to_rev_graph(graph, filler->separator);
6606 /* Place the symbol for this revision. */
6607 append_to_rev_graph(graph, symbol);
6609 if (graph->prev->size > graph->size)
6610 filler = &fillers[RDIAG];
6611 else
6612 filler = &fillers[DEFAULT];
6614 i++;
6616 for (; i < graph->size; i++) {
6617 append_to_rev_graph(graph, filler->separator);
6618 append_to_rev_graph(graph, filler->line);
6619 if (graph_parent_is_merge(graph->prev) &&
6620 i < graph->prev->pos + graph->parents->size)
6621 filler = &fillers[RSHARP];
6622 if (graph->prev->size > graph->size)
6623 filler = &fillers[LDIAG];
6626 if (graph->prev->size > graph->size) {
6627 append_to_rev_graph(graph, filler->separator);
6628 if (filler->line != ' ')
6629 append_to_rev_graph(graph, filler->line);
6633 /* Prepare the next rev graph */
6634 static void
6635 prepare_rev_graph(struct rev_graph *graph)
6637 size_t i;
6639 /* First, traverse all lines of revisions up to the active one. */
6640 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6641 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6642 break;
6644 push_rev_graph(graph->next, graph->rev[graph->pos]);
6647 /* Interleave the new revision parent(s). */
6648 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6649 push_rev_graph(graph->next, graph->parents->rev[i]);
6651 /* Lastly, put any remaining revisions. */
6652 for (i = graph->pos + 1; i < graph->size; i++)
6653 push_rev_graph(graph->next, graph->rev[i]);
6656 static void
6657 update_rev_graph(struct view *view, struct rev_graph *graph)
6659 /* If this is the finalizing update ... */
6660 if (graph->commit)
6661 prepare_rev_graph(graph);
6663 /* Graph visualization needs a one rev look-ahead,
6664 * so the first update doesn't visualize anything. */
6665 if (!graph->prev->commit)
6666 return;
6668 if (view->lines > 2)
6669 view->line[view->lines - 3].dirty = 1;
6670 if (view->lines > 1)
6671 view->line[view->lines - 2].dirty = 1;
6672 draw_rev_graph(graph->prev);
6673 done_rev_graph(graph->prev->prev);
6678 * Main view backend
6681 static const char *main_argv[SIZEOF_ARG] = {
6682 "git", "log", "--no-color", "--pretty=raw", "--parents",
6683 "--topo-order", "%(head)", NULL
6686 static bool
6687 main_draw(struct view *view, struct line *line, unsigned int lineno)
6689 struct commit *commit = line->data;
6691 if (!commit->author)
6692 return FALSE;
6694 if (opt_date && draw_date(view, &commit->time))
6695 return TRUE;
6697 if (opt_author && draw_author(view, commit->author))
6698 return TRUE;
6700 if (opt_rev_graph && commit->graph_size &&
6701 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6702 return TRUE;
6704 if (opt_show_refs && commit->refs) {
6705 size_t i;
6707 for (i = 0; i < commit->refs->size; i++) {
6708 struct ref *ref = commit->refs->refs[i];
6709 enum line_type type;
6711 if (ref->head)
6712 type = LINE_MAIN_HEAD;
6713 else if (ref->ltag)
6714 type = LINE_MAIN_LOCAL_TAG;
6715 else if (ref->tag)
6716 type = LINE_MAIN_TAG;
6717 else if (ref->tracked)
6718 type = LINE_MAIN_TRACKED;
6719 else if (ref->remote)
6720 type = LINE_MAIN_REMOTE;
6721 else
6722 type = LINE_MAIN_REF;
6724 if (draw_text(view, type, "[", TRUE) ||
6725 draw_text(view, type, ref->name, TRUE) ||
6726 draw_text(view, type, "]", TRUE))
6727 return TRUE;
6729 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6730 return TRUE;
6734 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6735 return TRUE;
6738 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6739 static bool
6740 main_read(struct view *view, char *line)
6742 static struct rev_graph *graph = graph_stacks;
6743 enum line_type type;
6744 struct commit *commit;
6746 if (!line) {
6747 int i;
6749 if (!view->lines && !view->parent)
6750 die("No revisions match the given arguments.");
6751 if (view->lines > 0) {
6752 commit = view->line[view->lines - 1].data;
6753 view->line[view->lines - 1].dirty = 1;
6754 if (!commit->author) {
6755 view->lines--;
6756 free(commit);
6757 graph->commit = NULL;
6760 update_rev_graph(view, graph);
6762 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6763 clear_rev_graph(&graph_stacks[i]);
6764 return TRUE;
6767 type = get_line_type(line);
6768 if (type == LINE_COMMIT) {
6769 commit = calloc(1, sizeof(struct commit));
6770 if (!commit)
6771 return FALSE;
6773 line += STRING_SIZE("commit ");
6774 if (*line == '-') {
6775 graph->boundary = 1;
6776 line++;
6779 string_copy_rev(commit->id, line);
6780 commit->refs = get_ref_list(commit->id);
6781 graph->commit = commit;
6782 add_line_data(view, commit, LINE_MAIN_COMMIT);
6784 while ((line = strchr(line, ' '))) {
6785 line++;
6786 push_rev_graph(graph->parents, line);
6787 commit->has_parents = TRUE;
6789 return TRUE;
6792 if (!view->lines)
6793 return TRUE;
6794 commit = view->line[view->lines - 1].data;
6796 switch (type) {
6797 case LINE_PARENT:
6798 if (commit->has_parents)
6799 break;
6800 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6801 break;
6803 case LINE_AUTHOR:
6804 parse_author_line(line + STRING_SIZE("author "),
6805 &commit->author, &commit->time);
6806 update_rev_graph(view, graph);
6807 graph = graph->next;
6808 break;
6810 default:
6811 /* Fill in the commit title if it has not already been set. */
6812 if (commit->title[0])
6813 break;
6815 /* Require titles to start with a non-space character at the
6816 * offset used by git log. */
6817 if (strncmp(line, " ", 4))
6818 break;
6819 line += 4;
6820 /* Well, if the title starts with a whitespace character,
6821 * try to be forgiving. Otherwise we end up with no title. */
6822 while (isspace(*line))
6823 line++;
6824 if (*line == '\0')
6825 break;
6826 /* FIXME: More graceful handling of titles; append "..." to
6827 * shortened titles, etc. */
6829 string_expand(commit->title, sizeof(commit->title), line, 1);
6830 view->line[view->lines - 1].dirty = 1;
6833 return TRUE;
6836 static enum request
6837 main_request(struct view *view, enum request request, struct line *line)
6839 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6841 switch (request) {
6842 case REQ_ENTER:
6843 open_view(view, REQ_VIEW_DIFF, flags);
6844 break;
6845 case REQ_REFRESH:
6846 load_refs();
6847 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6848 break;
6849 default:
6850 return request;
6853 return REQ_NONE;
6856 static bool
6857 grep_refs(struct ref_list *list, regex_t *regex)
6859 regmatch_t pmatch;
6860 size_t i;
6862 if (!opt_show_refs || !list)
6863 return FALSE;
6865 for (i = 0; i < list->size; i++) {
6866 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6867 return TRUE;
6870 return FALSE;
6873 static bool
6874 main_grep(struct view *view, struct line *line)
6876 struct commit *commit = line->data;
6877 const char *text[] = {
6878 commit->title,
6879 opt_author ? commit->author : "",
6880 mkdate(&commit->time, opt_date),
6881 NULL
6884 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6887 static void
6888 main_select(struct view *view, struct line *line)
6890 struct commit *commit = line->data;
6892 string_copy_rev(view->ref, commit->id);
6893 string_copy_rev(ref_commit, view->ref);
6896 static struct view_ops main_ops = {
6897 "commit",
6898 main_argv,
6899 NULL,
6900 main_read,
6901 main_draw,
6902 main_request,
6903 main_grep,
6904 main_select,
6909 * Status management
6912 /* Whether or not the curses interface has been initialized. */
6913 static bool cursed = FALSE;
6915 /* Terminal hacks and workarounds. */
6916 static bool use_scroll_redrawwin;
6917 static bool use_scroll_status_wclear;
6919 /* The status window is used for polling keystrokes. */
6920 static WINDOW *status_win;
6922 /* Reading from the prompt? */
6923 static bool input_mode = FALSE;
6925 static bool status_empty = FALSE;
6927 /* Update status and title window. */
6928 static void
6929 report(const char *msg, ...)
6931 struct view *view = display[current_view];
6933 if (input_mode)
6934 return;
6936 if (!view) {
6937 char buf[SIZEOF_STR];
6938 va_list args;
6940 va_start(args, msg);
6941 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6942 buf[sizeof(buf) - 1] = 0;
6943 buf[sizeof(buf) - 2] = '.';
6944 buf[sizeof(buf) - 3] = '.';
6945 buf[sizeof(buf) - 4] = '.';
6947 va_end(args);
6948 die("%s", buf);
6951 if (!status_empty || *msg) {
6952 va_list args;
6954 va_start(args, msg);
6956 wmove(status_win, 0, 0);
6957 if (view->has_scrolled && use_scroll_status_wclear)
6958 wclear(status_win);
6959 if (*msg) {
6960 vwprintw(status_win, msg, args);
6961 status_empty = FALSE;
6962 } else {
6963 status_empty = TRUE;
6965 wclrtoeol(status_win);
6966 wnoutrefresh(status_win);
6968 va_end(args);
6971 update_view_title(view);
6974 static void
6975 init_display(void)
6977 const char *term;
6978 int x, y;
6980 /* Initialize the curses library */
6981 if (isatty(STDIN_FILENO)) {
6982 cursed = !!initscr();
6983 opt_tty = stdin;
6984 } else {
6985 /* Leave stdin and stdout alone when acting as a pager. */
6986 opt_tty = fopen("/dev/tty", "r+");
6987 if (!opt_tty)
6988 die("Failed to open /dev/tty");
6989 cursed = !!newterm(NULL, opt_tty, opt_tty);
6992 if (!cursed)
6993 die("Failed to initialize curses");
6995 nonl(); /* Disable conversion and detect newlines from input. */
6996 cbreak(); /* Take input chars one at a time, no wait for \n */
6997 noecho(); /* Don't echo input */
6998 leaveok(stdscr, FALSE);
7000 if (has_colors())
7001 init_colors();
7003 getmaxyx(stdscr, y, x);
7004 status_win = newwin(1, 0, y - 1, 0);
7005 if (!status_win)
7006 die("Failed to create status window");
7008 /* Enable keyboard mapping */
7009 keypad(status_win, TRUE);
7010 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7012 TABSIZE = opt_tab_size;
7014 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7015 if (term && !strcmp(term, "gnome-terminal")) {
7016 /* In the gnome-terminal-emulator, the message from
7017 * scrolling up one line when impossible followed by
7018 * scrolling down one line causes corruption of the
7019 * status line. This is fixed by calling wclear. */
7020 use_scroll_status_wclear = TRUE;
7021 use_scroll_redrawwin = FALSE;
7023 } else if (term && !strcmp(term, "xrvt-xpm")) {
7024 /* No problems with full optimizations in xrvt-(unicode)
7025 * and aterm. */
7026 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7028 } else {
7029 /* When scrolling in (u)xterm the last line in the
7030 * scrolling direction will update slowly. */
7031 use_scroll_redrawwin = TRUE;
7032 use_scroll_status_wclear = FALSE;
7036 static int
7037 get_input(int prompt_position)
7039 struct view *view;
7040 int i, key, cursor_y, cursor_x;
7041 bool loading = FALSE;
7043 if (prompt_position)
7044 input_mode = TRUE;
7046 while (TRUE) {
7047 foreach_view (view, i) {
7048 update_view(view);
7049 if (view_is_displayed(view) && view->has_scrolled &&
7050 use_scroll_redrawwin)
7051 redrawwin(view->win);
7052 view->has_scrolled = FALSE;
7053 if (view->pipe)
7054 loading = TRUE;
7057 /* Update the cursor position. */
7058 if (prompt_position) {
7059 getbegyx(status_win, cursor_y, cursor_x);
7060 cursor_x = prompt_position;
7061 } else {
7062 view = display[current_view];
7063 getbegyx(view->win, cursor_y, cursor_x);
7064 cursor_x = view->width - 1;
7065 cursor_y += view->lineno - view->offset;
7067 setsyx(cursor_y, cursor_x);
7069 /* Refresh, accept single keystroke of input */
7070 doupdate();
7071 nodelay(status_win, loading);
7072 key = wgetch(status_win);
7074 /* wgetch() with nodelay() enabled returns ERR when
7075 * there's no input. */
7076 if (key == ERR) {
7078 } else if (key == KEY_RESIZE) {
7079 int height, width;
7081 getmaxyx(stdscr, height, width);
7083 wresize(status_win, 1, width);
7084 mvwin(status_win, height - 1, 0);
7085 wnoutrefresh(status_win);
7086 resize_display();
7087 redraw_display(TRUE);
7089 } else {
7090 input_mode = FALSE;
7091 return key;
7096 static char *
7097 prompt_input(const char *prompt, input_handler handler, void *data)
7099 enum input_status status = INPUT_OK;
7100 static char buf[SIZEOF_STR];
7101 size_t pos = 0;
7103 buf[pos] = 0;
7105 while (status == INPUT_OK || status == INPUT_SKIP) {
7106 int key;
7108 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7109 wclrtoeol(status_win);
7111 key = get_input(pos + 1);
7112 switch (key) {
7113 case KEY_RETURN:
7114 case KEY_ENTER:
7115 case '\n':
7116 status = pos ? INPUT_STOP : INPUT_CANCEL;
7117 break;
7119 case KEY_BACKSPACE:
7120 if (pos > 0)
7121 buf[--pos] = 0;
7122 else
7123 status = INPUT_CANCEL;
7124 break;
7126 case KEY_ESC:
7127 status = INPUT_CANCEL;
7128 break;
7130 default:
7131 if (pos >= sizeof(buf)) {
7132 report("Input string too long");
7133 return NULL;
7136 status = handler(data, buf, key);
7137 if (status == INPUT_OK)
7138 buf[pos++] = (char) key;
7142 /* Clear the status window */
7143 status_empty = FALSE;
7144 report("");
7146 if (status == INPUT_CANCEL)
7147 return NULL;
7149 buf[pos++] = 0;
7151 return buf;
7154 static enum input_status
7155 prompt_yesno_handler(void *data, char *buf, int c)
7157 if (c == 'y' || c == 'Y')
7158 return INPUT_STOP;
7159 if (c == 'n' || c == 'N')
7160 return INPUT_CANCEL;
7161 return INPUT_SKIP;
7164 static bool
7165 prompt_yesno(const char *prompt)
7167 char prompt2[SIZEOF_STR];
7169 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7170 return FALSE;
7172 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7175 static enum input_status
7176 read_prompt_handler(void *data, char *buf, int c)
7178 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7181 static char *
7182 read_prompt(const char *prompt)
7184 return prompt_input(prompt, read_prompt_handler, NULL);
7187 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7189 enum input_status status = INPUT_OK;
7190 int size = 0;
7192 while (items[size].text)
7193 size++;
7195 while (status == INPUT_OK) {
7196 const struct menu_item *item = &items[*selected];
7197 int key;
7198 int i;
7200 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7201 prompt, *selected + 1, size);
7202 if (item->hotkey)
7203 wprintw(status_win, "[%c] ", (char) item->hotkey);
7204 wprintw(status_win, "%s", item->text);
7205 wclrtoeol(status_win);
7207 key = get_input(COLS - 1);
7208 switch (key) {
7209 case KEY_RETURN:
7210 case KEY_ENTER:
7211 case '\n':
7212 status = INPUT_STOP;
7213 break;
7215 case KEY_LEFT:
7216 case KEY_UP:
7217 *selected = *selected - 1;
7218 if (*selected < 0)
7219 *selected = size - 1;
7220 break;
7222 case KEY_RIGHT:
7223 case KEY_DOWN:
7224 *selected = (*selected + 1) % size;
7225 break;
7227 case KEY_ESC:
7228 status = INPUT_CANCEL;
7229 break;
7231 default:
7232 for (i = 0; items[i].text; i++)
7233 if (items[i].hotkey == key) {
7234 *selected = i;
7235 status = INPUT_STOP;
7236 break;
7241 /* Clear the status window */
7242 status_empty = FALSE;
7243 report("");
7245 return status != INPUT_CANCEL;
7249 * Repository properties
7252 static struct ref **refs = NULL;
7253 static size_t refs_size = 0;
7254 static struct ref *refs_head = NULL;
7256 static struct ref_list **ref_lists = NULL;
7257 static size_t ref_lists_size = 0;
7259 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7260 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7261 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7263 static int
7264 compare_refs(const void *ref1_, const void *ref2_)
7266 const struct ref *ref1 = *(const struct ref **)ref1_;
7267 const struct ref *ref2 = *(const struct ref **)ref2_;
7269 if (ref1->tag != ref2->tag)
7270 return ref2->tag - ref1->tag;
7271 if (ref1->ltag != ref2->ltag)
7272 return ref2->ltag - ref2->ltag;
7273 if (ref1->head != ref2->head)
7274 return ref2->head - ref1->head;
7275 if (ref1->tracked != ref2->tracked)
7276 return ref2->tracked - ref1->tracked;
7277 if (ref1->remote != ref2->remote)
7278 return ref2->remote - ref1->remote;
7279 return strcmp(ref1->name, ref2->name);
7282 static void
7283 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7285 size_t i;
7287 for (i = 0; i < refs_size; i++)
7288 if (!visitor(data, refs[i]))
7289 break;
7292 static struct ref *
7293 get_ref_head()
7295 return refs_head;
7298 static struct ref_list *
7299 get_ref_list(const char *id)
7301 struct ref_list *list;
7302 size_t i;
7304 for (i = 0; i < ref_lists_size; i++)
7305 if (!strcmp(id, ref_lists[i]->id))
7306 return ref_lists[i];
7308 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7309 return NULL;
7310 list = calloc(1, sizeof(*list));
7311 if (!list)
7312 return NULL;
7314 for (i = 0; i < refs_size; i++) {
7315 if (!strcmp(id, refs[i]->id) &&
7316 realloc_refs_list(&list->refs, list->size, 1))
7317 list->refs[list->size++] = refs[i];
7320 if (!list->refs) {
7321 free(list);
7322 return NULL;
7325 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7326 ref_lists[ref_lists_size++] = list;
7327 return list;
7330 static int
7331 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7333 struct ref *ref = NULL;
7334 bool tag = FALSE;
7335 bool ltag = FALSE;
7336 bool remote = FALSE;
7337 bool tracked = FALSE;
7338 bool head = FALSE;
7339 int from = 0, to = refs_size - 1;
7341 if (!prefixcmp(name, "refs/tags/")) {
7342 if (!suffixcmp(name, namelen, "^{}")) {
7343 namelen -= 3;
7344 name[namelen] = 0;
7345 } else {
7346 ltag = TRUE;
7349 tag = TRUE;
7350 namelen -= STRING_SIZE("refs/tags/");
7351 name += STRING_SIZE("refs/tags/");
7353 } else if (!prefixcmp(name, "refs/remotes/")) {
7354 remote = TRUE;
7355 namelen -= STRING_SIZE("refs/remotes/");
7356 name += STRING_SIZE("refs/remotes/");
7357 tracked = !strcmp(opt_remote, name);
7359 } else if (!prefixcmp(name, "refs/heads/")) {
7360 namelen -= STRING_SIZE("refs/heads/");
7361 name += STRING_SIZE("refs/heads/");
7362 if (!strncmp(opt_head, name, namelen))
7363 return OK;
7365 } else if (!strcmp(name, "HEAD")) {
7366 head = TRUE;
7367 if (*opt_head) {
7368 namelen = strlen(opt_head);
7369 name = opt_head;
7373 /* If we are reloading or it's an annotated tag, replace the
7374 * previous SHA1 with the resolved commit id; relies on the fact
7375 * git-ls-remote lists the commit id of an annotated tag right
7376 * before the commit id it points to. */
7377 while (from <= to) {
7378 size_t pos = (to + from) / 2;
7379 int cmp = strcmp(name, refs[pos]->name);
7381 if (!cmp) {
7382 ref = refs[pos];
7383 break;
7386 if (cmp < 0)
7387 to = pos - 1;
7388 else
7389 from = pos + 1;
7392 if (!ref) {
7393 if (!realloc_refs(&refs, refs_size, 1))
7394 return ERR;
7395 ref = calloc(1, sizeof(*ref) + namelen);
7396 if (!ref)
7397 return ERR;
7398 memmove(refs + from + 1, refs + from,
7399 (refs_size - from) * sizeof(*refs));
7400 refs[from] = ref;
7401 strncpy(ref->name, name, namelen);
7402 refs_size++;
7405 ref->head = head;
7406 ref->tag = tag;
7407 ref->ltag = ltag;
7408 ref->remote = remote;
7409 ref->tracked = tracked;
7410 string_copy_rev(ref->id, id);
7412 if (head)
7413 refs_head = ref;
7414 return OK;
7417 static int
7418 load_refs(void)
7420 const char *head_argv[] = {
7421 "git", "symbolic-ref", "HEAD", NULL
7423 static const char *ls_remote_argv[SIZEOF_ARG] = {
7424 "git", "ls-remote", opt_git_dir, NULL
7426 static bool init = FALSE;
7427 size_t i;
7429 if (!init) {
7430 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7431 die("TIG_LS_REMOTE contains too many arguments");
7432 init = TRUE;
7435 if (!*opt_git_dir)
7436 return OK;
7438 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7439 !prefixcmp(opt_head, "refs/heads/")) {
7440 char *offset = opt_head + STRING_SIZE("refs/heads/");
7442 memmove(opt_head, offset, strlen(offset) + 1);
7445 refs_head = NULL;
7446 for (i = 0; i < refs_size; i++)
7447 refs[i]->id[0] = 0;
7449 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7450 return ERR;
7452 /* Update the ref lists to reflect changes. */
7453 for (i = 0; i < ref_lists_size; i++) {
7454 struct ref_list *list = ref_lists[i];
7455 size_t old, new;
7457 for (old = new = 0; old < list->size; old++)
7458 if (!strcmp(list->id, list->refs[old]->id))
7459 list->refs[new++] = list->refs[old];
7460 list->size = new;
7463 return OK;
7466 static void
7467 set_remote_branch(const char *name, const char *value, size_t valuelen)
7469 if (!strcmp(name, ".remote")) {
7470 string_ncopy(opt_remote, value, valuelen);
7472 } else if (*opt_remote && !strcmp(name, ".merge")) {
7473 size_t from = strlen(opt_remote);
7475 if (!prefixcmp(value, "refs/heads/"))
7476 value += STRING_SIZE("refs/heads/");
7478 if (!string_format_from(opt_remote, &from, "/%s", value))
7479 opt_remote[0] = 0;
7483 static void
7484 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7486 const char *argv[SIZEOF_ARG] = { name, "=" };
7487 int argc = 1 + (cmd == option_set_command);
7488 int error = ERR;
7490 if (!argv_from_string(argv, &argc, value))
7491 config_msg = "Too many option arguments";
7492 else
7493 error = cmd(argc, argv);
7495 if (error == ERR)
7496 warn("Option 'tig.%s': %s", name, config_msg);
7499 static bool
7500 set_environment_variable(const char *name, const char *value)
7502 size_t len = strlen(name) + 1 + strlen(value) + 1;
7503 char *env = malloc(len);
7505 if (env &&
7506 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7507 putenv(env) == 0)
7508 return TRUE;
7509 free(env);
7510 return FALSE;
7513 static void
7514 set_work_tree(const char *value)
7516 char cwd[SIZEOF_STR];
7518 if (!getcwd(cwd, sizeof(cwd)))
7519 die("Failed to get cwd path: %s", strerror(errno));
7520 if (chdir(opt_git_dir) < 0)
7521 die("Failed to chdir(%s): %s", strerror(errno));
7522 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7523 die("Failed to get git path: %s", strerror(errno));
7524 if (chdir(cwd) < 0)
7525 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7526 if (chdir(value) < 0)
7527 die("Failed to chdir(%s): %s", value, strerror(errno));
7528 if (!getcwd(cwd, sizeof(cwd)))
7529 die("Failed to get cwd path: %s", strerror(errno));
7530 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7531 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7532 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7533 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7534 opt_is_inside_work_tree = TRUE;
7537 static int
7538 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7540 if (!strcmp(name, "i18n.commitencoding"))
7541 string_ncopy(opt_encoding, value, valuelen);
7543 else if (!strcmp(name, "core.editor"))
7544 string_ncopy(opt_editor, value, valuelen);
7546 else if (!strcmp(name, "core.worktree"))
7547 set_work_tree(value);
7549 else if (!prefixcmp(name, "tig.color."))
7550 set_repo_config_option(name + 10, value, option_color_command);
7552 else if (!prefixcmp(name, "tig.bind."))
7553 set_repo_config_option(name + 9, value, option_bind_command);
7555 else if (!prefixcmp(name, "tig."))
7556 set_repo_config_option(name + 4, value, option_set_command);
7558 else if (*opt_head && !prefixcmp(name, "branch.") &&
7559 !strncmp(name + 7, opt_head, strlen(opt_head)))
7560 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7562 return OK;
7565 static int
7566 load_git_config(void)
7568 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7570 return io_run_load(config_list_argv, "=", read_repo_config_option);
7573 static int
7574 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7576 if (!opt_git_dir[0]) {
7577 string_ncopy(opt_git_dir, name, namelen);
7579 } else if (opt_is_inside_work_tree == -1) {
7580 /* This can be 3 different values depending on the
7581 * version of git being used. If git-rev-parse does not
7582 * understand --is-inside-work-tree it will simply echo
7583 * the option else either "true" or "false" is printed.
7584 * Default to true for the unknown case. */
7585 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7587 } else if (*name == '.') {
7588 string_ncopy(opt_cdup, name, namelen);
7590 } else {
7591 string_ncopy(opt_prefix, name, namelen);
7594 return OK;
7597 static int
7598 load_repo_info(void)
7600 const char *rev_parse_argv[] = {
7601 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7602 "--show-cdup", "--show-prefix", NULL
7605 return io_run_load(rev_parse_argv, "=", read_repo_info);
7610 * Main
7613 static const char usage[] =
7614 "tig " TIG_VERSION " (" __DATE__ ")\n"
7615 "\n"
7616 "Usage: tig [options] [revs] [--] [paths]\n"
7617 " or: tig show [options] [revs] [--] [paths]\n"
7618 " or: tig blame [rev] path\n"
7619 " or: tig status\n"
7620 " or: tig < [git command output]\n"
7621 "\n"
7622 "Options:\n"
7623 " -v, --version Show version and exit\n"
7624 " -h, --help Show help message and exit";
7626 static void __NORETURN
7627 quit(int sig)
7629 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7630 if (cursed)
7631 endwin();
7632 exit(0);
7635 static void __NORETURN
7636 die(const char *err, ...)
7638 va_list args;
7640 endwin();
7642 va_start(args, err);
7643 fputs("tig: ", stderr);
7644 vfprintf(stderr, err, args);
7645 fputs("\n", stderr);
7646 va_end(args);
7648 exit(1);
7651 static void
7652 warn(const char *msg, ...)
7654 va_list args;
7656 va_start(args, msg);
7657 fputs("tig warning: ", stderr);
7658 vfprintf(stderr, msg, args);
7659 fputs("\n", stderr);
7660 va_end(args);
7663 static enum request
7664 parse_options(int argc, const char *argv[])
7666 enum request request = REQ_VIEW_MAIN;
7667 const char *subcommand;
7668 bool seen_dashdash = FALSE;
7669 /* XXX: This is vulnerable to the user overriding options
7670 * required for the main view parser. */
7671 const char *custom_argv[SIZEOF_ARG] = {
7672 "git", "log", "--no-color", "--pretty=raw", "--parents",
7673 "--topo-order", NULL
7675 int i, j = 6;
7677 if (!isatty(STDIN_FILENO)) {
7678 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7679 return REQ_VIEW_PAGER;
7682 if (argc <= 1)
7683 return REQ_NONE;
7685 subcommand = argv[1];
7686 if (!strcmp(subcommand, "status")) {
7687 if (argc > 2)
7688 warn("ignoring arguments after `%s'", subcommand);
7689 return REQ_VIEW_STATUS;
7691 } else if (!strcmp(subcommand, "blame")) {
7692 if (argc <= 2 || argc > 4)
7693 die("invalid number of options to blame\n\n%s", usage);
7695 i = 2;
7696 if (argc == 4) {
7697 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7698 i++;
7701 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7702 return REQ_VIEW_BLAME;
7704 } else if (!strcmp(subcommand, "show")) {
7705 request = REQ_VIEW_DIFF;
7707 } else {
7708 subcommand = NULL;
7711 if (subcommand) {
7712 custom_argv[1] = subcommand;
7713 j = 2;
7716 for (i = 1 + !!subcommand; i < argc; i++) {
7717 const char *opt = argv[i];
7719 if (seen_dashdash || !strcmp(opt, "--")) {
7720 seen_dashdash = TRUE;
7722 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7723 printf("tig version %s\n", TIG_VERSION);
7724 quit(0);
7726 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7727 printf("%s\n", usage);
7728 quit(0);
7731 custom_argv[j++] = opt;
7732 if (j >= ARRAY_SIZE(custom_argv))
7733 die("command too long");
7736 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7737 die("Failed to format arguments");
7739 return request;
7743 main(int argc, const char *argv[])
7745 const char *codeset = "UTF-8";
7746 enum request request = parse_options(argc, argv);
7747 struct view *view;
7748 size_t i;
7750 signal(SIGINT, quit);
7751 signal(SIGPIPE, SIG_IGN);
7753 if (setlocale(LC_ALL, "")) {
7754 codeset = nl_langinfo(CODESET);
7757 if (load_repo_info() == ERR)
7758 die("Failed to load repo info.");
7760 if (load_options() == ERR)
7761 die("Failed to load user config.");
7763 if (load_git_config() == ERR)
7764 die("Failed to load repo config.");
7766 /* Require a git repository unless when running in pager mode. */
7767 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7768 die("Not a git repository");
7770 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7771 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7772 if (opt_iconv_in == ICONV_NONE)
7773 die("Failed to initialize character set conversion");
7776 if (codeset && strcmp(codeset, "UTF-8")) {
7777 opt_iconv_out = iconv_open(codeset, "UTF-8");
7778 if (opt_iconv_out == ICONV_NONE)
7779 die("Failed to initialize character set conversion");
7782 if (load_refs() == ERR)
7783 die("Failed to load refs.");
7785 foreach_view (view, i)
7786 if (!argv_from_env(view->ops->argv, view->cmd_env))
7787 die("Too many arguments in the `%s` environment variable",
7788 view->cmd_env);
7790 init_display();
7792 if (request != REQ_NONE)
7793 open_view(NULL, request, OPEN_PREPARED);
7794 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7796 while (view_driver(display[current_view], request)) {
7797 int key = get_input(0);
7799 view = display[current_view];
7800 request = get_keybinding(view->keymap, key);
7802 /* Some low-level request handling. This keeps access to
7803 * status_win restricted. */
7804 switch (request) {
7805 case REQ_PROMPT:
7807 char *cmd = read_prompt(":");
7809 if (cmd && isdigit(*cmd)) {
7810 int lineno = view->lineno + 1;
7812 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7813 select_view_line(view, lineno - 1);
7814 report("");
7815 } else {
7816 report("Unable to parse '%s' as a line number", cmd);
7819 } else if (cmd) {
7820 struct view *next = VIEW(REQ_VIEW_PAGER);
7821 const char *argv[SIZEOF_ARG] = { "git" };
7822 int argc = 1;
7824 /* When running random commands, initially show the
7825 * command in the title. However, it maybe later be
7826 * overwritten if a commit line is selected. */
7827 string_ncopy(next->ref, cmd, strlen(cmd));
7829 if (!argv_from_string(argv, &argc, cmd)) {
7830 report("Too many arguments");
7831 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7832 report("Failed to format command");
7833 } else {
7834 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7838 request = REQ_NONE;
7839 break;
7841 case REQ_SEARCH:
7842 case REQ_SEARCH_BACK:
7844 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7845 char *search = read_prompt(prompt);
7847 if (search)
7848 string_ncopy(opt_search, search, strlen(search));
7849 else if (*opt_search)
7850 request = request == REQ_SEARCH ?
7851 REQ_FIND_NEXT :
7852 REQ_FIND_PREV;
7853 else
7854 request = REQ_NONE;
7855 break;
7857 default:
7858 break;
7862 quit(0);
7864 return 0;