argv: make prepare_update use FORMAT_NONE
[tig.git] / tig.c
blob3c128876a7fbc383c9b1f3802e045d6f5262cd4b
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 *checkout[] = { "git", "checkout", "%(branch)", NULL };
1734 const char *commit[] = { "git", "commit", NULL };
1735 const char *gc[] = { "git", "gc", NULL };
1736 struct {
1737 enum keymap keymap;
1738 int key;
1739 int argc;
1740 const char **argv;
1741 } reqs[] = {
1742 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1743 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1744 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1745 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1747 int i;
1749 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1750 enum request req;
1752 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1753 if (req != REQ_NONE)
1754 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1759 * User config file handling.
1762 static int config_lineno;
1763 static bool config_errors;
1764 static const char *config_msg;
1766 static const struct enum_map color_map[] = {
1767 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1768 COLOR_MAP(DEFAULT),
1769 COLOR_MAP(BLACK),
1770 COLOR_MAP(BLUE),
1771 COLOR_MAP(CYAN),
1772 COLOR_MAP(GREEN),
1773 COLOR_MAP(MAGENTA),
1774 COLOR_MAP(RED),
1775 COLOR_MAP(WHITE),
1776 COLOR_MAP(YELLOW),
1779 static const struct enum_map attr_map[] = {
1780 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1781 ATTR_MAP(NORMAL),
1782 ATTR_MAP(BLINK),
1783 ATTR_MAP(BOLD),
1784 ATTR_MAP(DIM),
1785 ATTR_MAP(REVERSE),
1786 ATTR_MAP(STANDOUT),
1787 ATTR_MAP(UNDERLINE),
1790 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1792 static int parse_step(double *opt, const char *arg)
1794 *opt = atoi(arg);
1795 if (!strchr(arg, '%'))
1796 return OK;
1798 /* "Shift down" so 100% and 1 does not conflict. */
1799 *opt = (*opt - 1) / 100;
1800 if (*opt >= 1.0) {
1801 *opt = 0.99;
1802 config_msg = "Step value larger than 100%";
1803 return ERR;
1805 if (*opt < 0.0) {
1806 *opt = 1;
1807 config_msg = "Invalid step value";
1808 return ERR;
1810 return OK;
1813 static int
1814 parse_int(int *opt, const char *arg, int min, int max)
1816 int value = atoi(arg);
1818 if (min <= value && value <= max) {
1819 *opt = value;
1820 return OK;
1823 config_msg = "Integer value out of bound";
1824 return ERR;
1827 static bool
1828 set_color(int *color, const char *name)
1830 if (map_enum(color, color_map, name))
1831 return TRUE;
1832 if (!prefixcmp(name, "color"))
1833 return parse_int(color, name + 5, 0, 255) == OK;
1834 return FALSE;
1837 /* Wants: object fgcolor bgcolor [attribute] */
1838 static int
1839 option_color_command(int argc, const char *argv[])
1841 struct line_info *info;
1843 if (argc < 3) {
1844 config_msg = "Wrong number of arguments given to color command";
1845 return ERR;
1848 info = get_line_info(argv[0]);
1849 if (!info) {
1850 static const struct enum_map obsolete[] = {
1851 ENUM_MAP("main-delim", LINE_DELIMITER),
1852 ENUM_MAP("main-date", LINE_DATE),
1853 ENUM_MAP("main-author", LINE_AUTHOR),
1855 int index;
1857 if (!map_enum(&index, obsolete, argv[0])) {
1858 config_msg = "Unknown color name";
1859 return ERR;
1861 info = &line_info[index];
1864 if (!set_color(&info->fg, argv[1]) ||
1865 !set_color(&info->bg, argv[2])) {
1866 config_msg = "Unknown color";
1867 return ERR;
1870 info->attr = 0;
1871 while (argc-- > 3) {
1872 int attr;
1874 if (!set_attribute(&attr, argv[argc])) {
1875 config_msg = "Unknown attribute";
1876 return ERR;
1878 info->attr |= attr;
1881 return OK;
1884 static int parse_bool(bool *opt, const char *arg)
1886 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1887 ? TRUE : FALSE;
1888 return OK;
1891 static int parse_enum_do(unsigned int *opt, const char *arg,
1892 const struct enum_map *map, size_t map_size)
1894 bool is_true;
1896 assert(map_size > 1);
1898 if (map_enum_do(map, map_size, (int *) opt, arg))
1899 return OK;
1901 if (parse_bool(&is_true, arg) != OK)
1902 return ERR;
1904 *opt = is_true ? map[1].value : map[0].value;
1905 return OK;
1908 #define parse_enum(opt, arg, map) \
1909 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1911 static int
1912 parse_string(char *opt, const char *arg, size_t optsize)
1914 int arglen = strlen(arg);
1916 switch (arg[0]) {
1917 case '\"':
1918 case '\'':
1919 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1920 config_msg = "Unmatched quotation";
1921 return ERR;
1923 arg += 1; arglen -= 2;
1924 default:
1925 string_ncopy_do(opt, optsize, arg, arglen);
1926 return OK;
1930 /* Wants: name = value */
1931 static int
1932 option_set_command(int argc, const char *argv[])
1934 if (argc != 3) {
1935 config_msg = "Wrong number of arguments given to set command";
1936 return ERR;
1939 if (strcmp(argv[1], "=")) {
1940 config_msg = "No value assigned";
1941 return ERR;
1944 if (!strcmp(argv[0], "show-author"))
1945 return parse_enum(&opt_author, argv[2], author_map);
1947 if (!strcmp(argv[0], "show-date"))
1948 return parse_enum(&opt_date, argv[2], date_map);
1950 if (!strcmp(argv[0], "show-rev-graph"))
1951 return parse_bool(&opt_rev_graph, argv[2]);
1953 if (!strcmp(argv[0], "show-refs"))
1954 return parse_bool(&opt_show_refs, argv[2]);
1956 if (!strcmp(argv[0], "show-line-numbers"))
1957 return parse_bool(&opt_line_number, argv[2]);
1959 if (!strcmp(argv[0], "line-graphics"))
1960 return parse_bool(&opt_line_graphics, argv[2]);
1962 if (!strcmp(argv[0], "line-number-interval"))
1963 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1965 if (!strcmp(argv[0], "author-width"))
1966 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1968 if (!strcmp(argv[0], "horizontal-scroll"))
1969 return parse_step(&opt_hscroll, argv[2]);
1971 if (!strcmp(argv[0], "split-view-height"))
1972 return parse_step(&opt_scale_split_view, argv[2]);
1974 if (!strcmp(argv[0], "tab-size"))
1975 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1977 if (!strcmp(argv[0], "commit-encoding"))
1978 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1980 config_msg = "Unknown variable name";
1981 return ERR;
1984 /* Wants: mode request key */
1985 static int
1986 option_bind_command(int argc, const char *argv[])
1988 enum request request;
1989 int keymap = -1;
1990 int key;
1992 if (argc < 3) {
1993 config_msg = "Wrong number of arguments given to bind command";
1994 return ERR;
1997 if (set_keymap(&keymap, argv[0]) == ERR) {
1998 config_msg = "Unknown key map";
1999 return ERR;
2002 key = get_key_value(argv[1]);
2003 if (key == ERR) {
2004 config_msg = "Unknown key";
2005 return ERR;
2008 request = get_request(argv[2]);
2009 if (request == REQ_NONE) {
2010 static const struct enum_map obsolete[] = {
2011 ENUM_MAP("cherry-pick", REQ_NONE),
2012 ENUM_MAP("screen-resize", REQ_NONE),
2013 ENUM_MAP("tree-parent", REQ_PARENT),
2015 int alias;
2017 if (map_enum(&alias, obsolete, argv[2])) {
2018 if (alias != REQ_NONE)
2019 add_keybinding(keymap, alias, key);
2020 config_msg = "Obsolete request name";
2021 return ERR;
2024 if (request == REQ_NONE && *argv[2]++ == '!')
2025 request = add_run_request(keymap, key, argc - 2, argv + 2);
2026 if (request == REQ_NONE) {
2027 config_msg = "Unknown request name";
2028 return ERR;
2031 add_keybinding(keymap, request, key);
2033 return OK;
2036 static int
2037 set_option(const char *opt, char *value)
2039 const char *argv[SIZEOF_ARG];
2040 int argc = 0;
2042 if (!argv_from_string(argv, &argc, value)) {
2043 config_msg = "Too many option arguments";
2044 return ERR;
2047 if (!strcmp(opt, "color"))
2048 return option_color_command(argc, argv);
2050 if (!strcmp(opt, "set"))
2051 return option_set_command(argc, argv);
2053 if (!strcmp(opt, "bind"))
2054 return option_bind_command(argc, argv);
2056 config_msg = "Unknown option command";
2057 return ERR;
2060 static int
2061 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2063 int status = OK;
2065 config_lineno++;
2066 config_msg = "Internal error";
2068 /* Check for comment markers, since read_properties() will
2069 * only ensure opt and value are split at first " \t". */
2070 optlen = strcspn(opt, "#");
2071 if (optlen == 0)
2072 return OK;
2074 if (opt[optlen] != 0) {
2075 config_msg = "No option value";
2076 status = ERR;
2078 } else {
2079 /* Look for comment endings in the value. */
2080 size_t len = strcspn(value, "#");
2082 if (len < valuelen) {
2083 valuelen = len;
2084 value[valuelen] = 0;
2087 status = set_option(opt, value);
2090 if (status == ERR) {
2091 warn("Error on line %d, near '%.*s': %s",
2092 config_lineno, (int) optlen, opt, config_msg);
2093 config_errors = TRUE;
2096 /* Always keep going if errors are encountered. */
2097 return OK;
2100 static void
2101 load_option_file(const char *path)
2103 struct io io = {};
2105 /* It's OK that the file doesn't exist. */
2106 if (!io_open(&io, "%s", path))
2107 return;
2109 config_lineno = 0;
2110 config_errors = FALSE;
2112 if (io_load(&io, " \t", read_option) == ERR ||
2113 config_errors == TRUE)
2114 warn("Errors while loading %s.", path);
2117 static int
2118 load_options(void)
2120 const char *home = getenv("HOME");
2121 const char *tigrc_user = getenv("TIGRC_USER");
2122 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2123 char buf[SIZEOF_STR];
2125 add_builtin_run_requests();
2127 if (!tigrc_system)
2128 tigrc_system = SYSCONFDIR "/tigrc";
2129 load_option_file(tigrc_system);
2131 if (!tigrc_user) {
2132 if (!home || !string_format(buf, "%s/.tigrc", home))
2133 return ERR;
2134 tigrc_user = buf;
2136 load_option_file(tigrc_user);
2138 return OK;
2143 * The viewer
2146 struct view;
2147 struct view_ops;
2149 /* The display array of active views and the index of the current view. */
2150 static struct view *display[2];
2151 static unsigned int current_view;
2153 #define foreach_displayed_view(view, i) \
2154 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2156 #define displayed_views() (display[1] != NULL ? 2 : 1)
2158 /* Current head and commit ID */
2159 static char ref_blob[SIZEOF_REF] = "";
2160 static char ref_commit[SIZEOF_REF] = "HEAD";
2161 static char ref_head[SIZEOF_REF] = "HEAD";
2162 static char ref_branch[SIZEOF_REF] = "";
2164 struct view {
2165 const char *name; /* View name */
2166 const char *cmd_env; /* Command line set via environment */
2167 const char *id; /* Points to either of ref_{head,commit,blob} */
2169 struct view_ops *ops; /* View operations */
2171 enum keymap keymap; /* What keymap does this view have */
2172 bool git_dir; /* Whether the view requires a git directory. */
2174 char ref[SIZEOF_REF]; /* Hovered commit reference */
2175 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2177 int height, width; /* The width and height of the main window */
2178 WINDOW *win; /* The main window */
2179 WINDOW *title; /* The title window living below the main window */
2181 /* Navigation */
2182 unsigned long offset; /* Offset of the window top */
2183 unsigned long yoffset; /* Offset from the window side. */
2184 unsigned long lineno; /* Current line number */
2185 unsigned long p_offset; /* Previous offset of the window top */
2186 unsigned long p_yoffset;/* Previous offset from the window side */
2187 unsigned long p_lineno; /* Previous current line number */
2188 bool p_restore; /* Should the previous position be restored. */
2190 /* Searching */
2191 char grep[SIZEOF_STR]; /* Search string */
2192 regex_t *regex; /* Pre-compiled regexp */
2194 /* If non-NULL, points to the view that opened this view. If this view
2195 * is closed tig will switch back to the parent view. */
2196 struct view *parent;
2198 /* Buffering */
2199 size_t lines; /* Total number of lines */
2200 struct line *line; /* Line index */
2201 unsigned int digits; /* Number of digits in the lines member. */
2203 /* Drawing */
2204 struct line *curline; /* Line currently being drawn. */
2205 enum line_type curtype; /* Attribute currently used for drawing. */
2206 unsigned long col; /* Column when drawing. */
2207 bool has_scrolled; /* View was scrolled. */
2209 /* Loading */
2210 struct io io;
2211 struct io *pipe;
2212 time_t start_time;
2213 time_t update_secs;
2216 struct view_ops {
2217 /* What type of content being displayed. Used in the title bar. */
2218 const char *type;
2219 /* Default command arguments. */
2220 const char **argv;
2221 /* Open and reads in all view content. */
2222 bool (*open)(struct view *view);
2223 /* Read one line; updates view->line. */
2224 bool (*read)(struct view *view, char *data);
2225 /* Draw one line; @lineno must be < view->height. */
2226 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2227 /* Depending on view handle a special requests. */
2228 enum request (*request)(struct view *view, enum request request, struct line *line);
2229 /* Search for regexp in a line. */
2230 bool (*grep)(struct view *view, struct line *line);
2231 /* Select line */
2232 void (*select)(struct view *view, struct line *line);
2233 /* Prepare view for loading */
2234 bool (*prepare)(struct view *view);
2237 static struct view_ops blame_ops;
2238 static struct view_ops blob_ops;
2239 static struct view_ops diff_ops;
2240 static struct view_ops help_ops;
2241 static struct view_ops log_ops;
2242 static struct view_ops main_ops;
2243 static struct view_ops pager_ops;
2244 static struct view_ops stage_ops;
2245 static struct view_ops status_ops;
2246 static struct view_ops tree_ops;
2247 static struct view_ops branch_ops;
2249 #define VIEW_STR(name, env, ref, ops, map, git) \
2250 { name, #env, ref, ops, map, git }
2252 #define VIEW_(id, name, ops, git, ref) \
2253 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2256 static struct view views[] = {
2257 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2258 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2259 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2260 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2261 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2262 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2263 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2264 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2265 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2266 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2267 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2270 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2271 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2273 #define foreach_view(view, i) \
2274 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2276 #define view_is_displayed(view) \
2277 (view == display[0] || view == display[1])
2280 static inline void
2281 set_view_attr(struct view *view, enum line_type type)
2283 if (!view->curline->selected && view->curtype != type) {
2284 (void) wattrset(view->win, get_line_attr(type));
2285 wchgat(view->win, -1, 0, type, NULL);
2286 view->curtype = type;
2290 static int
2291 draw_chars(struct view *view, enum line_type type, const char *string,
2292 int max_len, bool use_tilde)
2294 static char out_buffer[BUFSIZ * 2];
2295 int len = 0;
2296 int col = 0;
2297 int trimmed = FALSE;
2298 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2300 if (max_len <= 0)
2301 return 0;
2303 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2305 set_view_attr(view, type);
2306 if (len > 0) {
2307 if (opt_iconv_out != ICONV_NONE) {
2308 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2309 size_t inlen = len + 1;
2311 char *outbuf = out_buffer;
2312 size_t outlen = sizeof(out_buffer);
2314 size_t ret;
2316 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2317 if (ret != (size_t) -1) {
2318 string = out_buffer;
2319 len = sizeof(out_buffer) - outlen;
2323 waddnstr(view->win, string, len);
2325 if (trimmed && use_tilde) {
2326 set_view_attr(view, LINE_DELIMITER);
2327 waddch(view->win, '~');
2328 col++;
2331 return col;
2334 static int
2335 draw_space(struct view *view, enum line_type type, int max, int spaces)
2337 static char space[] = " ";
2338 int col = 0;
2340 spaces = MIN(max, spaces);
2342 while (spaces > 0) {
2343 int len = MIN(spaces, sizeof(space) - 1);
2345 col += draw_chars(view, type, space, len, FALSE);
2346 spaces -= len;
2349 return col;
2352 static bool
2353 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2355 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2356 return view->width + view->yoffset <= view->col;
2359 static bool
2360 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2362 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2363 int max = view->width + view->yoffset - view->col;
2364 int i;
2366 if (max < size)
2367 size = max;
2369 set_view_attr(view, type);
2370 /* Using waddch() instead of waddnstr() ensures that
2371 * they'll be rendered correctly for the cursor line. */
2372 for (i = skip; i < size; i++)
2373 waddch(view->win, graphic[i]);
2375 view->col += size;
2376 if (size < max && skip <= size)
2377 waddch(view->win, ' ');
2378 view->col++;
2380 return view->width + view->yoffset <= view->col;
2383 static bool
2384 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2386 int max = MIN(view->width + view->yoffset - view->col, len);
2387 int col;
2389 if (text)
2390 col = draw_chars(view, type, text, max - 1, trim);
2391 else
2392 col = draw_space(view, type, max - 1, max - 1);
2394 view->col += col;
2395 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2396 return view->width + view->yoffset <= view->col;
2399 static bool
2400 draw_date(struct view *view, struct time *time)
2402 const char *date = mkdate(time, opt_date);
2403 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2405 return draw_field(view, LINE_DATE, date, cols, FALSE);
2408 static bool
2409 draw_author(struct view *view, const char *author)
2411 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2412 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2414 if (abbreviate && author)
2415 author = get_author_initials(author);
2417 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2420 static bool
2421 draw_mode(struct view *view, mode_t mode)
2423 const char *str;
2425 if (S_ISDIR(mode))
2426 str = "drwxr-xr-x";
2427 else if (S_ISLNK(mode))
2428 str = "lrwxrwxrwx";
2429 else if (S_ISGITLINK(mode))
2430 str = "m---------";
2431 else if (S_ISREG(mode) && mode & S_IXUSR)
2432 str = "-rwxr-xr-x";
2433 else if (S_ISREG(mode))
2434 str = "-rw-r--r--";
2435 else
2436 str = "----------";
2438 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2441 static bool
2442 draw_lineno(struct view *view, unsigned int lineno)
2444 char number[10];
2445 int digits3 = view->digits < 3 ? 3 : view->digits;
2446 int max = MIN(view->width + view->yoffset - view->col, digits3);
2447 char *text = NULL;
2448 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2450 lineno += view->offset + 1;
2451 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2452 static char fmt[] = "%1ld";
2454 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2455 if (string_format(number, fmt, lineno))
2456 text = number;
2458 if (text)
2459 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2460 else
2461 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2462 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2465 static bool
2466 draw_view_line(struct view *view, unsigned int lineno)
2468 struct line *line;
2469 bool selected = (view->offset + lineno == view->lineno);
2471 assert(view_is_displayed(view));
2473 if (view->offset + lineno >= view->lines)
2474 return FALSE;
2476 line = &view->line[view->offset + lineno];
2478 wmove(view->win, lineno, 0);
2479 if (line->cleareol)
2480 wclrtoeol(view->win);
2481 view->col = 0;
2482 view->curline = line;
2483 view->curtype = LINE_NONE;
2484 line->selected = FALSE;
2485 line->dirty = line->cleareol = 0;
2487 if (selected) {
2488 set_view_attr(view, LINE_CURSOR);
2489 line->selected = TRUE;
2490 view->ops->select(view, line);
2493 return view->ops->draw(view, line, lineno);
2496 static void
2497 redraw_view_dirty(struct view *view)
2499 bool dirty = FALSE;
2500 int lineno;
2502 for (lineno = 0; lineno < view->height; lineno++) {
2503 if (view->offset + lineno >= view->lines)
2504 break;
2505 if (!view->line[view->offset + lineno].dirty)
2506 continue;
2507 dirty = TRUE;
2508 if (!draw_view_line(view, lineno))
2509 break;
2512 if (!dirty)
2513 return;
2514 wnoutrefresh(view->win);
2517 static void
2518 redraw_view_from(struct view *view, int lineno)
2520 assert(0 <= lineno && lineno < view->height);
2522 for (; lineno < view->height; lineno++) {
2523 if (!draw_view_line(view, lineno))
2524 break;
2527 wnoutrefresh(view->win);
2530 static void
2531 redraw_view(struct view *view)
2533 werase(view->win);
2534 redraw_view_from(view, 0);
2538 static void
2539 update_view_title(struct view *view)
2541 char buf[SIZEOF_STR];
2542 char state[SIZEOF_STR];
2543 size_t bufpos = 0, statelen = 0;
2545 assert(view_is_displayed(view));
2547 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2548 unsigned int view_lines = view->offset + view->height;
2549 unsigned int lines = view->lines
2550 ? MIN(view_lines, view->lines) * 100 / view->lines
2551 : 0;
2553 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2554 view->ops->type,
2555 view->lineno + 1,
2556 view->lines,
2557 lines);
2561 if (view->pipe) {
2562 time_t secs = time(NULL) - view->start_time;
2564 /* Three git seconds are a long time ... */
2565 if (secs > 2)
2566 string_format_from(state, &statelen, " loading %lds", secs);
2569 string_format_from(buf, &bufpos, "[%s]", view->name);
2570 if (*view->ref && bufpos < view->width) {
2571 size_t refsize = strlen(view->ref);
2572 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2574 if (minsize < view->width)
2575 refsize = view->width - minsize + 7;
2576 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2579 if (statelen && bufpos < view->width) {
2580 string_format_from(buf, &bufpos, "%s", state);
2583 if (view == display[current_view])
2584 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2585 else
2586 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2588 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2589 wclrtoeol(view->title);
2590 wnoutrefresh(view->title);
2593 static int
2594 apply_step(double step, int value)
2596 if (step >= 1)
2597 return (int) step;
2598 value *= step + 0.01;
2599 return value ? value : 1;
2602 static void
2603 resize_display(void)
2605 int offset, i;
2606 struct view *base = display[0];
2607 struct view *view = display[1] ? display[1] : display[0];
2609 /* Setup window dimensions */
2611 getmaxyx(stdscr, base->height, base->width);
2613 /* Make room for the status window. */
2614 base->height -= 1;
2616 if (view != base) {
2617 /* Horizontal split. */
2618 view->width = base->width;
2619 view->height = apply_step(opt_scale_split_view, base->height);
2620 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2621 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2622 base->height -= view->height;
2624 /* Make room for the title bar. */
2625 view->height -= 1;
2628 /* Make room for the title bar. */
2629 base->height -= 1;
2631 offset = 0;
2633 foreach_displayed_view (view, i) {
2634 if (!view->win) {
2635 view->win = newwin(view->height, 0, offset, 0);
2636 if (!view->win)
2637 die("Failed to create %s view", view->name);
2639 scrollok(view->win, FALSE);
2641 view->title = newwin(1, 0, offset + view->height, 0);
2642 if (!view->title)
2643 die("Failed to create title window");
2645 } else {
2646 wresize(view->win, view->height, view->width);
2647 mvwin(view->win, offset, 0);
2648 mvwin(view->title, offset + view->height, 0);
2651 offset += view->height + 1;
2655 static void
2656 redraw_display(bool clear)
2658 struct view *view;
2659 int i;
2661 foreach_displayed_view (view, i) {
2662 if (clear)
2663 wclear(view->win);
2664 redraw_view(view);
2665 update_view_title(view);
2669 static void
2670 toggle_enum_option_do(unsigned int *opt, const char *help,
2671 const struct enum_map *map, size_t size)
2673 *opt = (*opt + 1) % size;
2674 redraw_display(FALSE);
2675 report("Displaying %s %s", enum_name(map[*opt]), help);
2678 #define toggle_enum_option(opt, help, map) \
2679 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2681 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2682 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2684 static void
2685 toggle_view_option(bool *option, const char *help)
2687 *option = !*option;
2688 redraw_display(FALSE);
2689 report("%sabling %s", *option ? "En" : "Dis", help);
2692 static void
2693 open_option_menu(void)
2695 const struct menu_item menu[] = {
2696 { '.', "line numbers", &opt_line_number },
2697 { 'D', "date display", &opt_date },
2698 { 'A', "author display", &opt_author },
2699 { 'g', "revision graph display", &opt_rev_graph },
2700 { 'F', "reference display", &opt_show_refs },
2701 { 0 }
2703 int selected = 0;
2705 if (prompt_menu("Toggle option", menu, &selected)) {
2706 if (menu[selected].data == &opt_date)
2707 toggle_date();
2708 else if (menu[selected].data == &opt_author)
2709 toggle_author();
2710 else
2711 toggle_view_option(menu[selected].data, menu[selected].text);
2715 static void
2716 maximize_view(struct view *view)
2718 memset(display, 0, sizeof(display));
2719 current_view = 0;
2720 display[current_view] = view;
2721 resize_display();
2722 redraw_display(FALSE);
2723 report("");
2728 * Navigation
2731 static bool
2732 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2734 if (lineno >= view->lines)
2735 lineno = view->lines > 0 ? view->lines - 1 : 0;
2737 if (offset > lineno || offset + view->height <= lineno) {
2738 unsigned long half = view->height / 2;
2740 if (lineno > half)
2741 offset = lineno - half;
2742 else
2743 offset = 0;
2746 if (offset != view->offset || lineno != view->lineno) {
2747 view->offset = offset;
2748 view->lineno = lineno;
2749 return TRUE;
2752 return FALSE;
2755 /* Scrolling backend */
2756 static void
2757 do_scroll_view(struct view *view, int lines)
2759 bool redraw_current_line = FALSE;
2761 /* The rendering expects the new offset. */
2762 view->offset += lines;
2764 assert(0 <= view->offset && view->offset < view->lines);
2765 assert(lines);
2767 /* Move current line into the view. */
2768 if (view->lineno < view->offset) {
2769 view->lineno = view->offset;
2770 redraw_current_line = TRUE;
2771 } else if (view->lineno >= view->offset + view->height) {
2772 view->lineno = view->offset + view->height - 1;
2773 redraw_current_line = TRUE;
2776 assert(view->offset <= view->lineno && view->lineno < view->lines);
2778 /* Redraw the whole screen if scrolling is pointless. */
2779 if (view->height < ABS(lines)) {
2780 redraw_view(view);
2782 } else {
2783 int line = lines > 0 ? view->height - lines : 0;
2784 int end = line + ABS(lines);
2786 scrollok(view->win, TRUE);
2787 wscrl(view->win, lines);
2788 scrollok(view->win, FALSE);
2790 while (line < end && draw_view_line(view, line))
2791 line++;
2793 if (redraw_current_line)
2794 draw_view_line(view, view->lineno - view->offset);
2795 wnoutrefresh(view->win);
2798 view->has_scrolled = TRUE;
2799 report("");
2802 /* Scroll frontend */
2803 static void
2804 scroll_view(struct view *view, enum request request)
2806 int lines = 1;
2808 assert(view_is_displayed(view));
2810 switch (request) {
2811 case REQ_SCROLL_LEFT:
2812 if (view->yoffset == 0) {
2813 report("Cannot scroll beyond the first column");
2814 return;
2816 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2817 view->yoffset = 0;
2818 else
2819 view->yoffset -= apply_step(opt_hscroll, view->width);
2820 redraw_view_from(view, 0);
2821 report("");
2822 return;
2823 case REQ_SCROLL_RIGHT:
2824 view->yoffset += apply_step(opt_hscroll, view->width);
2825 redraw_view(view);
2826 report("");
2827 return;
2828 case REQ_SCROLL_PAGE_DOWN:
2829 lines = view->height;
2830 case REQ_SCROLL_LINE_DOWN:
2831 if (view->offset + lines > view->lines)
2832 lines = view->lines - view->offset;
2834 if (lines == 0 || view->offset + view->height >= view->lines) {
2835 report("Cannot scroll beyond the last line");
2836 return;
2838 break;
2840 case REQ_SCROLL_PAGE_UP:
2841 lines = view->height;
2842 case REQ_SCROLL_LINE_UP:
2843 if (lines > view->offset)
2844 lines = view->offset;
2846 if (lines == 0) {
2847 report("Cannot scroll beyond the first line");
2848 return;
2851 lines = -lines;
2852 break;
2854 default:
2855 die("request %d not handled in switch", request);
2858 do_scroll_view(view, lines);
2861 /* Cursor moving */
2862 static void
2863 move_view(struct view *view, enum request request)
2865 int scroll_steps = 0;
2866 int steps;
2868 switch (request) {
2869 case REQ_MOVE_FIRST_LINE:
2870 steps = -view->lineno;
2871 break;
2873 case REQ_MOVE_LAST_LINE:
2874 steps = view->lines - view->lineno - 1;
2875 break;
2877 case REQ_MOVE_PAGE_UP:
2878 steps = view->height > view->lineno
2879 ? -view->lineno : -view->height;
2880 break;
2882 case REQ_MOVE_PAGE_DOWN:
2883 steps = view->lineno + view->height >= view->lines
2884 ? view->lines - view->lineno - 1 : view->height;
2885 break;
2887 case REQ_MOVE_UP:
2888 steps = -1;
2889 break;
2891 case REQ_MOVE_DOWN:
2892 steps = 1;
2893 break;
2895 default:
2896 die("request %d not handled in switch", request);
2899 if (steps <= 0 && view->lineno == 0) {
2900 report("Cannot move beyond the first line");
2901 return;
2903 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2904 report("Cannot move beyond the last line");
2905 return;
2908 /* Move the current line */
2909 view->lineno += steps;
2910 assert(0 <= view->lineno && view->lineno < view->lines);
2912 /* Check whether the view needs to be scrolled */
2913 if (view->lineno < view->offset ||
2914 view->lineno >= view->offset + view->height) {
2915 scroll_steps = steps;
2916 if (steps < 0 && -steps > view->offset) {
2917 scroll_steps = -view->offset;
2919 } else if (steps > 0) {
2920 if (view->lineno == view->lines - 1 &&
2921 view->lines > view->height) {
2922 scroll_steps = view->lines - view->offset - 1;
2923 if (scroll_steps >= view->height)
2924 scroll_steps -= view->height - 1;
2929 if (!view_is_displayed(view)) {
2930 view->offset += scroll_steps;
2931 assert(0 <= view->offset && view->offset < view->lines);
2932 view->ops->select(view, &view->line[view->lineno]);
2933 return;
2936 /* Repaint the old "current" line if we be scrolling */
2937 if (ABS(steps) < view->height)
2938 draw_view_line(view, view->lineno - steps - view->offset);
2940 if (scroll_steps) {
2941 do_scroll_view(view, scroll_steps);
2942 return;
2945 /* Draw the current line */
2946 draw_view_line(view, view->lineno - view->offset);
2948 wnoutrefresh(view->win);
2949 report("");
2954 * Searching
2957 static void search_view(struct view *view, enum request request);
2959 static bool
2960 grep_text(struct view *view, const char *text[])
2962 regmatch_t pmatch;
2963 size_t i;
2965 for (i = 0; text[i]; i++)
2966 if (*text[i] &&
2967 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2968 return TRUE;
2969 return FALSE;
2972 static void
2973 select_view_line(struct view *view, unsigned long lineno)
2975 unsigned long old_lineno = view->lineno;
2976 unsigned long old_offset = view->offset;
2978 if (goto_view_line(view, view->offset, lineno)) {
2979 if (view_is_displayed(view)) {
2980 if (old_offset != view->offset) {
2981 redraw_view(view);
2982 } else {
2983 draw_view_line(view, old_lineno - view->offset);
2984 draw_view_line(view, view->lineno - view->offset);
2985 wnoutrefresh(view->win);
2987 } else {
2988 view->ops->select(view, &view->line[view->lineno]);
2993 static void
2994 find_next(struct view *view, enum request request)
2996 unsigned long lineno = view->lineno;
2997 int direction;
2999 if (!*view->grep) {
3000 if (!*opt_search)
3001 report("No previous search");
3002 else
3003 search_view(view, request);
3004 return;
3007 switch (request) {
3008 case REQ_SEARCH:
3009 case REQ_FIND_NEXT:
3010 direction = 1;
3011 break;
3013 case REQ_SEARCH_BACK:
3014 case REQ_FIND_PREV:
3015 direction = -1;
3016 break;
3018 default:
3019 return;
3022 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3023 lineno += direction;
3025 /* Note, lineno is unsigned long so will wrap around in which case it
3026 * will become bigger than view->lines. */
3027 for (; lineno < view->lines; lineno += direction) {
3028 if (view->ops->grep(view, &view->line[lineno])) {
3029 select_view_line(view, lineno);
3030 report("Line %ld matches '%s'", lineno + 1, view->grep);
3031 return;
3035 report("No match found for '%s'", view->grep);
3038 static void
3039 search_view(struct view *view, enum request request)
3041 int regex_err;
3043 if (view->regex) {
3044 regfree(view->regex);
3045 *view->grep = 0;
3046 } else {
3047 view->regex = calloc(1, sizeof(*view->regex));
3048 if (!view->regex)
3049 return;
3052 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3053 if (regex_err != 0) {
3054 char buf[SIZEOF_STR] = "unknown error";
3056 regerror(regex_err, view->regex, buf, sizeof(buf));
3057 report("Search failed: %s", buf);
3058 return;
3061 string_copy(view->grep, opt_search);
3063 find_next(view, request);
3067 * Incremental updating
3070 static void
3071 reset_view(struct view *view)
3073 int i;
3075 for (i = 0; i < view->lines; i++)
3076 free(view->line[i].data);
3077 free(view->line);
3079 view->p_offset = view->offset;
3080 view->p_yoffset = view->yoffset;
3081 view->p_lineno = view->lineno;
3083 view->line = NULL;
3084 view->offset = 0;
3085 view->yoffset = 0;
3086 view->lines = 0;
3087 view->lineno = 0;
3088 view->vid[0] = 0;
3089 view->update_secs = 0;
3092 static void
3093 free_argv(const char *argv[])
3095 int argc;
3097 for (argc = 0; argv[argc]; argc++)
3098 free((void *) argv[argc]);
3101 static const char *
3102 format_arg(const char *name)
3104 static struct {
3105 const char *name;
3106 size_t namelen;
3107 const char *value;
3108 const char *value_if_empty;
3109 } vars[] = {
3110 #define FORMAT_VAR(name, value, value_if_empty) \
3111 { name, STRING_SIZE(name), value, value_if_empty }
3112 FORMAT_VAR("%(directory)", opt_path, ""),
3113 FORMAT_VAR("%(file)", opt_file, ""),
3114 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3115 FORMAT_VAR("%(head)", ref_head, ""),
3116 FORMAT_VAR("%(commit)", ref_commit, ""),
3117 FORMAT_VAR("%(blob)", ref_blob, ""),
3118 FORMAT_VAR("%(branch)", ref_branch, ""),
3120 int i;
3122 for (i = 0; i < ARRAY_SIZE(vars); i++)
3123 if (!strncmp(name, vars[i].name, vars[i].namelen))
3124 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3126 report("Unknown replacement: `%s`", name);
3127 return NULL;
3130 static bool
3131 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3133 char buf[SIZEOF_STR];
3134 int argc;
3135 bool noreplace = flags == FORMAT_NONE;
3137 free_argv(dst_argv);
3139 for (argc = 0; src_argv[argc]; argc++) {
3140 const char *arg = src_argv[argc];
3141 size_t bufpos = 0;
3143 while (arg) {
3144 char *next = strstr(arg, "%(");
3145 int len = next - arg;
3146 const char *value;
3148 if (!next || noreplace) {
3149 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3150 noreplace = TRUE;
3151 len = strlen(arg);
3152 value = "";
3154 } else {
3155 value = format_arg(next);
3157 if (!value) {
3158 return FALSE;
3162 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3163 return FALSE;
3165 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3168 dst_argv[argc] = strdup(buf);
3169 if (!dst_argv[argc])
3170 break;
3173 dst_argv[argc] = NULL;
3175 return src_argv[argc] == NULL;
3178 static bool
3179 restore_view_position(struct view *view)
3181 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3182 return FALSE;
3184 /* Changing the view position cancels the restoring. */
3185 /* FIXME: Changing back to the first line is not detected. */
3186 if (view->offset != 0 || view->lineno != 0) {
3187 view->p_restore = FALSE;
3188 return FALSE;
3191 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3192 view_is_displayed(view))
3193 werase(view->win);
3195 view->yoffset = view->p_yoffset;
3196 view->p_restore = FALSE;
3198 return TRUE;
3201 static void
3202 end_update(struct view *view, bool force)
3204 if (!view->pipe)
3205 return;
3206 while (!view->ops->read(view, NULL))
3207 if (!force)
3208 return;
3209 if (force)
3210 io_kill(view->pipe);
3211 io_done(view->pipe);
3212 view->pipe = NULL;
3215 static void
3216 setup_update(struct view *view, const char *vid)
3218 reset_view(view);
3219 string_copy_rev(view->vid, vid);
3220 view->pipe = &view->io;
3221 view->start_time = time(NULL);
3224 static bool
3225 prepare_update(struct view *view, const char *argv[], const char *dir)
3227 if (view->pipe)
3228 end_update(view, TRUE);
3229 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3232 static bool
3233 prepare_update_file(struct view *view, const char *name)
3235 if (view->pipe)
3236 end_update(view, TRUE);
3237 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3240 static bool
3241 begin_update(struct view *view, bool refresh)
3243 if (view->pipe)
3244 end_update(view, TRUE);
3246 if (!refresh) {
3247 if (view->ops->prepare) {
3248 if (!view->ops->prepare(view))
3249 return FALSE;
3250 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3251 return FALSE;
3254 /* Put the current ref_* value to the view title ref
3255 * member. This is needed by the blob view. Most other
3256 * views sets it automatically after loading because the
3257 * first line is a commit line. */
3258 string_copy_rev(view->ref, view->id);
3261 if (!io_start(&view->io))
3262 return FALSE;
3264 setup_update(view, view->id);
3266 return TRUE;
3269 static bool
3270 update_view(struct view *view)
3272 char out_buffer[BUFSIZ * 2];
3273 char *line;
3274 /* Clear the view and redraw everything since the tree sorting
3275 * might have rearranged things. */
3276 bool redraw = view->lines == 0;
3277 bool can_read = TRUE;
3279 if (!view->pipe)
3280 return TRUE;
3282 if (!io_can_read(view->pipe)) {
3283 if (view->lines == 0 && view_is_displayed(view)) {
3284 time_t secs = time(NULL) - view->start_time;
3286 if (secs > 1 && secs > view->update_secs) {
3287 if (view->update_secs == 0)
3288 redraw_view(view);
3289 update_view_title(view);
3290 view->update_secs = secs;
3293 return TRUE;
3296 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3297 if (opt_iconv_in != ICONV_NONE) {
3298 ICONV_CONST char *inbuf = line;
3299 size_t inlen = strlen(line) + 1;
3301 char *outbuf = out_buffer;
3302 size_t outlen = sizeof(out_buffer);
3304 size_t ret;
3306 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3307 if (ret != (size_t) -1)
3308 line = out_buffer;
3311 if (!view->ops->read(view, line)) {
3312 report("Allocation failure");
3313 end_update(view, TRUE);
3314 return FALSE;
3319 unsigned long lines = view->lines;
3320 int digits;
3322 for (digits = 0; lines; digits++)
3323 lines /= 10;
3325 /* Keep the displayed view in sync with line number scaling. */
3326 if (digits != view->digits) {
3327 view->digits = digits;
3328 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3329 redraw = TRUE;
3333 if (io_error(view->pipe)) {
3334 report("Failed to read: %s", io_strerror(view->pipe));
3335 end_update(view, TRUE);
3337 } else if (io_eof(view->pipe)) {
3338 report("");
3339 end_update(view, FALSE);
3342 if (restore_view_position(view))
3343 redraw = TRUE;
3345 if (!view_is_displayed(view))
3346 return TRUE;
3348 if (redraw)
3349 redraw_view_from(view, 0);
3350 else
3351 redraw_view_dirty(view);
3353 /* Update the title _after_ the redraw so that if the redraw picks up a
3354 * commit reference in view->ref it'll be available here. */
3355 update_view_title(view);
3356 return TRUE;
3359 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3361 static struct line *
3362 add_line_data(struct view *view, void *data, enum line_type type)
3364 struct line *line;
3366 if (!realloc_lines(&view->line, view->lines, 1))
3367 return NULL;
3369 line = &view->line[view->lines++];
3370 memset(line, 0, sizeof(*line));
3371 line->type = type;
3372 line->data = data;
3373 line->dirty = 1;
3375 return line;
3378 static struct line *
3379 add_line_text(struct view *view, const char *text, enum line_type type)
3381 char *data = text ? strdup(text) : NULL;
3383 return data ? add_line_data(view, data, type) : NULL;
3386 static struct line *
3387 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3389 char buf[SIZEOF_STR];
3390 va_list args;
3392 va_start(args, fmt);
3393 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3394 buf[0] = 0;
3395 va_end(args);
3397 return buf[0] ? add_line_text(view, buf, type) : NULL;
3401 * View opening
3404 enum open_flags {
3405 OPEN_DEFAULT = 0, /* Use default view switching. */
3406 OPEN_SPLIT = 1, /* Split current view. */
3407 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3408 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3409 OPEN_PREPARED = 32, /* Open already prepared command. */
3412 static void
3413 open_view(struct view *prev, enum request request, enum open_flags flags)
3415 bool split = !!(flags & OPEN_SPLIT);
3416 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3417 bool nomaximize = !!(flags & OPEN_REFRESH);
3418 struct view *view = VIEW(request);
3419 int nviews = displayed_views();
3420 struct view *base_view = display[0];
3422 if (view == prev && nviews == 1 && !reload) {
3423 report("Already in %s view", view->name);
3424 return;
3427 if (view->git_dir && !opt_git_dir[0]) {
3428 report("The %s view is disabled in pager view", view->name);
3429 return;
3432 if (split) {
3433 display[1] = view;
3434 current_view = 1;
3435 } else if (!nomaximize) {
3436 /* Maximize the current view. */
3437 memset(display, 0, sizeof(display));
3438 current_view = 0;
3439 display[current_view] = view;
3442 /* No parent signals that this is the first loaded view. */
3443 if (prev && view != prev) {
3444 view->parent = prev;
3447 /* Resize the view when switching between split- and full-screen,
3448 * or when switching between two different full-screen views. */
3449 if (nviews != displayed_views() ||
3450 (nviews == 1 && base_view != display[0]))
3451 resize_display();
3453 if (view->ops->open) {
3454 if (view->pipe)
3455 end_update(view, TRUE);
3456 if (!view->ops->open(view)) {
3457 report("Failed to load %s view", view->name);
3458 return;
3460 restore_view_position(view);
3462 } else if ((reload || strcmp(view->vid, view->id)) &&
3463 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3464 report("Failed to load %s view", view->name);
3465 return;
3468 if (split && prev->lineno - prev->offset >= prev->height) {
3469 /* Take the title line into account. */
3470 int lines = prev->lineno - prev->offset - prev->height + 1;
3472 /* Scroll the view that was split if the current line is
3473 * outside the new limited view. */
3474 do_scroll_view(prev, lines);
3477 if (prev && view != prev && split && view_is_displayed(prev)) {
3478 /* "Blur" the previous view. */
3479 update_view_title(prev);
3482 if (view->pipe && view->lines == 0) {
3483 /* Clear the old view and let the incremental updating refill
3484 * the screen. */
3485 werase(view->win);
3486 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3487 report("");
3488 } else if (view_is_displayed(view)) {
3489 redraw_view(view);
3490 report("");
3494 static void
3495 open_external_viewer(const char *argv[], const char *dir)
3497 def_prog_mode(); /* save current tty modes */
3498 endwin(); /* restore original tty modes */
3499 io_run_fg(argv, dir);
3500 fprintf(stderr, "Press Enter to continue");
3501 getc(opt_tty);
3502 reset_prog_mode();
3503 redraw_display(TRUE);
3506 static void
3507 open_mergetool(const char *file)
3509 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3511 open_external_viewer(mergetool_argv, opt_cdup);
3514 static void
3515 open_editor(const char *file)
3517 const char *editor_argv[] = { "vi", file, NULL };
3518 const char *editor;
3520 editor = getenv("GIT_EDITOR");
3521 if (!editor && *opt_editor)
3522 editor = opt_editor;
3523 if (!editor)
3524 editor = getenv("VISUAL");
3525 if (!editor)
3526 editor = getenv("EDITOR");
3527 if (!editor)
3528 editor = "vi";
3530 editor_argv[0] = editor;
3531 open_external_viewer(editor_argv, opt_cdup);
3534 static void
3535 open_run_request(enum request request)
3537 struct run_request *req = get_run_request(request);
3538 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3540 if (!req) {
3541 report("Unknown run request");
3542 return;
3545 if (format_argv(argv, req->argv, FORMAT_ALL))
3546 open_external_viewer(argv, NULL);
3547 free_argv(argv);
3551 * User request switch noodle
3554 static int
3555 view_driver(struct view *view, enum request request)
3557 int i;
3559 if (request == REQ_NONE)
3560 return TRUE;
3562 if (request > REQ_NONE) {
3563 open_run_request(request);
3564 /* FIXME: When all views can refresh always do this. */
3565 if (view == VIEW(REQ_VIEW_STATUS) ||
3566 view == VIEW(REQ_VIEW_MAIN) ||
3567 view == VIEW(REQ_VIEW_LOG) ||
3568 view == VIEW(REQ_VIEW_BRANCH) ||
3569 view == VIEW(REQ_VIEW_STAGE))
3570 request = REQ_REFRESH;
3571 else
3572 return TRUE;
3575 if (view && view->lines) {
3576 request = view->ops->request(view, request, &view->line[view->lineno]);
3577 if (request == REQ_NONE)
3578 return TRUE;
3581 switch (request) {
3582 case REQ_MOVE_UP:
3583 case REQ_MOVE_DOWN:
3584 case REQ_MOVE_PAGE_UP:
3585 case REQ_MOVE_PAGE_DOWN:
3586 case REQ_MOVE_FIRST_LINE:
3587 case REQ_MOVE_LAST_LINE:
3588 move_view(view, request);
3589 break;
3591 case REQ_SCROLL_LEFT:
3592 case REQ_SCROLL_RIGHT:
3593 case REQ_SCROLL_LINE_DOWN:
3594 case REQ_SCROLL_LINE_UP:
3595 case REQ_SCROLL_PAGE_DOWN:
3596 case REQ_SCROLL_PAGE_UP:
3597 scroll_view(view, request);
3598 break;
3600 case REQ_VIEW_BLAME:
3601 if (!opt_file[0]) {
3602 report("No file chosen, press %s to open tree view",
3603 get_key(view->keymap, REQ_VIEW_TREE));
3604 break;
3606 open_view(view, request, OPEN_DEFAULT);
3607 break;
3609 case REQ_VIEW_BLOB:
3610 if (!ref_blob[0]) {
3611 report("No file chosen, press %s to open tree view",
3612 get_key(view->keymap, REQ_VIEW_TREE));
3613 break;
3615 open_view(view, request, OPEN_DEFAULT);
3616 break;
3618 case REQ_VIEW_PAGER:
3619 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3620 report("No pager content, press %s to run command from prompt",
3621 get_key(view->keymap, REQ_PROMPT));
3622 break;
3624 open_view(view, request, OPEN_DEFAULT);
3625 break;
3627 case REQ_VIEW_STAGE:
3628 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3629 report("No stage content, press %s to open the status view and choose file",
3630 get_key(view->keymap, REQ_VIEW_STATUS));
3631 break;
3633 open_view(view, request, OPEN_DEFAULT);
3634 break;
3636 case REQ_VIEW_STATUS:
3637 if (opt_is_inside_work_tree == FALSE) {
3638 report("The status view requires a working tree");
3639 break;
3641 open_view(view, request, OPEN_DEFAULT);
3642 break;
3644 case REQ_VIEW_MAIN:
3645 case REQ_VIEW_DIFF:
3646 case REQ_VIEW_LOG:
3647 case REQ_VIEW_TREE:
3648 case REQ_VIEW_HELP:
3649 case REQ_VIEW_BRANCH:
3650 open_view(view, request, OPEN_DEFAULT);
3651 break;
3653 case REQ_NEXT:
3654 case REQ_PREVIOUS:
3655 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3657 if ((view == VIEW(REQ_VIEW_DIFF) &&
3658 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3659 (view == VIEW(REQ_VIEW_DIFF) &&
3660 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3661 (view == VIEW(REQ_VIEW_STAGE) &&
3662 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3663 (view == VIEW(REQ_VIEW_BLOB) &&
3664 view->parent == VIEW(REQ_VIEW_TREE)) ||
3665 (view == VIEW(REQ_VIEW_MAIN) &&
3666 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3667 int line;
3669 view = view->parent;
3670 line = view->lineno;
3671 move_view(view, request);
3672 if (view_is_displayed(view))
3673 update_view_title(view);
3674 if (line != view->lineno)
3675 view->ops->request(view, REQ_ENTER,
3676 &view->line[view->lineno]);
3678 } else {
3679 move_view(view, request);
3681 break;
3683 case REQ_VIEW_NEXT:
3685 int nviews = displayed_views();
3686 int next_view = (current_view + 1) % nviews;
3688 if (next_view == current_view) {
3689 report("Only one view is displayed");
3690 break;
3693 current_view = next_view;
3694 /* Blur out the title of the previous view. */
3695 update_view_title(view);
3696 report("");
3697 break;
3699 case REQ_REFRESH:
3700 report("Refreshing is not yet supported for the %s view", view->name);
3701 break;
3703 case REQ_MAXIMIZE:
3704 if (displayed_views() == 2)
3705 maximize_view(view);
3706 break;
3708 case REQ_OPTIONS:
3709 open_option_menu();
3710 break;
3712 case REQ_TOGGLE_LINENO:
3713 toggle_view_option(&opt_line_number, "line numbers");
3714 break;
3716 case REQ_TOGGLE_DATE:
3717 toggle_date();
3718 break;
3720 case REQ_TOGGLE_AUTHOR:
3721 toggle_author();
3722 break;
3724 case REQ_TOGGLE_REV_GRAPH:
3725 toggle_view_option(&opt_rev_graph, "revision graph display");
3726 break;
3728 case REQ_TOGGLE_REFS:
3729 toggle_view_option(&opt_show_refs, "reference display");
3730 break;
3732 case REQ_TOGGLE_SORT_FIELD:
3733 case REQ_TOGGLE_SORT_ORDER:
3734 report("Sorting is not yet supported for the %s view", view->name);
3735 break;
3737 case REQ_SEARCH:
3738 case REQ_SEARCH_BACK:
3739 search_view(view, request);
3740 break;
3742 case REQ_FIND_NEXT:
3743 case REQ_FIND_PREV:
3744 find_next(view, request);
3745 break;
3747 case REQ_STOP_LOADING:
3748 for (i = 0; i < ARRAY_SIZE(views); i++) {
3749 view = &views[i];
3750 if (view->pipe)
3751 report("Stopped loading the %s view", view->name),
3752 end_update(view, TRUE);
3754 break;
3756 case REQ_SHOW_VERSION:
3757 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3758 return TRUE;
3760 case REQ_SCREEN_REDRAW:
3761 redraw_display(TRUE);
3762 break;
3764 case REQ_EDIT:
3765 report("Nothing to edit");
3766 break;
3768 case REQ_ENTER:
3769 report("Nothing to enter");
3770 break;
3772 case REQ_VIEW_CLOSE:
3773 /* XXX: Mark closed views by letting view->parent point to the
3774 * view itself. Parents to closed view should never be
3775 * followed. */
3776 if (view->parent &&
3777 view->parent->parent != view->parent) {
3778 maximize_view(view->parent);
3779 view->parent = view;
3780 break;
3782 /* Fall-through */
3783 case REQ_QUIT:
3784 return FALSE;
3786 default:
3787 report("Unknown key, press %s for help",
3788 get_key(view->keymap, REQ_VIEW_HELP));
3789 return TRUE;
3792 return TRUE;
3797 * View backend utilities
3800 enum sort_field {
3801 ORDERBY_NAME,
3802 ORDERBY_DATE,
3803 ORDERBY_AUTHOR,
3806 struct sort_state {
3807 const enum sort_field *fields;
3808 size_t size, current;
3809 bool reverse;
3812 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3813 #define get_sort_field(state) ((state).fields[(state).current])
3814 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3816 static void
3817 sort_view(struct view *view, enum request request, struct sort_state *state,
3818 int (*compare)(const void *, const void *))
3820 switch (request) {
3821 case REQ_TOGGLE_SORT_FIELD:
3822 state->current = (state->current + 1) % state->size;
3823 break;
3825 case REQ_TOGGLE_SORT_ORDER:
3826 state->reverse = !state->reverse;
3827 break;
3828 default:
3829 die("Not a sort request");
3832 qsort(view->line, view->lines, sizeof(*view->line), compare);
3833 redraw_view(view);
3836 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3838 /* Small author cache to reduce memory consumption. It uses binary
3839 * search to lookup or find place to position new entries. No entries
3840 * are ever freed. */
3841 static const char *
3842 get_author(const char *name)
3844 static const char **authors;
3845 static size_t authors_size;
3846 int from = 0, to = authors_size - 1;
3848 while (from <= to) {
3849 size_t pos = (to + from) / 2;
3850 int cmp = strcmp(name, authors[pos]);
3852 if (!cmp)
3853 return authors[pos];
3855 if (cmp < 0)
3856 to = pos - 1;
3857 else
3858 from = pos + 1;
3861 if (!realloc_authors(&authors, authors_size, 1))
3862 return NULL;
3863 name = strdup(name);
3864 if (!name)
3865 return NULL;
3867 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3868 authors[from] = name;
3869 authors_size++;
3871 return name;
3874 static void
3875 parse_timesec(struct time *time, const char *sec)
3877 time->sec = (time_t) atol(sec);
3880 static void
3881 parse_timezone(struct time *time, const char *zone)
3883 long tz;
3885 tz = ('0' - zone[1]) * 60 * 60 * 10;
3886 tz += ('0' - zone[2]) * 60 * 60;
3887 tz += ('0' - zone[3]) * 60;
3888 tz += ('0' - zone[4]);
3890 if (zone[0] == '-')
3891 tz = -tz;
3893 time->tz = tz;
3894 time->sec -= tz;
3897 /* Parse author lines where the name may be empty:
3898 * author <email@address.tld> 1138474660 +0100
3900 static void
3901 parse_author_line(char *ident, const char **author, struct time *time)
3903 char *nameend = strchr(ident, '<');
3904 char *emailend = strchr(ident, '>');
3906 if (nameend && emailend)
3907 *nameend = *emailend = 0;
3908 ident = chomp_string(ident);
3909 if (!*ident) {
3910 if (nameend)
3911 ident = chomp_string(nameend + 1);
3912 if (!*ident)
3913 ident = "Unknown";
3916 *author = get_author(ident);
3918 /* Parse epoch and timezone */
3919 if (emailend && emailend[1] == ' ') {
3920 char *secs = emailend + 2;
3921 char *zone = strchr(secs, ' ');
3923 parse_timesec(time, secs);
3925 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3926 parse_timezone(time, zone + 1);
3930 static bool
3931 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3933 char rev[SIZEOF_REV];
3934 const char *revlist_argv[] = {
3935 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3937 struct menu_item *items;
3938 char text[SIZEOF_STR];
3939 bool ok = TRUE;
3940 int i;
3942 items = calloc(*parents + 1, sizeof(*items));
3943 if (!items)
3944 return FALSE;
3946 for (i = 0; i < *parents; i++) {
3947 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3948 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3949 !(items[i].text = strdup(text))) {
3950 ok = FALSE;
3951 break;
3955 if (ok) {
3956 *parents = 0;
3957 ok = prompt_menu("Select parent", items, parents);
3959 for (i = 0; items[i].text; i++)
3960 free((char *) items[i].text);
3961 free(items);
3962 return ok;
3965 static bool
3966 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3968 char buf[SIZEOF_STR * 4];
3969 const char *revlist_argv[] = {
3970 "git", "log", "--no-color", "-1",
3971 "--pretty=format:%P", id, "--", path, NULL
3973 int parents;
3975 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3976 (parents = strlen(buf) / 40) < 0) {
3977 report("Failed to get parent information");
3978 return FALSE;
3980 } else if (parents == 0) {
3981 if (path)
3982 report("Path '%s' does not exist in the parent", path);
3983 else
3984 report("The selected commit has no parents");
3985 return FALSE;
3988 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3989 return FALSE;
3991 string_copy_rev(rev, &buf[41 * parents]);
3992 return TRUE;
3996 * Pager backend
3999 static bool
4000 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4002 char text[SIZEOF_STR];
4004 if (opt_line_number && draw_lineno(view, lineno))
4005 return TRUE;
4007 string_expand(text, sizeof(text), line->data, opt_tab_size);
4008 draw_text(view, line->type, text, TRUE);
4009 return TRUE;
4012 static bool
4013 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4015 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4016 char ref[SIZEOF_STR];
4018 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4019 return TRUE;
4021 /* This is the only fatal call, since it can "corrupt" the buffer. */
4022 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4023 return FALSE;
4025 return TRUE;
4028 static void
4029 add_pager_refs(struct view *view, struct line *line)
4031 char buf[SIZEOF_STR];
4032 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4033 struct ref_list *list;
4034 size_t bufpos = 0, i;
4035 const char *sep = "Refs: ";
4036 bool is_tag = FALSE;
4038 assert(line->type == LINE_COMMIT);
4040 list = get_ref_list(commit_id);
4041 if (!list) {
4042 if (view == VIEW(REQ_VIEW_DIFF))
4043 goto try_add_describe_ref;
4044 return;
4047 for (i = 0; i < list->size; i++) {
4048 struct ref *ref = list->refs[i];
4049 const char *fmt = ref->tag ? "%s[%s]" :
4050 ref->remote ? "%s<%s>" : "%s%s";
4052 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4053 return;
4054 sep = ", ";
4055 if (ref->tag)
4056 is_tag = TRUE;
4059 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4060 try_add_describe_ref:
4061 /* Add <tag>-g<commit_id> "fake" reference. */
4062 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4063 return;
4066 if (bufpos == 0)
4067 return;
4069 add_line_text(view, buf, LINE_PP_REFS);
4072 static bool
4073 pager_read(struct view *view, char *data)
4075 struct line *line;
4077 if (!data)
4078 return TRUE;
4080 line = add_line_text(view, data, get_line_type(data));
4081 if (!line)
4082 return FALSE;
4084 if (line->type == LINE_COMMIT &&
4085 (view == VIEW(REQ_VIEW_DIFF) ||
4086 view == VIEW(REQ_VIEW_LOG)))
4087 add_pager_refs(view, line);
4089 return TRUE;
4092 static enum request
4093 pager_request(struct view *view, enum request request, struct line *line)
4095 int split = 0;
4097 if (request != REQ_ENTER)
4098 return request;
4100 if (line->type == LINE_COMMIT &&
4101 (view == VIEW(REQ_VIEW_LOG) ||
4102 view == VIEW(REQ_VIEW_PAGER))) {
4103 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4104 split = 1;
4107 /* Always scroll the view even if it was split. That way
4108 * you can use Enter to scroll through the log view and
4109 * split open each commit diff. */
4110 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4112 /* FIXME: A minor workaround. Scrolling the view will call report("")
4113 * but if we are scrolling a non-current view this won't properly
4114 * update the view title. */
4115 if (split)
4116 update_view_title(view);
4118 return REQ_NONE;
4121 static bool
4122 pager_grep(struct view *view, struct line *line)
4124 const char *text[] = { line->data, NULL };
4126 return grep_text(view, text);
4129 static void
4130 pager_select(struct view *view, struct line *line)
4132 if (line->type == LINE_COMMIT) {
4133 char *text = (char *)line->data + STRING_SIZE("commit ");
4135 if (view != VIEW(REQ_VIEW_PAGER))
4136 string_copy_rev(view->ref, text);
4137 string_copy_rev(ref_commit, text);
4141 static struct view_ops pager_ops = {
4142 "line",
4143 NULL,
4144 NULL,
4145 pager_read,
4146 pager_draw,
4147 pager_request,
4148 pager_grep,
4149 pager_select,
4152 static const char *log_argv[SIZEOF_ARG] = {
4153 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4156 static enum request
4157 log_request(struct view *view, enum request request, struct line *line)
4159 switch (request) {
4160 case REQ_REFRESH:
4161 load_refs();
4162 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4163 return REQ_NONE;
4164 default:
4165 return pager_request(view, request, line);
4169 static struct view_ops log_ops = {
4170 "line",
4171 log_argv,
4172 NULL,
4173 pager_read,
4174 pager_draw,
4175 log_request,
4176 pager_grep,
4177 pager_select,
4180 static const char *diff_argv[SIZEOF_ARG] = {
4181 "git", "show", "--pretty=fuller", "--no-color", "--root",
4182 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4185 static struct view_ops diff_ops = {
4186 "line",
4187 diff_argv,
4188 NULL,
4189 pager_read,
4190 pager_draw,
4191 pager_request,
4192 pager_grep,
4193 pager_select,
4197 * Help backend
4200 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4202 static bool
4203 help_open_keymap_title(struct view *view, enum keymap keymap)
4205 struct line *line;
4207 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4208 help_keymap_hidden[keymap] ? '+' : '-',
4209 enum_name(keymap_table[keymap]));
4210 if (line)
4211 line->other = keymap;
4213 return help_keymap_hidden[keymap];
4216 static void
4217 help_open_keymap(struct view *view, enum keymap keymap)
4219 const char *group = NULL;
4220 char buf[SIZEOF_STR];
4221 size_t bufpos;
4222 bool add_title = TRUE;
4223 int i;
4225 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4226 const char *key = NULL;
4228 if (req_info[i].request == REQ_NONE)
4229 continue;
4231 if (!req_info[i].request) {
4232 group = req_info[i].help;
4233 continue;
4236 key = get_keys(keymap, req_info[i].request, TRUE);
4237 if (!key || !*key)
4238 continue;
4240 if (add_title && help_open_keymap_title(view, keymap))
4241 return;
4242 add_title = FALSE;
4244 if (group) {
4245 add_line_text(view, group, LINE_HELP_GROUP);
4246 group = NULL;
4249 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4250 enum_name(req_info[i]), req_info[i].help);
4253 group = "External commands:";
4255 for (i = 0; i < run_requests; i++) {
4256 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4257 const char *key;
4258 int argc;
4260 if (!req || req->keymap != keymap)
4261 continue;
4263 key = get_key_name(req->key);
4264 if (!*key)
4265 key = "(no key defined)";
4267 if (add_title && help_open_keymap_title(view, keymap))
4268 return;
4269 if (group) {
4270 add_line_text(view, group, LINE_HELP_GROUP);
4271 group = NULL;
4274 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4275 if (!string_format_from(buf, &bufpos, "%s%s",
4276 argc ? " " : "", req->argv[argc]))
4277 return;
4279 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4283 static bool
4284 help_open(struct view *view)
4286 enum keymap keymap;
4288 reset_view(view);
4289 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4290 add_line_text(view, "", LINE_DEFAULT);
4292 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4293 help_open_keymap(view, keymap);
4295 return TRUE;
4298 static enum request
4299 help_request(struct view *view, enum request request, struct line *line)
4301 switch (request) {
4302 case REQ_ENTER:
4303 if (line->type == LINE_HELP_KEYMAP) {
4304 help_keymap_hidden[line->other] =
4305 !help_keymap_hidden[line->other];
4306 view->p_restore = TRUE;
4307 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4310 return REQ_NONE;
4311 default:
4312 return pager_request(view, request, line);
4316 static struct view_ops help_ops = {
4317 "line",
4318 NULL,
4319 help_open,
4320 NULL,
4321 pager_draw,
4322 help_request,
4323 pager_grep,
4324 pager_select,
4329 * Tree backend
4332 struct tree_stack_entry {
4333 struct tree_stack_entry *prev; /* Entry below this in the stack */
4334 unsigned long lineno; /* Line number to restore */
4335 char *name; /* Position of name in opt_path */
4338 /* The top of the path stack. */
4339 static struct tree_stack_entry *tree_stack = NULL;
4340 unsigned long tree_lineno = 0;
4342 static void
4343 pop_tree_stack_entry(void)
4345 struct tree_stack_entry *entry = tree_stack;
4347 tree_lineno = entry->lineno;
4348 entry->name[0] = 0;
4349 tree_stack = entry->prev;
4350 free(entry);
4353 static void
4354 push_tree_stack_entry(const char *name, unsigned long lineno)
4356 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4357 size_t pathlen = strlen(opt_path);
4359 if (!entry)
4360 return;
4362 entry->prev = tree_stack;
4363 entry->name = opt_path + pathlen;
4364 tree_stack = entry;
4366 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4367 pop_tree_stack_entry();
4368 return;
4371 /* Move the current line to the first tree entry. */
4372 tree_lineno = 1;
4373 entry->lineno = lineno;
4376 /* Parse output from git-ls-tree(1):
4378 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4381 #define SIZEOF_TREE_ATTR \
4382 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4384 #define SIZEOF_TREE_MODE \
4385 STRING_SIZE("100644 ")
4387 #define TREE_ID_OFFSET \
4388 STRING_SIZE("100644 blob ")
4390 struct tree_entry {
4391 char id[SIZEOF_REV];
4392 mode_t mode;
4393 struct time time; /* Date from the author ident. */
4394 const char *author; /* Author of the commit. */
4395 char name[1];
4398 static const char *
4399 tree_path(const struct line *line)
4401 return ((struct tree_entry *) line->data)->name;
4404 static int
4405 tree_compare_entry(const struct line *line1, const struct line *line2)
4407 if (line1->type != line2->type)
4408 return line1->type == LINE_TREE_DIR ? -1 : 1;
4409 return strcmp(tree_path(line1), tree_path(line2));
4412 static const enum sort_field tree_sort_fields[] = {
4413 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4415 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4417 static int
4418 tree_compare(const void *l1, const void *l2)
4420 const struct line *line1 = (const struct line *) l1;
4421 const struct line *line2 = (const struct line *) l2;
4422 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4423 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4425 if (line1->type == LINE_TREE_HEAD)
4426 return -1;
4427 if (line2->type == LINE_TREE_HEAD)
4428 return 1;
4430 switch (get_sort_field(tree_sort_state)) {
4431 case ORDERBY_DATE:
4432 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4434 case ORDERBY_AUTHOR:
4435 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4437 case ORDERBY_NAME:
4438 default:
4439 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4444 static struct line *
4445 tree_entry(struct view *view, enum line_type type, const char *path,
4446 const char *mode, const char *id)
4448 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4449 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4451 if (!entry || !line) {
4452 free(entry);
4453 return NULL;
4456 strncpy(entry->name, path, strlen(path));
4457 if (mode)
4458 entry->mode = strtoul(mode, NULL, 8);
4459 if (id)
4460 string_copy_rev(entry->id, id);
4462 return line;
4465 static bool
4466 tree_read_date(struct view *view, char *text, bool *read_date)
4468 static const char *author_name;
4469 static struct time author_time;
4471 if (!text && *read_date) {
4472 *read_date = FALSE;
4473 return TRUE;
4475 } else if (!text) {
4476 char *path = *opt_path ? opt_path : ".";
4477 /* Find next entry to process */
4478 const char *log_file[] = {
4479 "git", "log", "--no-color", "--pretty=raw",
4480 "--cc", "--raw", view->id, "--", path, NULL
4482 struct io io = {};
4484 if (!view->lines) {
4485 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4486 report("Tree is empty");
4487 return TRUE;
4490 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4491 report("Failed to load tree data");
4492 return TRUE;
4495 io_done(view->pipe);
4496 view->io = io;
4497 *read_date = TRUE;
4498 return FALSE;
4500 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4501 parse_author_line(text + STRING_SIZE("author "),
4502 &author_name, &author_time);
4504 } else if (*text == ':') {
4505 char *pos;
4506 size_t annotated = 1;
4507 size_t i;
4509 pos = strchr(text, '\t');
4510 if (!pos)
4511 return TRUE;
4512 text = pos + 1;
4513 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4514 text += strlen(opt_path);
4515 pos = strchr(text, '/');
4516 if (pos)
4517 *pos = 0;
4519 for (i = 1; i < view->lines; i++) {
4520 struct line *line = &view->line[i];
4521 struct tree_entry *entry = line->data;
4523 annotated += !!entry->author;
4524 if (entry->author || strcmp(entry->name, text))
4525 continue;
4527 entry->author = author_name;
4528 entry->time = author_time;
4529 line->dirty = 1;
4530 break;
4533 if (annotated == view->lines)
4534 io_kill(view->pipe);
4536 return TRUE;
4539 static bool
4540 tree_read(struct view *view, char *text)
4542 static bool read_date = FALSE;
4543 struct tree_entry *data;
4544 struct line *entry, *line;
4545 enum line_type type;
4546 size_t textlen = text ? strlen(text) : 0;
4547 char *path = text + SIZEOF_TREE_ATTR;
4549 if (read_date || !text)
4550 return tree_read_date(view, text, &read_date);
4552 if (textlen <= SIZEOF_TREE_ATTR)
4553 return FALSE;
4554 if (view->lines == 0 &&
4555 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4556 return FALSE;
4558 /* Strip the path part ... */
4559 if (*opt_path) {
4560 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4561 size_t striplen = strlen(opt_path);
4563 if (pathlen > striplen)
4564 memmove(path, path + striplen,
4565 pathlen - striplen + 1);
4567 /* Insert "link" to parent directory. */
4568 if (view->lines == 1 &&
4569 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4570 return FALSE;
4573 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4574 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4575 if (!entry)
4576 return FALSE;
4577 data = entry->data;
4579 /* Skip "Directory ..." and ".." line. */
4580 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4581 if (tree_compare_entry(line, entry) <= 0)
4582 continue;
4584 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4586 line->data = data;
4587 line->type = type;
4588 for (; line <= entry; line++)
4589 line->dirty = line->cleareol = 1;
4590 return TRUE;
4593 if (tree_lineno > view->lineno) {
4594 view->lineno = tree_lineno;
4595 tree_lineno = 0;
4598 return TRUE;
4601 static bool
4602 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4604 struct tree_entry *entry = line->data;
4606 if (line->type == LINE_TREE_HEAD) {
4607 if (draw_text(view, line->type, "Directory path /", TRUE))
4608 return TRUE;
4609 } else {
4610 if (draw_mode(view, entry->mode))
4611 return TRUE;
4613 if (opt_author && draw_author(view, entry->author))
4614 return TRUE;
4616 if (opt_date && draw_date(view, &entry->time))
4617 return TRUE;
4619 if (draw_text(view, line->type, entry->name, TRUE))
4620 return TRUE;
4621 return TRUE;
4624 static void
4625 open_blob_editor()
4627 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4628 int fd = mkstemp(file);
4630 if (fd == -1)
4631 report("Failed to create temporary file");
4632 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4633 report("Failed to save blob data to file");
4634 else
4635 open_editor(file);
4636 if (fd != -1)
4637 unlink(file);
4640 static enum request
4641 tree_request(struct view *view, enum request request, struct line *line)
4643 enum open_flags flags;
4645 switch (request) {
4646 case REQ_VIEW_BLAME:
4647 if (line->type != LINE_TREE_FILE) {
4648 report("Blame only supported for files");
4649 return REQ_NONE;
4652 string_copy(opt_ref, view->vid);
4653 return request;
4655 case REQ_EDIT:
4656 if (line->type != LINE_TREE_FILE) {
4657 report("Edit only supported for files");
4658 } else if (!is_head_commit(view->vid)) {
4659 open_blob_editor();
4660 } else {
4661 open_editor(opt_file);
4663 return REQ_NONE;
4665 case REQ_TOGGLE_SORT_FIELD:
4666 case REQ_TOGGLE_SORT_ORDER:
4667 sort_view(view, request, &tree_sort_state, tree_compare);
4668 return REQ_NONE;
4670 case REQ_PARENT:
4671 if (!*opt_path) {
4672 /* quit view if at top of tree */
4673 return REQ_VIEW_CLOSE;
4675 /* fake 'cd ..' */
4676 line = &view->line[1];
4677 break;
4679 case REQ_ENTER:
4680 break;
4682 default:
4683 return request;
4686 /* Cleanup the stack if the tree view is at a different tree. */
4687 while (!*opt_path && tree_stack)
4688 pop_tree_stack_entry();
4690 switch (line->type) {
4691 case LINE_TREE_DIR:
4692 /* Depending on whether it is a subdirectory or parent link
4693 * mangle the path buffer. */
4694 if (line == &view->line[1] && *opt_path) {
4695 pop_tree_stack_entry();
4697 } else {
4698 const char *basename = tree_path(line);
4700 push_tree_stack_entry(basename, view->lineno);
4703 /* Trees and subtrees share the same ID, so they are not not
4704 * unique like blobs. */
4705 flags = OPEN_RELOAD;
4706 request = REQ_VIEW_TREE;
4707 break;
4709 case LINE_TREE_FILE:
4710 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4711 request = REQ_VIEW_BLOB;
4712 break;
4714 default:
4715 return REQ_NONE;
4718 open_view(view, request, flags);
4719 if (request == REQ_VIEW_TREE)
4720 view->lineno = tree_lineno;
4722 return REQ_NONE;
4725 static bool
4726 tree_grep(struct view *view, struct line *line)
4728 struct tree_entry *entry = line->data;
4729 const char *text[] = {
4730 entry->name,
4731 opt_author ? entry->author : "",
4732 mkdate(&entry->time, opt_date),
4733 NULL
4736 return grep_text(view, text);
4739 static void
4740 tree_select(struct view *view, struct line *line)
4742 struct tree_entry *entry = line->data;
4744 if (line->type == LINE_TREE_FILE) {
4745 string_copy_rev(ref_blob, entry->id);
4746 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4748 } else if (line->type != LINE_TREE_DIR) {
4749 return;
4752 string_copy_rev(view->ref, entry->id);
4755 static bool
4756 tree_prepare(struct view *view)
4758 if (view->lines == 0 && opt_prefix[0]) {
4759 char *pos = opt_prefix;
4761 while (pos && *pos) {
4762 char *end = strchr(pos, '/');
4764 if (end)
4765 *end = 0;
4766 push_tree_stack_entry(pos, 0);
4767 pos = end;
4768 if (end) {
4769 *end = '/';
4770 pos++;
4774 } else if (strcmp(view->vid, view->id)) {
4775 opt_path[0] = 0;
4778 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4781 static const char *tree_argv[SIZEOF_ARG] = {
4782 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4785 static struct view_ops tree_ops = {
4786 "file",
4787 tree_argv,
4788 NULL,
4789 tree_read,
4790 tree_draw,
4791 tree_request,
4792 tree_grep,
4793 tree_select,
4794 tree_prepare,
4797 static bool
4798 blob_read(struct view *view, char *line)
4800 if (!line)
4801 return TRUE;
4802 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4805 static enum request
4806 blob_request(struct view *view, enum request request, struct line *line)
4808 switch (request) {
4809 case REQ_EDIT:
4810 open_blob_editor();
4811 return REQ_NONE;
4812 default:
4813 return pager_request(view, request, line);
4817 static const char *blob_argv[SIZEOF_ARG] = {
4818 "git", "cat-file", "blob", "%(blob)", NULL
4821 static struct view_ops blob_ops = {
4822 "line",
4823 blob_argv,
4824 NULL,
4825 blob_read,
4826 pager_draw,
4827 blob_request,
4828 pager_grep,
4829 pager_select,
4833 * Blame backend
4835 * Loading the blame view is a two phase job:
4837 * 1. File content is read either using opt_file from the
4838 * filesystem or using git-cat-file.
4839 * 2. Then blame information is incrementally added by
4840 * reading output from git-blame.
4843 static const char *blame_head_argv[] = {
4844 "git", "blame", "--incremental", "--", "%(file)", NULL
4847 static const char *blame_ref_argv[] = {
4848 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4851 static const char *blame_cat_file_argv[] = {
4852 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4855 struct blame_commit {
4856 char id[SIZEOF_REV]; /* SHA1 ID. */
4857 char title[128]; /* First line of the commit message. */
4858 const char *author; /* Author of the commit. */
4859 struct time time; /* Date from the author ident. */
4860 char filename[128]; /* Name of file. */
4861 bool has_previous; /* Was a "previous" line detected. */
4864 struct blame {
4865 struct blame_commit *commit;
4866 unsigned long lineno;
4867 char text[1];
4870 static bool
4871 blame_open(struct view *view)
4873 char path[SIZEOF_STR];
4875 if (!view->parent && *opt_prefix) {
4876 string_copy(path, opt_file);
4877 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4878 return FALSE;
4881 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4882 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4883 return FALSE;
4886 setup_update(view, opt_file);
4887 string_format(view->ref, "%s ...", opt_file);
4889 return TRUE;
4892 static struct blame_commit *
4893 get_blame_commit(struct view *view, const char *id)
4895 size_t i;
4897 for (i = 0; i < view->lines; i++) {
4898 struct blame *blame = view->line[i].data;
4900 if (!blame->commit)
4901 continue;
4903 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4904 return blame->commit;
4908 struct blame_commit *commit = calloc(1, sizeof(*commit));
4910 if (commit)
4911 string_ncopy(commit->id, id, SIZEOF_REV);
4912 return commit;
4916 static bool
4917 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4919 const char *pos = *posref;
4921 *posref = NULL;
4922 pos = strchr(pos + 1, ' ');
4923 if (!pos || !isdigit(pos[1]))
4924 return FALSE;
4925 *number = atoi(pos + 1);
4926 if (*number < min || *number > max)
4927 return FALSE;
4929 *posref = pos;
4930 return TRUE;
4933 static struct blame_commit *
4934 parse_blame_commit(struct view *view, const char *text, int *blamed)
4936 struct blame_commit *commit;
4937 struct blame *blame;
4938 const char *pos = text + SIZEOF_REV - 2;
4939 size_t orig_lineno = 0;
4940 size_t lineno;
4941 size_t group;
4943 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4944 return NULL;
4946 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4947 !parse_number(&pos, &lineno, 1, view->lines) ||
4948 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4949 return NULL;
4951 commit = get_blame_commit(view, text);
4952 if (!commit)
4953 return NULL;
4955 *blamed += group;
4956 while (group--) {
4957 struct line *line = &view->line[lineno + group - 1];
4959 blame = line->data;
4960 blame->commit = commit;
4961 blame->lineno = orig_lineno + group - 1;
4962 line->dirty = 1;
4965 return commit;
4968 static bool
4969 blame_read_file(struct view *view, const char *line, bool *read_file)
4971 if (!line) {
4972 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4973 struct io io = {};
4975 if (view->lines == 0 && !view->parent)
4976 die("No blame exist for %s", view->vid);
4978 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4979 report("Failed to load blame data");
4980 return TRUE;
4983 io_done(view->pipe);
4984 view->io = io;
4985 *read_file = FALSE;
4986 return FALSE;
4988 } else {
4989 size_t linelen = strlen(line);
4990 struct blame *blame = malloc(sizeof(*blame) + linelen);
4992 if (!blame)
4993 return FALSE;
4995 blame->commit = NULL;
4996 strncpy(blame->text, line, linelen);
4997 blame->text[linelen] = 0;
4998 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5002 static bool
5003 match_blame_header(const char *name, char **line)
5005 size_t namelen = strlen(name);
5006 bool matched = !strncmp(name, *line, namelen);
5008 if (matched)
5009 *line += namelen;
5011 return matched;
5014 static bool
5015 blame_read(struct view *view, char *line)
5017 static struct blame_commit *commit = NULL;
5018 static int blamed = 0;
5019 static bool read_file = TRUE;
5021 if (read_file)
5022 return blame_read_file(view, line, &read_file);
5024 if (!line) {
5025 /* Reset all! */
5026 commit = NULL;
5027 blamed = 0;
5028 read_file = TRUE;
5029 string_format(view->ref, "%s", view->vid);
5030 if (view_is_displayed(view)) {
5031 update_view_title(view);
5032 redraw_view_from(view, 0);
5034 return TRUE;
5037 if (!commit) {
5038 commit = parse_blame_commit(view, line, &blamed);
5039 string_format(view->ref, "%s %2d%%", view->vid,
5040 view->lines ? blamed * 100 / view->lines : 0);
5042 } else if (match_blame_header("author ", &line)) {
5043 commit->author = get_author(line);
5045 } else if (match_blame_header("author-time ", &line)) {
5046 parse_timesec(&commit->time, line);
5048 } else if (match_blame_header("author-tz ", &line)) {
5049 parse_timezone(&commit->time, line);
5051 } else if (match_blame_header("summary ", &line)) {
5052 string_ncopy(commit->title, line, strlen(line));
5054 } else if (match_blame_header("previous ", &line)) {
5055 commit->has_previous = TRUE;
5057 } else if (match_blame_header("filename ", &line)) {
5058 string_ncopy(commit->filename, line, strlen(line));
5059 commit = NULL;
5062 return TRUE;
5065 static bool
5066 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5068 struct blame *blame = line->data;
5069 struct time *time = NULL;
5070 const char *id = NULL, *author = NULL;
5071 char text[SIZEOF_STR];
5073 if (blame->commit && *blame->commit->filename) {
5074 id = blame->commit->id;
5075 author = blame->commit->author;
5076 time = &blame->commit->time;
5079 if (opt_date && draw_date(view, time))
5080 return TRUE;
5082 if (opt_author && draw_author(view, author))
5083 return TRUE;
5085 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5086 return TRUE;
5088 if (draw_lineno(view, lineno))
5089 return TRUE;
5091 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5092 draw_text(view, LINE_DEFAULT, text, TRUE);
5093 return TRUE;
5096 static bool
5097 check_blame_commit(struct blame *blame, bool check_null_id)
5099 if (!blame->commit)
5100 report("Commit data not loaded yet");
5101 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5102 report("No commit exist for the selected line");
5103 else
5104 return TRUE;
5105 return FALSE;
5108 static void
5109 setup_blame_parent_line(struct view *view, struct blame *blame)
5111 const char *diff_tree_argv[] = {
5112 "git", "diff-tree", "-U0", blame->commit->id,
5113 "--", blame->commit->filename, NULL
5115 struct io io = {};
5116 int parent_lineno = -1;
5117 int blamed_lineno = -1;
5118 char *line;
5120 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5121 return;
5123 while ((line = io_get(&io, '\n', TRUE))) {
5124 if (*line == '@') {
5125 char *pos = strchr(line, '+');
5127 parent_lineno = atoi(line + 4);
5128 if (pos)
5129 blamed_lineno = atoi(pos + 1);
5131 } else if (*line == '+' && parent_lineno != -1) {
5132 if (blame->lineno == blamed_lineno - 1 &&
5133 !strcmp(blame->text, line + 1)) {
5134 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5135 break;
5137 blamed_lineno++;
5141 io_done(&io);
5144 static enum request
5145 blame_request(struct view *view, enum request request, struct line *line)
5147 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5148 struct blame *blame = line->data;
5150 switch (request) {
5151 case REQ_VIEW_BLAME:
5152 if (check_blame_commit(blame, TRUE)) {
5153 string_copy(opt_ref, blame->commit->id);
5154 string_copy(opt_file, blame->commit->filename);
5155 if (blame->lineno)
5156 view->lineno = blame->lineno;
5157 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5159 break;
5161 case REQ_PARENT:
5162 if (check_blame_commit(blame, TRUE) &&
5163 select_commit_parent(blame->commit->id, opt_ref,
5164 blame->commit->filename)) {
5165 string_copy(opt_file, blame->commit->filename);
5166 setup_blame_parent_line(view, blame);
5167 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5169 break;
5171 case REQ_ENTER:
5172 if (!check_blame_commit(blame, FALSE))
5173 break;
5175 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5176 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5177 break;
5179 if (!strcmp(blame->commit->id, NULL_ID)) {
5180 struct view *diff = VIEW(REQ_VIEW_DIFF);
5181 const char *diff_index_argv[] = {
5182 "git", "diff-index", "--root", "--patch-with-stat",
5183 "-C", "-M", "HEAD", "--", view->vid, NULL
5186 if (!blame->commit->has_previous) {
5187 diff_index_argv[1] = "diff";
5188 diff_index_argv[2] = "--no-color";
5189 diff_index_argv[6] = "--";
5190 diff_index_argv[7] = "/dev/null";
5193 if (!prepare_update(diff, diff_index_argv, NULL)) {
5194 report("Failed to allocate diff command");
5195 break;
5197 flags |= OPEN_PREPARED;
5200 open_view(view, REQ_VIEW_DIFF, flags);
5201 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5202 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5203 break;
5205 default:
5206 return request;
5209 return REQ_NONE;
5212 static bool
5213 blame_grep(struct view *view, struct line *line)
5215 struct blame *blame = line->data;
5216 struct blame_commit *commit = blame->commit;
5217 const char *text[] = {
5218 blame->text,
5219 commit ? commit->title : "",
5220 commit ? commit->id : "",
5221 commit && opt_author ? commit->author : "",
5222 commit ? mkdate(&commit->time, opt_date) : "",
5223 NULL
5226 return grep_text(view, text);
5229 static void
5230 blame_select(struct view *view, struct line *line)
5232 struct blame *blame = line->data;
5233 struct blame_commit *commit = blame->commit;
5235 if (!commit)
5236 return;
5238 if (!strcmp(commit->id, NULL_ID))
5239 string_ncopy(ref_commit, "HEAD", 4);
5240 else
5241 string_copy_rev(ref_commit, commit->id);
5244 static struct view_ops blame_ops = {
5245 "line",
5246 NULL,
5247 blame_open,
5248 blame_read,
5249 blame_draw,
5250 blame_request,
5251 blame_grep,
5252 blame_select,
5256 * Branch backend
5259 struct branch {
5260 const char *author; /* Author of the last commit. */
5261 struct time time; /* Date of the last activity. */
5262 const struct ref *ref; /* Name and commit ID information. */
5265 static const struct ref branch_all;
5267 static const enum sort_field branch_sort_fields[] = {
5268 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5270 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5272 static int
5273 branch_compare(const void *l1, const void *l2)
5275 const struct branch *branch1 = ((const struct line *) l1)->data;
5276 const struct branch *branch2 = ((const struct line *) l2)->data;
5278 switch (get_sort_field(branch_sort_state)) {
5279 case ORDERBY_DATE:
5280 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5282 case ORDERBY_AUTHOR:
5283 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5285 case ORDERBY_NAME:
5286 default:
5287 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5291 static bool
5292 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5294 struct branch *branch = line->data;
5295 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5297 if (opt_date && draw_date(view, &branch->time))
5298 return TRUE;
5300 if (opt_author && draw_author(view, branch->author))
5301 return TRUE;
5303 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5304 return TRUE;
5307 static enum request
5308 branch_request(struct view *view, enum request request, struct line *line)
5310 struct branch *branch = line->data;
5312 switch (request) {
5313 case REQ_REFRESH:
5314 load_refs();
5315 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5316 return REQ_NONE;
5318 case REQ_TOGGLE_SORT_FIELD:
5319 case REQ_TOGGLE_SORT_ORDER:
5320 sort_view(view, request, &branch_sort_state, branch_compare);
5321 return REQ_NONE;
5323 case REQ_ENTER:
5324 if (branch->ref == &branch_all) {
5325 const char *all_branches_argv[] = {
5326 "git", "log", "--no-color", "--pretty=raw", "--parents",
5327 "--topo-order", "--all", NULL
5329 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5331 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5332 report("Failed to load view of all branches");
5333 return REQ_NONE;
5335 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5336 } else {
5337 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5339 return REQ_NONE;
5341 default:
5342 return request;
5346 static bool
5347 branch_read(struct view *view, char *line)
5349 static char id[SIZEOF_REV];
5350 struct branch *reference;
5351 size_t i;
5353 if (!line)
5354 return TRUE;
5356 switch (get_line_type(line)) {
5357 case LINE_COMMIT:
5358 string_copy_rev(id, line + STRING_SIZE("commit "));
5359 return TRUE;
5361 case LINE_AUTHOR:
5362 for (i = 0, reference = NULL; i < view->lines; i++) {
5363 struct branch *branch = view->line[i].data;
5365 if (strcmp(branch->ref->id, id))
5366 continue;
5368 view->line[i].dirty = TRUE;
5369 if (reference) {
5370 branch->author = reference->author;
5371 branch->time = reference->time;
5372 continue;
5375 parse_author_line(line + STRING_SIZE("author "),
5376 &branch->author, &branch->time);
5377 reference = branch;
5379 return TRUE;
5381 default:
5382 return TRUE;
5387 static bool
5388 branch_open_visitor(void *data, const struct ref *ref)
5390 struct view *view = data;
5391 struct branch *branch;
5393 if (ref->tag || ref->ltag || ref->remote)
5394 return TRUE;
5396 branch = calloc(1, sizeof(*branch));
5397 if (!branch)
5398 return FALSE;
5400 branch->ref = ref;
5401 return !!add_line_data(view, branch, LINE_DEFAULT);
5404 static bool
5405 branch_open(struct view *view)
5407 const char *branch_log[] = {
5408 "git", "log", "--no-color", "--pretty=raw",
5409 "--simplify-by-decoration", "--all", NULL
5412 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5413 report("Failed to load branch data");
5414 return TRUE;
5417 setup_update(view, view->id);
5418 branch_open_visitor(view, &branch_all);
5419 foreach_ref(branch_open_visitor, view);
5420 view->p_restore = TRUE;
5422 return TRUE;
5425 static bool
5426 branch_grep(struct view *view, struct line *line)
5428 struct branch *branch = line->data;
5429 const char *text[] = {
5430 branch->ref->name,
5431 branch->author,
5432 NULL
5435 return grep_text(view, text);
5438 static void
5439 branch_select(struct view *view, struct line *line)
5441 struct branch *branch = line->data;
5443 string_copy_rev(view->ref, branch->ref->id);
5444 string_copy_rev(ref_commit, branch->ref->id);
5445 string_copy_rev(ref_head, branch->ref->id);
5446 string_copy_rev(ref_branch, branch->ref->name);
5449 static struct view_ops branch_ops = {
5450 "branch",
5451 NULL,
5452 branch_open,
5453 branch_read,
5454 branch_draw,
5455 branch_request,
5456 branch_grep,
5457 branch_select,
5461 * Status backend
5464 struct status {
5465 char status;
5466 struct {
5467 mode_t mode;
5468 char rev[SIZEOF_REV];
5469 char name[SIZEOF_STR];
5470 } old;
5471 struct {
5472 mode_t mode;
5473 char rev[SIZEOF_REV];
5474 char name[SIZEOF_STR];
5475 } new;
5478 static char status_onbranch[SIZEOF_STR];
5479 static struct status stage_status;
5480 static enum line_type stage_line_type;
5481 static size_t stage_chunks;
5482 static int *stage_chunk;
5484 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5486 /* This should work even for the "On branch" line. */
5487 static inline bool
5488 status_has_none(struct view *view, struct line *line)
5490 return line < view->line + view->lines && !line[1].data;
5493 /* Get fields from the diff line:
5494 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5496 static inline bool
5497 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5499 const char *old_mode = buf + 1;
5500 const char *new_mode = buf + 8;
5501 const char *old_rev = buf + 15;
5502 const char *new_rev = buf + 56;
5503 const char *status = buf + 97;
5505 if (bufsize < 98 ||
5506 old_mode[-1] != ':' ||
5507 new_mode[-1] != ' ' ||
5508 old_rev[-1] != ' ' ||
5509 new_rev[-1] != ' ' ||
5510 status[-1] != ' ')
5511 return FALSE;
5513 file->status = *status;
5515 string_copy_rev(file->old.rev, old_rev);
5516 string_copy_rev(file->new.rev, new_rev);
5518 file->old.mode = strtoul(old_mode, NULL, 8);
5519 file->new.mode = strtoul(new_mode, NULL, 8);
5521 file->old.name[0] = file->new.name[0] = 0;
5523 return TRUE;
5526 static bool
5527 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5529 struct status *unmerged = NULL;
5530 char *buf;
5531 struct io io = {};
5533 if (!io_run(&io, argv, opt_cdup, IO_RD))
5534 return FALSE;
5536 add_line_data(view, NULL, type);
5538 while ((buf = io_get(&io, 0, TRUE))) {
5539 struct status *file = unmerged;
5541 if (!file) {
5542 file = calloc(1, sizeof(*file));
5543 if (!file || !add_line_data(view, file, type))
5544 goto error_out;
5547 /* Parse diff info part. */
5548 if (status) {
5549 file->status = status;
5550 if (status == 'A')
5551 string_copy(file->old.rev, NULL_ID);
5553 } else if (!file->status || file == unmerged) {
5554 if (!status_get_diff(file, buf, strlen(buf)))
5555 goto error_out;
5557 buf = io_get(&io, 0, TRUE);
5558 if (!buf)
5559 break;
5561 /* Collapse all modified entries that follow an
5562 * associated unmerged entry. */
5563 if (unmerged == file) {
5564 unmerged->status = 'U';
5565 unmerged = NULL;
5566 } else if (file->status == 'U') {
5567 unmerged = file;
5571 /* Grab the old name for rename/copy. */
5572 if (!*file->old.name &&
5573 (file->status == 'R' || file->status == 'C')) {
5574 string_ncopy(file->old.name, buf, strlen(buf));
5576 buf = io_get(&io, 0, TRUE);
5577 if (!buf)
5578 break;
5581 /* git-ls-files just delivers a NUL separated list of
5582 * file names similar to the second half of the
5583 * git-diff-* output. */
5584 string_ncopy(file->new.name, buf, strlen(buf));
5585 if (!*file->old.name)
5586 string_copy(file->old.name, file->new.name);
5587 file = NULL;
5590 if (io_error(&io)) {
5591 error_out:
5592 io_done(&io);
5593 return FALSE;
5596 if (!view->line[view->lines - 1].data)
5597 add_line_data(view, NULL, LINE_STAT_NONE);
5599 io_done(&io);
5600 return TRUE;
5603 /* Don't show unmerged entries in the staged section. */
5604 static const char *status_diff_index_argv[] = {
5605 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5606 "--cached", "-M", "HEAD", NULL
5609 static const char *status_diff_files_argv[] = {
5610 "git", "diff-files", "-z", NULL
5613 static const char *status_list_other_argv[] = {
5614 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5617 static const char *status_list_no_head_argv[] = {
5618 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5621 static const char *update_index_argv[] = {
5622 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5625 /* Restore the previous line number to stay in the context or select a
5626 * line with something that can be updated. */
5627 static void
5628 status_restore(struct view *view)
5630 if (view->p_lineno >= view->lines)
5631 view->p_lineno = view->lines - 1;
5632 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5633 view->p_lineno++;
5634 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5635 view->p_lineno--;
5637 /* If the above fails, always skip the "On branch" line. */
5638 if (view->p_lineno < view->lines)
5639 view->lineno = view->p_lineno;
5640 else
5641 view->lineno = 1;
5643 if (view->lineno < view->offset)
5644 view->offset = view->lineno;
5645 else if (view->offset + view->height <= view->lineno)
5646 view->offset = view->lineno - view->height + 1;
5648 view->p_restore = FALSE;
5651 static void
5652 status_update_onbranch(void)
5654 static const char *paths[][2] = {
5655 { "rebase-apply/rebasing", "Rebasing" },
5656 { "rebase-apply/applying", "Applying mailbox" },
5657 { "rebase-apply/", "Rebasing mailbox" },
5658 { "rebase-merge/interactive", "Interactive rebase" },
5659 { "rebase-merge/", "Rebase merge" },
5660 { "MERGE_HEAD", "Merging" },
5661 { "BISECT_LOG", "Bisecting" },
5662 { "HEAD", "On branch" },
5664 char buf[SIZEOF_STR];
5665 struct stat stat;
5666 int i;
5668 if (is_initial_commit()) {
5669 string_copy(status_onbranch, "Initial commit");
5670 return;
5673 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5674 char *head = opt_head;
5676 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5677 lstat(buf, &stat) < 0)
5678 continue;
5680 if (!*opt_head) {
5681 struct io io = {};
5683 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5684 io_read_buf(&io, buf, sizeof(buf))) {
5685 head = buf;
5686 if (!prefixcmp(head, "refs/heads/"))
5687 head += STRING_SIZE("refs/heads/");
5691 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5692 string_copy(status_onbranch, opt_head);
5693 return;
5696 string_copy(status_onbranch, "Not currently on any branch");
5699 /* First parse staged info using git-diff-index(1), then parse unstaged
5700 * info using git-diff-files(1), and finally untracked files using
5701 * git-ls-files(1). */
5702 static bool
5703 status_open(struct view *view)
5705 reset_view(view);
5707 add_line_data(view, NULL, LINE_STAT_HEAD);
5708 status_update_onbranch();
5710 io_run_bg(update_index_argv);
5712 if (is_initial_commit()) {
5713 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5714 return FALSE;
5715 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5716 return FALSE;
5719 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5720 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5721 return FALSE;
5723 /* Restore the exact position or use the specialized restore
5724 * mode? */
5725 if (!view->p_restore)
5726 status_restore(view);
5727 return TRUE;
5730 static bool
5731 status_draw(struct view *view, struct line *line, unsigned int lineno)
5733 struct status *status = line->data;
5734 enum line_type type;
5735 const char *text;
5737 if (!status) {
5738 switch (line->type) {
5739 case LINE_STAT_STAGED:
5740 type = LINE_STAT_SECTION;
5741 text = "Changes to be committed:";
5742 break;
5744 case LINE_STAT_UNSTAGED:
5745 type = LINE_STAT_SECTION;
5746 text = "Changed but not updated:";
5747 break;
5749 case LINE_STAT_UNTRACKED:
5750 type = LINE_STAT_SECTION;
5751 text = "Untracked files:";
5752 break;
5754 case LINE_STAT_NONE:
5755 type = LINE_DEFAULT;
5756 text = " (no files)";
5757 break;
5759 case LINE_STAT_HEAD:
5760 type = LINE_STAT_HEAD;
5761 text = status_onbranch;
5762 break;
5764 default:
5765 return FALSE;
5767 } else {
5768 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5770 buf[0] = status->status;
5771 if (draw_text(view, line->type, buf, TRUE))
5772 return TRUE;
5773 type = LINE_DEFAULT;
5774 text = status->new.name;
5777 draw_text(view, type, text, TRUE);
5778 return TRUE;
5781 static enum request
5782 status_load_error(struct view *view, struct view *stage, const char *path)
5784 if (displayed_views() == 2 || display[current_view] != view)
5785 maximize_view(view);
5786 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5787 return REQ_NONE;
5790 static enum request
5791 status_enter(struct view *view, struct line *line)
5793 struct status *status = line->data;
5794 const char *oldpath = status ? status->old.name : NULL;
5795 /* Diffs for unmerged entries are empty when passing the new
5796 * path, so leave it empty. */
5797 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5798 const char *info;
5799 enum open_flags split;
5800 struct view *stage = VIEW(REQ_VIEW_STAGE);
5802 if (line->type == LINE_STAT_NONE ||
5803 (!status && line[1].type == LINE_STAT_NONE)) {
5804 report("No file to diff");
5805 return REQ_NONE;
5808 switch (line->type) {
5809 case LINE_STAT_STAGED:
5810 if (is_initial_commit()) {
5811 const char *no_head_diff_argv[] = {
5812 "git", "diff", "--no-color", "--patch-with-stat",
5813 "--", "/dev/null", newpath, NULL
5816 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5817 return status_load_error(view, stage, newpath);
5818 } else {
5819 const char *index_show_argv[] = {
5820 "git", "diff-index", "--root", "--patch-with-stat",
5821 "-C", "-M", "--cached", "HEAD", "--",
5822 oldpath, newpath, NULL
5825 if (!prepare_update(stage, index_show_argv, opt_cdup))
5826 return status_load_error(view, stage, newpath);
5829 if (status)
5830 info = "Staged changes to %s";
5831 else
5832 info = "Staged changes";
5833 break;
5835 case LINE_STAT_UNSTAGED:
5837 const char *files_show_argv[] = {
5838 "git", "diff-files", "--root", "--patch-with-stat",
5839 "-C", "-M", "--", oldpath, newpath, NULL
5842 if (!prepare_update(stage, files_show_argv, opt_cdup))
5843 return status_load_error(view, stage, newpath);
5844 if (status)
5845 info = "Unstaged changes to %s";
5846 else
5847 info = "Unstaged changes";
5848 break;
5850 case LINE_STAT_UNTRACKED:
5851 if (!newpath) {
5852 report("No file to show");
5853 return REQ_NONE;
5856 if (!suffixcmp(status->new.name, -1, "/")) {
5857 report("Cannot display a directory");
5858 return REQ_NONE;
5861 if (!prepare_update_file(stage, newpath))
5862 return status_load_error(view, stage, newpath);
5863 info = "Untracked file %s";
5864 break;
5866 case LINE_STAT_HEAD:
5867 return REQ_NONE;
5869 default:
5870 die("line type %d not handled in switch", line->type);
5873 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5874 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5875 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5876 if (status) {
5877 stage_status = *status;
5878 } else {
5879 memset(&stage_status, 0, sizeof(stage_status));
5882 stage_line_type = line->type;
5883 stage_chunks = 0;
5884 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5887 return REQ_NONE;
5890 static bool
5891 status_exists(struct status *status, enum line_type type)
5893 struct view *view = VIEW(REQ_VIEW_STATUS);
5894 unsigned long lineno;
5896 for (lineno = 0; lineno < view->lines; lineno++) {
5897 struct line *line = &view->line[lineno];
5898 struct status *pos = line->data;
5900 if (line->type != type)
5901 continue;
5902 if (!pos && (!status || !status->status) && line[1].data) {
5903 select_view_line(view, lineno);
5904 return TRUE;
5906 if (pos && !strcmp(status->new.name, pos->new.name)) {
5907 select_view_line(view, lineno);
5908 return TRUE;
5912 return FALSE;
5916 static bool
5917 status_update_prepare(struct io *io, enum line_type type)
5919 const char *staged_argv[] = {
5920 "git", "update-index", "-z", "--index-info", NULL
5922 const char *others_argv[] = {
5923 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5926 switch (type) {
5927 case LINE_STAT_STAGED:
5928 return io_run(io, staged_argv, opt_cdup, IO_WR);
5930 case LINE_STAT_UNSTAGED:
5931 case LINE_STAT_UNTRACKED:
5932 return io_run(io, others_argv, opt_cdup, IO_WR);
5934 default:
5935 die("line type %d not handled in switch", type);
5936 return FALSE;
5940 static bool
5941 status_update_write(struct io *io, struct status *status, enum line_type type)
5943 char buf[SIZEOF_STR];
5944 size_t bufsize = 0;
5946 switch (type) {
5947 case LINE_STAT_STAGED:
5948 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5949 status->old.mode,
5950 status->old.rev,
5951 status->old.name, 0))
5952 return FALSE;
5953 break;
5955 case LINE_STAT_UNSTAGED:
5956 case LINE_STAT_UNTRACKED:
5957 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5958 return FALSE;
5959 break;
5961 default:
5962 die("line type %d not handled in switch", type);
5965 return io_write(io, buf, bufsize);
5968 static bool
5969 status_update_file(struct status *status, enum line_type type)
5971 struct io io = {};
5972 bool result;
5974 if (!status_update_prepare(&io, type))
5975 return FALSE;
5977 result = status_update_write(&io, status, type);
5978 return io_done(&io) && result;
5981 static bool
5982 status_update_files(struct view *view, struct line *line)
5984 char buf[sizeof(view->ref)];
5985 struct io io = {};
5986 bool result = TRUE;
5987 struct line *pos = view->line + view->lines;
5988 int files = 0;
5989 int file, done;
5990 int cursor_y = -1, cursor_x = -1;
5992 if (!status_update_prepare(&io, line->type))
5993 return FALSE;
5995 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5996 files++;
5998 string_copy(buf, view->ref);
5999 getsyx(cursor_y, cursor_x);
6000 for (file = 0, done = 5; result && file < files; line++, file++) {
6001 int almost_done = file * 100 / files;
6003 if (almost_done > done) {
6004 done = almost_done;
6005 string_format(view->ref, "updating file %u of %u (%d%% done)",
6006 file, files, done);
6007 update_view_title(view);
6008 setsyx(cursor_y, cursor_x);
6009 doupdate();
6011 result = status_update_write(&io, line->data, line->type);
6013 string_copy(view->ref, buf);
6015 return io_done(&io) && result;
6018 static bool
6019 status_update(struct view *view)
6021 struct line *line = &view->line[view->lineno];
6023 assert(view->lines);
6025 if (!line->data) {
6026 /* This should work even for the "On branch" line. */
6027 if (line < view->line + view->lines && !line[1].data) {
6028 report("Nothing to update");
6029 return FALSE;
6032 if (!status_update_files(view, line + 1)) {
6033 report("Failed to update file status");
6034 return FALSE;
6037 } else if (!status_update_file(line->data, line->type)) {
6038 report("Failed to update file status");
6039 return FALSE;
6042 return TRUE;
6045 static bool
6046 status_revert(struct status *status, enum line_type type, bool has_none)
6048 if (!status || type != LINE_STAT_UNSTAGED) {
6049 if (type == LINE_STAT_STAGED) {
6050 report("Cannot revert changes to staged files");
6051 } else if (type == LINE_STAT_UNTRACKED) {
6052 report("Cannot revert changes to untracked files");
6053 } else if (has_none) {
6054 report("Nothing to revert");
6055 } else {
6056 report("Cannot revert changes to multiple files");
6059 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6060 char mode[10] = "100644";
6061 const char *reset_argv[] = {
6062 "git", "update-index", "--cacheinfo", mode,
6063 status->old.rev, status->old.name, NULL
6065 const char *checkout_argv[] = {
6066 "git", "checkout", "--", status->old.name, NULL
6069 if (status->status == 'U') {
6070 string_format(mode, "%5o", status->old.mode);
6072 if (status->old.mode == 0 && status->new.mode == 0) {
6073 reset_argv[2] = "--force-remove";
6074 reset_argv[3] = status->old.name;
6075 reset_argv[4] = NULL;
6078 if (!io_run_fg(reset_argv, opt_cdup))
6079 return FALSE;
6080 if (status->old.mode == 0 && status->new.mode == 0)
6081 return TRUE;
6084 return io_run_fg(checkout_argv, opt_cdup);
6087 return FALSE;
6090 static enum request
6091 status_request(struct view *view, enum request request, struct line *line)
6093 struct status *status = line->data;
6095 switch (request) {
6096 case REQ_STATUS_UPDATE:
6097 if (!status_update(view))
6098 return REQ_NONE;
6099 break;
6101 case REQ_STATUS_REVERT:
6102 if (!status_revert(status, line->type, status_has_none(view, line)))
6103 return REQ_NONE;
6104 break;
6106 case REQ_STATUS_MERGE:
6107 if (!status || status->status != 'U') {
6108 report("Merging only possible for files with unmerged status ('U').");
6109 return REQ_NONE;
6111 open_mergetool(status->new.name);
6112 break;
6114 case REQ_EDIT:
6115 if (!status)
6116 return request;
6117 if (status->status == 'D') {
6118 report("File has been deleted.");
6119 return REQ_NONE;
6122 open_editor(status->new.name);
6123 break;
6125 case REQ_VIEW_BLAME:
6126 if (status)
6127 opt_ref[0] = 0;
6128 return request;
6130 case REQ_ENTER:
6131 /* After returning the status view has been split to
6132 * show the stage view. No further reloading is
6133 * necessary. */
6134 return status_enter(view, line);
6136 case REQ_REFRESH:
6137 /* Simply reload the view. */
6138 break;
6140 default:
6141 return request;
6144 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6146 return REQ_NONE;
6149 static void
6150 status_select(struct view *view, struct line *line)
6152 struct status *status = line->data;
6153 char file[SIZEOF_STR] = "all files";
6154 const char *text;
6155 const char *key;
6157 if (status && !string_format(file, "'%s'", status->new.name))
6158 return;
6160 if (!status && line[1].type == LINE_STAT_NONE)
6161 line++;
6163 switch (line->type) {
6164 case LINE_STAT_STAGED:
6165 text = "Press %s to unstage %s for commit";
6166 break;
6168 case LINE_STAT_UNSTAGED:
6169 text = "Press %s to stage %s for commit";
6170 break;
6172 case LINE_STAT_UNTRACKED:
6173 text = "Press %s to stage %s for addition";
6174 break;
6176 case LINE_STAT_HEAD:
6177 case LINE_STAT_NONE:
6178 text = "Nothing to update";
6179 break;
6181 default:
6182 die("line type %d not handled in switch", line->type);
6185 if (status && status->status == 'U') {
6186 text = "Press %s to resolve conflict in %s";
6187 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6189 } else {
6190 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6193 string_format(view->ref, text, key, file);
6194 if (status)
6195 string_copy(opt_file, status->new.name);
6198 static bool
6199 status_grep(struct view *view, struct line *line)
6201 struct status *status = line->data;
6203 if (status) {
6204 const char buf[2] = { status->status, 0 };
6205 const char *text[] = { status->new.name, buf, NULL };
6207 return grep_text(view, text);
6210 return FALSE;
6213 static struct view_ops status_ops = {
6214 "file",
6215 NULL,
6216 status_open,
6217 NULL,
6218 status_draw,
6219 status_request,
6220 status_grep,
6221 status_select,
6225 static bool
6226 stage_diff_write(struct io *io, struct line *line, struct line *end)
6228 while (line < end) {
6229 if (!io_write(io, line->data, strlen(line->data)) ||
6230 !io_write(io, "\n", 1))
6231 return FALSE;
6232 line++;
6233 if (line->type == LINE_DIFF_CHUNK ||
6234 line->type == LINE_DIFF_HEADER)
6235 break;
6238 return TRUE;
6241 static struct line *
6242 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6244 for (; view->line < line; line--)
6245 if (line->type == type)
6246 return line;
6248 return NULL;
6251 static bool
6252 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6254 const char *apply_argv[SIZEOF_ARG] = {
6255 "git", "apply", "--whitespace=nowarn", NULL
6257 struct line *diff_hdr;
6258 struct io io = {};
6259 int argc = 3;
6261 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6262 if (!diff_hdr)
6263 return FALSE;
6265 if (!revert)
6266 apply_argv[argc++] = "--cached";
6267 if (revert || stage_line_type == LINE_STAT_STAGED)
6268 apply_argv[argc++] = "-R";
6269 apply_argv[argc++] = "-";
6270 apply_argv[argc++] = NULL;
6271 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6272 return FALSE;
6274 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6275 !stage_diff_write(&io, chunk, view->line + view->lines))
6276 chunk = NULL;
6278 io_done(&io);
6279 io_run_bg(update_index_argv);
6281 return chunk ? TRUE : FALSE;
6284 static bool
6285 stage_update(struct view *view, struct line *line)
6287 struct line *chunk = NULL;
6289 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6290 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6292 if (chunk) {
6293 if (!stage_apply_chunk(view, chunk, FALSE)) {
6294 report("Failed to apply chunk");
6295 return FALSE;
6298 } else if (!stage_status.status) {
6299 view = VIEW(REQ_VIEW_STATUS);
6301 for (line = view->line; line < view->line + view->lines; line++)
6302 if (line->type == stage_line_type)
6303 break;
6305 if (!status_update_files(view, line + 1)) {
6306 report("Failed to update files");
6307 return FALSE;
6310 } else if (!status_update_file(&stage_status, stage_line_type)) {
6311 report("Failed to update file");
6312 return FALSE;
6315 return TRUE;
6318 static bool
6319 stage_revert(struct view *view, struct line *line)
6321 struct line *chunk = NULL;
6323 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6324 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6326 if (chunk) {
6327 if (!prompt_yesno("Are you sure you want to revert changes?"))
6328 return FALSE;
6330 if (!stage_apply_chunk(view, chunk, TRUE)) {
6331 report("Failed to revert chunk");
6332 return FALSE;
6334 return TRUE;
6336 } else {
6337 return status_revert(stage_status.status ? &stage_status : NULL,
6338 stage_line_type, FALSE);
6343 static void
6344 stage_next(struct view *view, struct line *line)
6346 int i;
6348 if (!stage_chunks) {
6349 for (line = view->line; line < view->line + view->lines; line++) {
6350 if (line->type != LINE_DIFF_CHUNK)
6351 continue;
6353 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6354 report("Allocation failure");
6355 return;
6358 stage_chunk[stage_chunks++] = line - view->line;
6362 for (i = 0; i < stage_chunks; i++) {
6363 if (stage_chunk[i] > view->lineno) {
6364 do_scroll_view(view, stage_chunk[i] - view->lineno);
6365 report("Chunk %d of %d", i + 1, stage_chunks);
6366 return;
6370 report("No next chunk found");
6373 static enum request
6374 stage_request(struct view *view, enum request request, struct line *line)
6376 switch (request) {
6377 case REQ_STATUS_UPDATE:
6378 if (!stage_update(view, line))
6379 return REQ_NONE;
6380 break;
6382 case REQ_STATUS_REVERT:
6383 if (!stage_revert(view, line))
6384 return REQ_NONE;
6385 break;
6387 case REQ_STAGE_NEXT:
6388 if (stage_line_type == LINE_STAT_UNTRACKED) {
6389 report("File is untracked; press %s to add",
6390 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6391 return REQ_NONE;
6393 stage_next(view, line);
6394 return REQ_NONE;
6396 case REQ_EDIT:
6397 if (!stage_status.new.name[0])
6398 return request;
6399 if (stage_status.status == 'D') {
6400 report("File has been deleted.");
6401 return REQ_NONE;
6404 open_editor(stage_status.new.name);
6405 break;
6407 case REQ_REFRESH:
6408 /* Reload everything ... */
6409 break;
6411 case REQ_VIEW_BLAME:
6412 if (stage_status.new.name[0]) {
6413 string_copy(opt_file, stage_status.new.name);
6414 opt_ref[0] = 0;
6416 return request;
6418 case REQ_ENTER:
6419 return pager_request(view, request, line);
6421 default:
6422 return request;
6425 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6426 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6428 /* Check whether the staged entry still exists, and close the
6429 * stage view if it doesn't. */
6430 if (!status_exists(&stage_status, stage_line_type)) {
6431 status_restore(VIEW(REQ_VIEW_STATUS));
6432 return REQ_VIEW_CLOSE;
6435 if (stage_line_type == LINE_STAT_UNTRACKED) {
6436 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6437 report("Cannot display a directory");
6438 return REQ_NONE;
6441 if (!prepare_update_file(view, stage_status.new.name)) {
6442 report("Failed to open file: %s", strerror(errno));
6443 return REQ_NONE;
6446 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6448 return REQ_NONE;
6451 static struct view_ops stage_ops = {
6452 "line",
6453 NULL,
6454 NULL,
6455 pager_read,
6456 pager_draw,
6457 stage_request,
6458 pager_grep,
6459 pager_select,
6464 * Revision graph
6467 struct commit {
6468 char id[SIZEOF_REV]; /* SHA1 ID. */
6469 char title[128]; /* First line of the commit message. */
6470 const char *author; /* Author of the commit. */
6471 struct time time; /* Date from the author ident. */
6472 struct ref_list *refs; /* Repository references. */
6473 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6474 size_t graph_size; /* The width of the graph array. */
6475 bool has_parents; /* Rewritten --parents seen. */
6478 /* Size of rev graph with no "padding" columns */
6479 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6481 struct rev_graph {
6482 struct rev_graph *prev, *next, *parents;
6483 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6484 size_t size;
6485 struct commit *commit;
6486 size_t pos;
6487 unsigned int boundary:1;
6490 /* Parents of the commit being visualized. */
6491 static struct rev_graph graph_parents[4];
6493 /* The current stack of revisions on the graph. */
6494 static struct rev_graph graph_stacks[4] = {
6495 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6496 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6497 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6498 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6501 static inline bool
6502 graph_parent_is_merge(struct rev_graph *graph)
6504 return graph->parents->size > 1;
6507 static inline void
6508 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6510 struct commit *commit = graph->commit;
6512 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6513 commit->graph[commit->graph_size++] = symbol;
6516 static void
6517 clear_rev_graph(struct rev_graph *graph)
6519 graph->boundary = 0;
6520 graph->size = graph->pos = 0;
6521 graph->commit = NULL;
6522 memset(graph->parents, 0, sizeof(*graph->parents));
6525 static void
6526 done_rev_graph(struct rev_graph *graph)
6528 if (graph_parent_is_merge(graph) &&
6529 graph->pos < graph->size - 1 &&
6530 graph->next->size == graph->size + graph->parents->size - 1) {
6531 size_t i = graph->pos + graph->parents->size - 1;
6533 graph->commit->graph_size = i * 2;
6534 while (i < graph->next->size - 1) {
6535 append_to_rev_graph(graph, ' ');
6536 append_to_rev_graph(graph, '\\');
6537 i++;
6541 clear_rev_graph(graph);
6544 static void
6545 push_rev_graph(struct rev_graph *graph, const char *parent)
6547 int i;
6549 /* "Collapse" duplicate parents lines.
6551 * FIXME: This needs to also update update the drawn graph but
6552 * for now it just serves as a method for pruning graph lines. */
6553 for (i = 0; i < graph->size; i++)
6554 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6555 return;
6557 if (graph->size < SIZEOF_REVITEMS) {
6558 string_copy_rev(graph->rev[graph->size++], parent);
6562 static chtype
6563 get_rev_graph_symbol(struct rev_graph *graph)
6565 chtype symbol;
6567 if (graph->boundary)
6568 symbol = REVGRAPH_BOUND;
6569 else if (graph->parents->size == 0)
6570 symbol = REVGRAPH_INIT;
6571 else if (graph_parent_is_merge(graph))
6572 symbol = REVGRAPH_MERGE;
6573 else if (graph->pos >= graph->size)
6574 symbol = REVGRAPH_BRANCH;
6575 else
6576 symbol = REVGRAPH_COMMIT;
6578 return symbol;
6581 static void
6582 draw_rev_graph(struct rev_graph *graph)
6584 struct rev_filler {
6585 chtype separator, line;
6587 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6588 static struct rev_filler fillers[] = {
6589 { ' ', '|' },
6590 { '`', '.' },
6591 { '\'', ' ' },
6592 { '/', ' ' },
6594 chtype symbol = get_rev_graph_symbol(graph);
6595 struct rev_filler *filler;
6596 size_t i;
6598 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6599 filler = &fillers[DEFAULT];
6601 for (i = 0; i < graph->pos; i++) {
6602 append_to_rev_graph(graph, filler->line);
6603 if (graph_parent_is_merge(graph->prev) &&
6604 graph->prev->pos == i)
6605 filler = &fillers[RSHARP];
6607 append_to_rev_graph(graph, filler->separator);
6610 /* Place the symbol for this revision. */
6611 append_to_rev_graph(graph, symbol);
6613 if (graph->prev->size > graph->size)
6614 filler = &fillers[RDIAG];
6615 else
6616 filler = &fillers[DEFAULT];
6618 i++;
6620 for (; i < graph->size; i++) {
6621 append_to_rev_graph(graph, filler->separator);
6622 append_to_rev_graph(graph, filler->line);
6623 if (graph_parent_is_merge(graph->prev) &&
6624 i < graph->prev->pos + graph->parents->size)
6625 filler = &fillers[RSHARP];
6626 if (graph->prev->size > graph->size)
6627 filler = &fillers[LDIAG];
6630 if (graph->prev->size > graph->size) {
6631 append_to_rev_graph(graph, filler->separator);
6632 if (filler->line != ' ')
6633 append_to_rev_graph(graph, filler->line);
6637 /* Prepare the next rev graph */
6638 static void
6639 prepare_rev_graph(struct rev_graph *graph)
6641 size_t i;
6643 /* First, traverse all lines of revisions up to the active one. */
6644 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6645 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6646 break;
6648 push_rev_graph(graph->next, graph->rev[graph->pos]);
6651 /* Interleave the new revision parent(s). */
6652 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6653 push_rev_graph(graph->next, graph->parents->rev[i]);
6655 /* Lastly, put any remaining revisions. */
6656 for (i = graph->pos + 1; i < graph->size; i++)
6657 push_rev_graph(graph->next, graph->rev[i]);
6660 static void
6661 update_rev_graph(struct view *view, struct rev_graph *graph)
6663 /* If this is the finalizing update ... */
6664 if (graph->commit)
6665 prepare_rev_graph(graph);
6667 /* Graph visualization needs a one rev look-ahead,
6668 * so the first update doesn't visualize anything. */
6669 if (!graph->prev->commit)
6670 return;
6672 if (view->lines > 2)
6673 view->line[view->lines - 3].dirty = 1;
6674 if (view->lines > 1)
6675 view->line[view->lines - 2].dirty = 1;
6676 draw_rev_graph(graph->prev);
6677 done_rev_graph(graph->prev->prev);
6682 * Main view backend
6685 static const char *main_argv[SIZEOF_ARG] = {
6686 "git", "log", "--no-color", "--pretty=raw", "--parents",
6687 "--topo-order", "%(head)", NULL
6690 static bool
6691 main_draw(struct view *view, struct line *line, unsigned int lineno)
6693 struct commit *commit = line->data;
6695 if (!commit->author)
6696 return FALSE;
6698 if (opt_date && draw_date(view, &commit->time))
6699 return TRUE;
6701 if (opt_author && draw_author(view, commit->author))
6702 return TRUE;
6704 if (opt_rev_graph && commit->graph_size &&
6705 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6706 return TRUE;
6708 if (opt_show_refs && commit->refs) {
6709 size_t i;
6711 for (i = 0; i < commit->refs->size; i++) {
6712 struct ref *ref = commit->refs->refs[i];
6713 enum line_type type;
6715 if (ref->head)
6716 type = LINE_MAIN_HEAD;
6717 else if (ref->ltag)
6718 type = LINE_MAIN_LOCAL_TAG;
6719 else if (ref->tag)
6720 type = LINE_MAIN_TAG;
6721 else if (ref->tracked)
6722 type = LINE_MAIN_TRACKED;
6723 else if (ref->remote)
6724 type = LINE_MAIN_REMOTE;
6725 else
6726 type = LINE_MAIN_REF;
6728 if (draw_text(view, type, "[", TRUE) ||
6729 draw_text(view, type, ref->name, TRUE) ||
6730 draw_text(view, type, "]", TRUE))
6731 return TRUE;
6733 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6734 return TRUE;
6738 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6739 return TRUE;
6742 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6743 static bool
6744 main_read(struct view *view, char *line)
6746 static struct rev_graph *graph = graph_stacks;
6747 enum line_type type;
6748 struct commit *commit;
6750 if (!line) {
6751 int i;
6753 if (!view->lines && !view->parent)
6754 die("No revisions match the given arguments.");
6755 if (view->lines > 0) {
6756 commit = view->line[view->lines - 1].data;
6757 view->line[view->lines - 1].dirty = 1;
6758 if (!commit->author) {
6759 view->lines--;
6760 free(commit);
6761 graph->commit = NULL;
6764 update_rev_graph(view, graph);
6766 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6767 clear_rev_graph(&graph_stacks[i]);
6768 return TRUE;
6771 type = get_line_type(line);
6772 if (type == LINE_COMMIT) {
6773 commit = calloc(1, sizeof(struct commit));
6774 if (!commit)
6775 return FALSE;
6777 line += STRING_SIZE("commit ");
6778 if (*line == '-') {
6779 graph->boundary = 1;
6780 line++;
6783 string_copy_rev(commit->id, line);
6784 commit->refs = get_ref_list(commit->id);
6785 graph->commit = commit;
6786 add_line_data(view, commit, LINE_MAIN_COMMIT);
6788 while ((line = strchr(line, ' '))) {
6789 line++;
6790 push_rev_graph(graph->parents, line);
6791 commit->has_parents = TRUE;
6793 return TRUE;
6796 if (!view->lines)
6797 return TRUE;
6798 commit = view->line[view->lines - 1].data;
6800 switch (type) {
6801 case LINE_PARENT:
6802 if (commit->has_parents)
6803 break;
6804 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6805 break;
6807 case LINE_AUTHOR:
6808 parse_author_line(line + STRING_SIZE("author "),
6809 &commit->author, &commit->time);
6810 update_rev_graph(view, graph);
6811 graph = graph->next;
6812 break;
6814 default:
6815 /* Fill in the commit title if it has not already been set. */
6816 if (commit->title[0])
6817 break;
6819 /* Require titles to start with a non-space character at the
6820 * offset used by git log. */
6821 if (strncmp(line, " ", 4))
6822 break;
6823 line += 4;
6824 /* Well, if the title starts with a whitespace character,
6825 * try to be forgiving. Otherwise we end up with no title. */
6826 while (isspace(*line))
6827 line++;
6828 if (*line == '\0')
6829 break;
6830 /* FIXME: More graceful handling of titles; append "..." to
6831 * shortened titles, etc. */
6833 string_expand(commit->title, sizeof(commit->title), line, 1);
6834 view->line[view->lines - 1].dirty = 1;
6837 return TRUE;
6840 static enum request
6841 main_request(struct view *view, enum request request, struct line *line)
6843 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6845 switch (request) {
6846 case REQ_ENTER:
6847 open_view(view, REQ_VIEW_DIFF, flags);
6848 break;
6849 case REQ_REFRESH:
6850 load_refs();
6851 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6852 break;
6853 default:
6854 return request;
6857 return REQ_NONE;
6860 static bool
6861 grep_refs(struct ref_list *list, regex_t *regex)
6863 regmatch_t pmatch;
6864 size_t i;
6866 if (!opt_show_refs || !list)
6867 return FALSE;
6869 for (i = 0; i < list->size; i++) {
6870 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6871 return TRUE;
6874 return FALSE;
6877 static bool
6878 main_grep(struct view *view, struct line *line)
6880 struct commit *commit = line->data;
6881 const char *text[] = {
6882 commit->title,
6883 opt_author ? commit->author : "",
6884 mkdate(&commit->time, opt_date),
6885 NULL
6888 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6891 static void
6892 main_select(struct view *view, struct line *line)
6894 struct commit *commit = line->data;
6896 string_copy_rev(view->ref, commit->id);
6897 string_copy_rev(ref_commit, view->ref);
6900 static struct view_ops main_ops = {
6901 "commit",
6902 main_argv,
6903 NULL,
6904 main_read,
6905 main_draw,
6906 main_request,
6907 main_grep,
6908 main_select,
6913 * Status management
6916 /* Whether or not the curses interface has been initialized. */
6917 static bool cursed = FALSE;
6919 /* Terminal hacks and workarounds. */
6920 static bool use_scroll_redrawwin;
6921 static bool use_scroll_status_wclear;
6923 /* The status window is used for polling keystrokes. */
6924 static WINDOW *status_win;
6926 /* Reading from the prompt? */
6927 static bool input_mode = FALSE;
6929 static bool status_empty = FALSE;
6931 /* Update status and title window. */
6932 static void
6933 report(const char *msg, ...)
6935 struct view *view = display[current_view];
6937 if (input_mode)
6938 return;
6940 if (!view) {
6941 char buf[SIZEOF_STR];
6942 va_list args;
6944 va_start(args, msg);
6945 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6946 buf[sizeof(buf) - 1] = 0;
6947 buf[sizeof(buf) - 2] = '.';
6948 buf[sizeof(buf) - 3] = '.';
6949 buf[sizeof(buf) - 4] = '.';
6951 va_end(args);
6952 die("%s", buf);
6955 if (!status_empty || *msg) {
6956 va_list args;
6958 va_start(args, msg);
6960 wmove(status_win, 0, 0);
6961 if (view->has_scrolled && use_scroll_status_wclear)
6962 wclear(status_win);
6963 if (*msg) {
6964 vwprintw(status_win, msg, args);
6965 status_empty = FALSE;
6966 } else {
6967 status_empty = TRUE;
6969 wclrtoeol(status_win);
6970 wnoutrefresh(status_win);
6972 va_end(args);
6975 update_view_title(view);
6978 static void
6979 init_display(void)
6981 const char *term;
6982 int x, y;
6984 /* Initialize the curses library */
6985 if (isatty(STDIN_FILENO)) {
6986 cursed = !!initscr();
6987 opt_tty = stdin;
6988 } else {
6989 /* Leave stdin and stdout alone when acting as a pager. */
6990 opt_tty = fopen("/dev/tty", "r+");
6991 if (!opt_tty)
6992 die("Failed to open /dev/tty");
6993 cursed = !!newterm(NULL, opt_tty, opt_tty);
6996 if (!cursed)
6997 die("Failed to initialize curses");
6999 nonl(); /* Disable conversion and detect newlines from input. */
7000 cbreak(); /* Take input chars one at a time, no wait for \n */
7001 noecho(); /* Don't echo input */
7002 leaveok(stdscr, FALSE);
7004 if (has_colors())
7005 init_colors();
7007 getmaxyx(stdscr, y, x);
7008 status_win = newwin(1, 0, y - 1, 0);
7009 if (!status_win)
7010 die("Failed to create status window");
7012 /* Enable keyboard mapping */
7013 keypad(status_win, TRUE);
7014 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7016 TABSIZE = opt_tab_size;
7018 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7019 if (term && !strcmp(term, "gnome-terminal")) {
7020 /* In the gnome-terminal-emulator, the message from
7021 * scrolling up one line when impossible followed by
7022 * scrolling down one line causes corruption of the
7023 * status line. This is fixed by calling wclear. */
7024 use_scroll_status_wclear = TRUE;
7025 use_scroll_redrawwin = FALSE;
7027 } else if (term && !strcmp(term, "xrvt-xpm")) {
7028 /* No problems with full optimizations in xrvt-(unicode)
7029 * and aterm. */
7030 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7032 } else {
7033 /* When scrolling in (u)xterm the last line in the
7034 * scrolling direction will update slowly. */
7035 use_scroll_redrawwin = TRUE;
7036 use_scroll_status_wclear = FALSE;
7040 static int
7041 get_input(int prompt_position)
7043 struct view *view;
7044 int i, key, cursor_y, cursor_x;
7045 bool loading = FALSE;
7047 if (prompt_position)
7048 input_mode = TRUE;
7050 while (TRUE) {
7051 foreach_view (view, i) {
7052 update_view(view);
7053 if (view_is_displayed(view) && view->has_scrolled &&
7054 use_scroll_redrawwin)
7055 redrawwin(view->win);
7056 view->has_scrolled = FALSE;
7057 if (view->pipe)
7058 loading = TRUE;
7061 /* Update the cursor position. */
7062 if (prompt_position) {
7063 getbegyx(status_win, cursor_y, cursor_x);
7064 cursor_x = prompt_position;
7065 } else {
7066 view = display[current_view];
7067 getbegyx(view->win, cursor_y, cursor_x);
7068 cursor_x = view->width - 1;
7069 cursor_y += view->lineno - view->offset;
7071 setsyx(cursor_y, cursor_x);
7073 /* Refresh, accept single keystroke of input */
7074 doupdate();
7075 nodelay(status_win, loading);
7076 key = wgetch(status_win);
7078 /* wgetch() with nodelay() enabled returns ERR when
7079 * there's no input. */
7080 if (key == ERR) {
7082 } else if (key == KEY_RESIZE) {
7083 int height, width;
7085 getmaxyx(stdscr, height, width);
7087 wresize(status_win, 1, width);
7088 mvwin(status_win, height - 1, 0);
7089 wnoutrefresh(status_win);
7090 resize_display();
7091 redraw_display(TRUE);
7093 } else {
7094 input_mode = FALSE;
7095 return key;
7100 static char *
7101 prompt_input(const char *prompt, input_handler handler, void *data)
7103 enum input_status status = INPUT_OK;
7104 static char buf[SIZEOF_STR];
7105 size_t pos = 0;
7107 buf[pos] = 0;
7109 while (status == INPUT_OK || status == INPUT_SKIP) {
7110 int key;
7112 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7113 wclrtoeol(status_win);
7115 key = get_input(pos + 1);
7116 switch (key) {
7117 case KEY_RETURN:
7118 case KEY_ENTER:
7119 case '\n':
7120 status = pos ? INPUT_STOP : INPUT_CANCEL;
7121 break;
7123 case KEY_BACKSPACE:
7124 if (pos > 0)
7125 buf[--pos] = 0;
7126 else
7127 status = INPUT_CANCEL;
7128 break;
7130 case KEY_ESC:
7131 status = INPUT_CANCEL;
7132 break;
7134 default:
7135 if (pos >= sizeof(buf)) {
7136 report("Input string too long");
7137 return NULL;
7140 status = handler(data, buf, key);
7141 if (status == INPUT_OK)
7142 buf[pos++] = (char) key;
7146 /* Clear the status window */
7147 status_empty = FALSE;
7148 report("");
7150 if (status == INPUT_CANCEL)
7151 return NULL;
7153 buf[pos++] = 0;
7155 return buf;
7158 static enum input_status
7159 prompt_yesno_handler(void *data, char *buf, int c)
7161 if (c == 'y' || c == 'Y')
7162 return INPUT_STOP;
7163 if (c == 'n' || c == 'N')
7164 return INPUT_CANCEL;
7165 return INPUT_SKIP;
7168 static bool
7169 prompt_yesno(const char *prompt)
7171 char prompt2[SIZEOF_STR];
7173 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7174 return FALSE;
7176 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7179 static enum input_status
7180 read_prompt_handler(void *data, char *buf, int c)
7182 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7185 static char *
7186 read_prompt(const char *prompt)
7188 return prompt_input(prompt, read_prompt_handler, NULL);
7191 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7193 enum input_status status = INPUT_OK;
7194 int size = 0;
7196 while (items[size].text)
7197 size++;
7199 while (status == INPUT_OK) {
7200 const struct menu_item *item = &items[*selected];
7201 int key;
7202 int i;
7204 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7205 prompt, *selected + 1, size);
7206 if (item->hotkey)
7207 wprintw(status_win, "[%c] ", (char) item->hotkey);
7208 wprintw(status_win, "%s", item->text);
7209 wclrtoeol(status_win);
7211 key = get_input(COLS - 1);
7212 switch (key) {
7213 case KEY_RETURN:
7214 case KEY_ENTER:
7215 case '\n':
7216 status = INPUT_STOP;
7217 break;
7219 case KEY_LEFT:
7220 case KEY_UP:
7221 *selected = *selected - 1;
7222 if (*selected < 0)
7223 *selected = size - 1;
7224 break;
7226 case KEY_RIGHT:
7227 case KEY_DOWN:
7228 *selected = (*selected + 1) % size;
7229 break;
7231 case KEY_ESC:
7232 status = INPUT_CANCEL;
7233 break;
7235 default:
7236 for (i = 0; items[i].text; i++)
7237 if (items[i].hotkey == key) {
7238 *selected = i;
7239 status = INPUT_STOP;
7240 break;
7245 /* Clear the status window */
7246 status_empty = FALSE;
7247 report("");
7249 return status != INPUT_CANCEL;
7253 * Repository properties
7256 static struct ref **refs = NULL;
7257 static size_t refs_size = 0;
7258 static struct ref *refs_head = NULL;
7260 static struct ref_list **ref_lists = NULL;
7261 static size_t ref_lists_size = 0;
7263 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7264 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7265 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7267 static int
7268 compare_refs(const void *ref1_, const void *ref2_)
7270 const struct ref *ref1 = *(const struct ref **)ref1_;
7271 const struct ref *ref2 = *(const struct ref **)ref2_;
7273 if (ref1->tag != ref2->tag)
7274 return ref2->tag - ref1->tag;
7275 if (ref1->ltag != ref2->ltag)
7276 return ref2->ltag - ref2->ltag;
7277 if (ref1->head != ref2->head)
7278 return ref2->head - ref1->head;
7279 if (ref1->tracked != ref2->tracked)
7280 return ref2->tracked - ref1->tracked;
7281 if (ref1->remote != ref2->remote)
7282 return ref2->remote - ref1->remote;
7283 return strcmp(ref1->name, ref2->name);
7286 static void
7287 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7289 size_t i;
7291 for (i = 0; i < refs_size; i++)
7292 if (!visitor(data, refs[i]))
7293 break;
7296 static struct ref *
7297 get_ref_head()
7299 return refs_head;
7302 static struct ref_list *
7303 get_ref_list(const char *id)
7305 struct ref_list *list;
7306 size_t i;
7308 for (i = 0; i < ref_lists_size; i++)
7309 if (!strcmp(id, ref_lists[i]->id))
7310 return ref_lists[i];
7312 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7313 return NULL;
7314 list = calloc(1, sizeof(*list));
7315 if (!list)
7316 return NULL;
7318 for (i = 0; i < refs_size; i++) {
7319 if (!strcmp(id, refs[i]->id) &&
7320 realloc_refs_list(&list->refs, list->size, 1))
7321 list->refs[list->size++] = refs[i];
7324 if (!list->refs) {
7325 free(list);
7326 return NULL;
7329 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7330 ref_lists[ref_lists_size++] = list;
7331 return list;
7334 static int
7335 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7337 struct ref *ref = NULL;
7338 bool tag = FALSE;
7339 bool ltag = FALSE;
7340 bool remote = FALSE;
7341 bool tracked = FALSE;
7342 bool head = FALSE;
7343 int from = 0, to = refs_size - 1;
7345 if (!prefixcmp(name, "refs/tags/")) {
7346 if (!suffixcmp(name, namelen, "^{}")) {
7347 namelen -= 3;
7348 name[namelen] = 0;
7349 } else {
7350 ltag = TRUE;
7353 tag = TRUE;
7354 namelen -= STRING_SIZE("refs/tags/");
7355 name += STRING_SIZE("refs/tags/");
7357 } else if (!prefixcmp(name, "refs/remotes/")) {
7358 remote = TRUE;
7359 namelen -= STRING_SIZE("refs/remotes/");
7360 name += STRING_SIZE("refs/remotes/");
7361 tracked = !strcmp(opt_remote, name);
7363 } else if (!prefixcmp(name, "refs/heads/")) {
7364 namelen -= STRING_SIZE("refs/heads/");
7365 name += STRING_SIZE("refs/heads/");
7366 if (!strncmp(opt_head, name, namelen))
7367 return OK;
7369 } else if (!strcmp(name, "HEAD")) {
7370 head = TRUE;
7371 if (*opt_head) {
7372 namelen = strlen(opt_head);
7373 name = opt_head;
7377 /* If we are reloading or it's an annotated tag, replace the
7378 * previous SHA1 with the resolved commit id; relies on the fact
7379 * git-ls-remote lists the commit id of an annotated tag right
7380 * before the commit id it points to. */
7381 while (from <= to) {
7382 size_t pos = (to + from) / 2;
7383 int cmp = strcmp(name, refs[pos]->name);
7385 if (!cmp) {
7386 ref = refs[pos];
7387 break;
7390 if (cmp < 0)
7391 to = pos - 1;
7392 else
7393 from = pos + 1;
7396 if (!ref) {
7397 if (!realloc_refs(&refs, refs_size, 1))
7398 return ERR;
7399 ref = calloc(1, sizeof(*ref) + namelen);
7400 if (!ref)
7401 return ERR;
7402 memmove(refs + from + 1, refs + from,
7403 (refs_size - from) * sizeof(*refs));
7404 refs[from] = ref;
7405 strncpy(ref->name, name, namelen);
7406 refs_size++;
7409 ref->head = head;
7410 ref->tag = tag;
7411 ref->ltag = ltag;
7412 ref->remote = remote;
7413 ref->tracked = tracked;
7414 string_copy_rev(ref->id, id);
7416 if (head)
7417 refs_head = ref;
7418 return OK;
7421 static int
7422 load_refs(void)
7424 const char *head_argv[] = {
7425 "git", "symbolic-ref", "HEAD", NULL
7427 static const char *ls_remote_argv[SIZEOF_ARG] = {
7428 "git", "ls-remote", opt_git_dir, NULL
7430 static bool init = FALSE;
7431 size_t i;
7433 if (!init) {
7434 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7435 die("TIG_LS_REMOTE contains too many arguments");
7436 init = TRUE;
7439 if (!*opt_git_dir)
7440 return OK;
7442 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7443 !prefixcmp(opt_head, "refs/heads/")) {
7444 char *offset = opt_head + STRING_SIZE("refs/heads/");
7446 memmove(opt_head, offset, strlen(offset) + 1);
7449 refs_head = NULL;
7450 for (i = 0; i < refs_size; i++)
7451 refs[i]->id[0] = 0;
7453 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7454 return ERR;
7456 /* Update the ref lists to reflect changes. */
7457 for (i = 0; i < ref_lists_size; i++) {
7458 struct ref_list *list = ref_lists[i];
7459 size_t old, new;
7461 for (old = new = 0; old < list->size; old++)
7462 if (!strcmp(list->id, list->refs[old]->id))
7463 list->refs[new++] = list->refs[old];
7464 list->size = new;
7467 return OK;
7470 static void
7471 set_remote_branch(const char *name, const char *value, size_t valuelen)
7473 if (!strcmp(name, ".remote")) {
7474 string_ncopy(opt_remote, value, valuelen);
7476 } else if (*opt_remote && !strcmp(name, ".merge")) {
7477 size_t from = strlen(opt_remote);
7479 if (!prefixcmp(value, "refs/heads/"))
7480 value += STRING_SIZE("refs/heads/");
7482 if (!string_format_from(opt_remote, &from, "/%s", value))
7483 opt_remote[0] = 0;
7487 static void
7488 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7490 const char *argv[SIZEOF_ARG] = { name, "=" };
7491 int argc = 1 + (cmd == option_set_command);
7492 int error = ERR;
7494 if (!argv_from_string(argv, &argc, value))
7495 config_msg = "Too many option arguments";
7496 else
7497 error = cmd(argc, argv);
7499 if (error == ERR)
7500 warn("Option 'tig.%s': %s", name, config_msg);
7503 static bool
7504 set_environment_variable(const char *name, const char *value)
7506 size_t len = strlen(name) + 1 + strlen(value) + 1;
7507 char *env = malloc(len);
7509 if (env &&
7510 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7511 putenv(env) == 0)
7512 return TRUE;
7513 free(env);
7514 return FALSE;
7517 static void
7518 set_work_tree(const char *value)
7520 char cwd[SIZEOF_STR];
7522 if (!getcwd(cwd, sizeof(cwd)))
7523 die("Failed to get cwd path: %s", strerror(errno));
7524 if (chdir(opt_git_dir) < 0)
7525 die("Failed to chdir(%s): %s", strerror(errno));
7526 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7527 die("Failed to get git path: %s", strerror(errno));
7528 if (chdir(cwd) < 0)
7529 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7530 if (chdir(value) < 0)
7531 die("Failed to chdir(%s): %s", value, strerror(errno));
7532 if (!getcwd(cwd, sizeof(cwd)))
7533 die("Failed to get cwd path: %s", strerror(errno));
7534 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7535 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7536 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7537 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7538 opt_is_inside_work_tree = TRUE;
7541 static int
7542 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7544 if (!strcmp(name, "i18n.commitencoding"))
7545 string_ncopy(opt_encoding, value, valuelen);
7547 else if (!strcmp(name, "core.editor"))
7548 string_ncopy(opt_editor, value, valuelen);
7550 else if (!strcmp(name, "core.worktree"))
7551 set_work_tree(value);
7553 else if (!prefixcmp(name, "tig.color."))
7554 set_repo_config_option(name + 10, value, option_color_command);
7556 else if (!prefixcmp(name, "tig.bind."))
7557 set_repo_config_option(name + 9, value, option_bind_command);
7559 else if (!prefixcmp(name, "tig."))
7560 set_repo_config_option(name + 4, value, option_set_command);
7562 else if (*opt_head && !prefixcmp(name, "branch.") &&
7563 !strncmp(name + 7, opt_head, strlen(opt_head)))
7564 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7566 return OK;
7569 static int
7570 load_git_config(void)
7572 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7574 return io_run_load(config_list_argv, "=", read_repo_config_option);
7577 static int
7578 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7580 if (!opt_git_dir[0]) {
7581 string_ncopy(opt_git_dir, name, namelen);
7583 } else if (opt_is_inside_work_tree == -1) {
7584 /* This can be 3 different values depending on the
7585 * version of git being used. If git-rev-parse does not
7586 * understand --is-inside-work-tree it will simply echo
7587 * the option else either "true" or "false" is printed.
7588 * Default to true for the unknown case. */
7589 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7591 } else if (*name == '.') {
7592 string_ncopy(opt_cdup, name, namelen);
7594 } else {
7595 string_ncopy(opt_prefix, name, namelen);
7598 return OK;
7601 static int
7602 load_repo_info(void)
7604 const char *rev_parse_argv[] = {
7605 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7606 "--show-cdup", "--show-prefix", NULL
7609 return io_run_load(rev_parse_argv, "=", read_repo_info);
7614 * Main
7617 static const char usage[] =
7618 "tig " TIG_VERSION " (" __DATE__ ")\n"
7619 "\n"
7620 "Usage: tig [options] [revs] [--] [paths]\n"
7621 " or: tig show [options] [revs] [--] [paths]\n"
7622 " or: tig blame [rev] path\n"
7623 " or: tig status\n"
7624 " or: tig < [git command output]\n"
7625 "\n"
7626 "Options:\n"
7627 " -v, --version Show version and exit\n"
7628 " -h, --help Show help message and exit";
7630 static void __NORETURN
7631 quit(int sig)
7633 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7634 if (cursed)
7635 endwin();
7636 exit(0);
7639 static void __NORETURN
7640 die(const char *err, ...)
7642 va_list args;
7644 endwin();
7646 va_start(args, err);
7647 fputs("tig: ", stderr);
7648 vfprintf(stderr, err, args);
7649 fputs("\n", stderr);
7650 va_end(args);
7652 exit(1);
7655 static void
7656 warn(const char *msg, ...)
7658 va_list args;
7660 va_start(args, msg);
7661 fputs("tig warning: ", stderr);
7662 vfprintf(stderr, msg, args);
7663 fputs("\n", stderr);
7664 va_end(args);
7667 static enum request
7668 parse_options(int argc, const char *argv[])
7670 enum request request = REQ_VIEW_MAIN;
7671 const char *subcommand;
7672 bool seen_dashdash = FALSE;
7673 /* XXX: This is vulnerable to the user overriding options
7674 * required for the main view parser. */
7675 const char *custom_argv[SIZEOF_ARG] = {
7676 "git", "log", "--no-color", "--pretty=raw", "--parents",
7677 "--topo-order", NULL
7679 int i, j = 6;
7681 if (!isatty(STDIN_FILENO)) {
7682 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7683 return REQ_VIEW_PAGER;
7686 if (argc <= 1)
7687 return REQ_NONE;
7689 subcommand = argv[1];
7690 if (!strcmp(subcommand, "status")) {
7691 if (argc > 2)
7692 warn("ignoring arguments after `%s'", subcommand);
7693 return REQ_VIEW_STATUS;
7695 } else if (!strcmp(subcommand, "blame")) {
7696 if (argc <= 2 || argc > 4)
7697 die("invalid number of options to blame\n\n%s", usage);
7699 i = 2;
7700 if (argc == 4) {
7701 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7702 i++;
7705 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7706 return REQ_VIEW_BLAME;
7708 } else if (!strcmp(subcommand, "show")) {
7709 request = REQ_VIEW_DIFF;
7711 } else {
7712 subcommand = NULL;
7715 if (subcommand) {
7716 custom_argv[1] = subcommand;
7717 j = 2;
7720 for (i = 1 + !!subcommand; i < argc; i++) {
7721 const char *opt = argv[i];
7723 if (seen_dashdash || !strcmp(opt, "--")) {
7724 seen_dashdash = TRUE;
7726 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7727 printf("tig version %s\n", TIG_VERSION);
7728 quit(0);
7730 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7731 printf("%s\n", usage);
7732 quit(0);
7735 custom_argv[j++] = opt;
7736 if (j >= ARRAY_SIZE(custom_argv))
7737 die("command too long");
7740 if (!prepare_update(VIEW(request), custom_argv, NULL))
7741 die("Failed to format arguments");
7743 return request;
7747 main(int argc, const char *argv[])
7749 const char *codeset = "UTF-8";
7750 enum request request = parse_options(argc, argv);
7751 struct view *view;
7752 size_t i;
7754 signal(SIGINT, quit);
7755 signal(SIGPIPE, SIG_IGN);
7757 if (setlocale(LC_ALL, "")) {
7758 codeset = nl_langinfo(CODESET);
7761 if (load_repo_info() == ERR)
7762 die("Failed to load repo info.");
7764 if (load_options() == ERR)
7765 die("Failed to load user config.");
7767 if (load_git_config() == ERR)
7768 die("Failed to load repo config.");
7770 /* Require a git repository unless when running in pager mode. */
7771 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7772 die("Not a git repository");
7774 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7775 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7776 if (opt_iconv_in == ICONV_NONE)
7777 die("Failed to initialize character set conversion");
7780 if (codeset && strcmp(codeset, "UTF-8")) {
7781 opt_iconv_out = iconv_open(codeset, "UTF-8");
7782 if (opt_iconv_out == ICONV_NONE)
7783 die("Failed to initialize character set conversion");
7786 if (load_refs() == ERR)
7787 die("Failed to load refs.");
7789 foreach_view (view, i)
7790 if (!argv_from_env(view->ops->argv, view->cmd_env))
7791 die("Too many arguments in the `%s` environment variable",
7792 view->cmd_env);
7794 init_display();
7796 if (request != REQ_NONE)
7797 open_view(NULL, request, OPEN_PREPARED);
7798 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7800 while (view_driver(display[current_view], request)) {
7801 int key = get_input(0);
7803 view = display[current_view];
7804 request = get_keybinding(view->keymap, key);
7806 /* Some low-level request handling. This keeps access to
7807 * status_win restricted. */
7808 switch (request) {
7809 case REQ_PROMPT:
7811 char *cmd = read_prompt(":");
7813 if (cmd && isdigit(*cmd)) {
7814 int lineno = view->lineno + 1;
7816 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7817 select_view_line(view, lineno - 1);
7818 report("");
7819 } else {
7820 report("Unable to parse '%s' as a line number", cmd);
7823 } else if (cmd) {
7824 struct view *next = VIEW(REQ_VIEW_PAGER);
7825 const char *argv[SIZEOF_ARG] = { "git" };
7826 int argc = 1;
7828 /* When running random commands, initially show the
7829 * command in the title. However, it maybe later be
7830 * overwritten if a commit line is selected. */
7831 string_ncopy(next->ref, cmd, strlen(cmd));
7833 if (!argv_from_string(argv, &argc, cmd)) {
7834 report("Too many arguments");
7835 } else if (!prepare_update(next, argv, NULL)) {
7836 report("Failed to format command");
7837 } else {
7838 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7842 request = REQ_NONE;
7843 break;
7845 case REQ_SEARCH:
7846 case REQ_SEARCH_BACK:
7848 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7849 char *search = read_prompt(prompt);
7851 if (search)
7852 string_ncopy(opt_search, search, strlen(search));
7853 else if (*opt_search)
7854 request = request == REQ_SEARCH ?
7855 REQ_FIND_NEXT :
7856 REQ_FIND_PREV;
7857 else
7858 request = REQ_NONE;
7859 break;
7861 default:
7862 break;
7866 quit(0);
7868 return 0;