Allow built-in run requests to be unbound
[tig.git] / tig.c
blob7eb9f71b6827036fea61d811fedd0296d0a7dc28
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_NONE /* No replacement should be performed. */
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151 INPUT_OK,
152 INPUT_SKIP,
153 INPUT_STOP,
154 INPUT_CANCEL
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163 int hotkey;
164 const char *text;
165 void *data;
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 * Allocation helpers ... Entering macro hell to never be seen again.
174 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
175 static type * \
176 name(type **mem, size_t size, size_t increase) \
178 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
179 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180 type *tmp = *mem; \
182 if (mem == NULL || num_chunks != num_chunks_new) { \
183 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184 if (tmp) \
185 *mem = tmp; \
188 return tmp; \
192 * String helpers
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198 if (srclen > dstlen - 1)
199 srclen = dstlen - 1;
201 strncpy(dst, src, srclen);
202 dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211 string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222 size_t size, pos;
224 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225 if (src[pos] == '\t') {
226 size_t expanded = tabsize - (size % tabsize);
228 if (expanded + size >= dstlen - 1)
229 expanded = dstlen - size - 1;
230 memcpy(dst + size, " ", expanded);
231 size += expanded;
232 } else {
233 dst[size++] = src[pos];
237 dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243 int namelen;
245 while (isspace(*name))
246 name++;
248 namelen = strlen(name) - 1;
249 while (namelen > 0 && isspace(name[namelen]))
250 name[namelen--] = 0;
252 return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258 va_list args;
259 size_t pos = bufpos ? *bufpos : 0;
261 va_start(args, fmt);
262 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 va_end(args);
265 if (bufpos)
266 *bufpos = pos;
268 return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272 string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275 string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280 size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284 /* Diff-Header == DIFF_HEADER */
285 for (i = 0; i < len; i++) {
286 if (toupper(str1[i]) == toupper(str2[i]))
287 continue;
289 if (string_enum_sep(str1[i]) &&
290 string_enum_sep(str2[i]))
291 continue;
293 return str1[i] - str2[i];
296 return 0;
299 #define enum_equals(entry, str, len) \
300 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303 const char *name;
304 int namelen;
305 int value;
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313 static char buf[SIZEOF_STR];
314 int bufpos;
316 for (bufpos = 0; bufpos <= namelen; bufpos++) {
317 buf[bufpos] = tolower(name[bufpos]);
318 if (buf[bufpos] == '_')
319 buf[bufpos] = '-';
322 buf[bufpos] = 0;
323 return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331 size_t namelen = strlen(name);
332 int i;
334 for (i = 0; i < map_size; i++)
335 if (enum_equals(map[i], name, namelen)) {
336 *value = map[i].value;
337 return TRUE;
340 return FALSE;
343 #define map_enum(attr, map, name) \
344 map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347 strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352 size_t len = slen >= 0 ? slen : strlen(str);
353 size_t suffixlen = strlen(suffix);
355 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
360 * Unicode / UTF-8 handling
362 * NOTE: Much of the following code for dealing with Unicode is derived from
363 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370 if (c >= 0x1100 &&
371 (c <= 0x115f /* Hangul Jamo */
372 || c == 0x2329
373 || c == 0x232a
374 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
375 /* CJK ... Yi */
376 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
377 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
378 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
379 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
380 || (c >= 0xffe0 && c <= 0xffe6)
381 || (c >= 0x20000 && c <= 0x2fffd)
382 || (c >= 0x30000 && c <= 0x3fffd)))
383 return 2;
385 if (c == '\t')
386 return tab_size;
388 return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392 * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407 int c = *(unsigned char *) string;
409 return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416 unsigned long unicode;
418 switch (length) {
419 case 1:
420 unicode = string[0];
421 break;
422 case 2:
423 unicode = (string[0] & 0x1f) << 6;
424 unicode += (string[1] & 0x3f);
425 break;
426 case 3:
427 unicode = (string[0] & 0x0f) << 12;
428 unicode += ((string[1] & 0x3f) << 6);
429 unicode += (string[2] & 0x3f);
430 break;
431 case 4:
432 unicode = (string[0] & 0x0f) << 18;
433 unicode += ((string[1] & 0x3f) << 12);
434 unicode += ((string[2] & 0x3f) << 6);
435 unicode += (string[3] & 0x3f);
436 break;
437 case 5:
438 unicode = (string[0] & 0x0f) << 24;
439 unicode += ((string[1] & 0x3f) << 18);
440 unicode += ((string[2] & 0x3f) << 12);
441 unicode += ((string[3] & 0x3f) << 6);
442 unicode += (string[4] & 0x3f);
443 break;
444 case 6:
445 unicode = (string[0] & 0x01) << 30;
446 unicode += ((string[1] & 0x3f) << 24);
447 unicode += ((string[2] & 0x3f) << 18);
448 unicode += ((string[3] & 0x3f) << 12);
449 unicode += ((string[4] & 0x3f) << 6);
450 unicode += (string[5] & 0x3f);
451 break;
452 default:
453 return 0;
456 /* Invalid characters could return the special 0xfffd value but NUL
457 * should be just as good. */
458 return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462 * and sets trimmed parameter to non-zero value if all of string could not be
463 * shown. If the reserve flag is TRUE, it will reserve at least one
464 * trailing character, which can be useful when drawing a delimiter.
466 * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470 const char *string = *start;
471 const char *end = strchr(string, '\0');
472 unsigned char last_bytes = 0;
473 size_t last_ucwidth = 0;
475 *width = 0;
476 *trimmed = 0;
478 while (string < end) {
479 unsigned char bytes = utf8_char_length(string, end);
480 size_t ucwidth;
481 unsigned long unicode;
483 if (string + bytes > end)
484 break;
486 /* Change representation to figure out whether
487 * it is a single- or double-width character. */
489 unicode = utf8_to_unicode(string, bytes);
490 /* FIXME: Graceful handling of invalid Unicode character. */
491 if (!unicode)
492 break;
494 ucwidth = unicode_width(unicode, tab_size);
495 if (skip > 0) {
496 skip -= ucwidth <= skip ? ucwidth : skip;
497 *start += bytes;
499 *width += ucwidth;
500 if (*width > max_width) {
501 *trimmed = 1;
502 *width -= ucwidth;
503 if (reserve && *width == max_width) {
504 string -= last_bytes;
505 *width -= last_ucwidth;
507 break;
510 string += bytes;
511 last_bytes = ucwidth ? bytes : 0;
512 last_ucwidth = ucwidth;
515 return string - *start;
519 #define DATE_INFO \
520 DATE_(NO), \
521 DATE_(DEFAULT), \
522 DATE_(LOCAL), \
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 if (date == DATE_LOCAL) {
588 time_t date = time->sec + time->tz;
589 localtime_r(&date, &tm);
591 else {
592 gmtime_r(&time->sec, &tm);
594 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
598 #define AUTHOR_VALUES \
599 AUTHOR_(NO), \
600 AUTHOR_(FULL), \
601 AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605 AUTHOR_VALUES,
606 #undef AUTHOR_
607 AUTHOR_DEFAULT = AUTHOR_FULL
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612 AUTHOR_VALUES
613 #undef AUTHOR_
616 static const char *
617 get_author_initials(const char *author)
619 static char initials[AUTHOR_COLS * 6 + 1];
620 size_t pos = 0;
621 const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625 memset(initials, 0, sizeof(initials));
626 while (author < end) {
627 unsigned char bytes;
628 size_t i;
630 while (is_initial_sep(*author))
631 author++;
633 bytes = utf8_char_length(author, end);
634 if (bytes < sizeof(initials) - 1 - pos) {
635 while (bytes--) {
636 initials[pos++] = *author++;
640 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641 if (i < sizeof(initials) - 1)
642 initials[i++] = *author;
645 initials[i++] = 0;
648 return initials;
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
655 int valuelen;
657 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658 bool advance = cmd[valuelen] != 0;
660 cmd[valuelen] = 0;
661 argv[(*argc)++] = chomp_string(cmd);
662 cmd = chomp_string(cmd + valuelen + advance);
665 if (*argc < SIZEOF_ARG)
666 argv[*argc] = NULL;
667 return *argc < SIZEOF_ARG;
670 static bool
671 argv_from_env(const char **argv, const char *name)
673 char *env = argv ? getenv(name) : NULL;
674 int argc = 0;
676 if (env && *env)
677 env = strdup(env);
678 return !env || argv_from_string(argv, &argc, env);
683 * Executing external commands.
686 enum io_type {
687 IO_FD, /* File descriptor based IO. */
688 IO_BG, /* Execute command in the background. */
689 IO_FG, /* Execute command with same std{in,out,err}. */
690 IO_RD, /* Read only fork+exec IO. */
691 IO_WR, /* Write only fork+exec IO. */
692 IO_AP, /* Append fork+exec output to file. */
695 struct io {
696 enum io_type type; /* The requested type of pipe. */
697 const char *dir; /* Directory from which to execute. */
698 pid_t pid; /* PID of spawned process. */
699 int pipe; /* Pipe end for reading or writing. */
700 int error; /* Error status. */
701 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
702 char *buf; /* Read buffer. */
703 size_t bufalloc; /* Allocated buffer size. */
704 size_t bufsize; /* Buffer content size. */
705 char *bufpos; /* Current buffer position. */
706 unsigned int eof:1; /* Has end of file been reached. */
709 static void
710 io_reset(struct io *io)
712 io->pipe = -1;
713 io->pid = 0;
714 io->buf = io->bufpos = NULL;
715 io->bufalloc = io->bufsize = 0;
716 io->error = 0;
717 io->eof = 0;
720 static void
721 io_init(struct io *io, const char *dir, enum io_type type)
723 io_reset(io);
724 io->type = type;
725 io->dir = dir;
728 static bool
729 io_format(struct io *io, const char *dir, enum io_type type,
730 const char *argv[], enum format_flags flags)
732 io_init(io, dir, type);
733 return format_argv(io->argv, argv, flags);
736 static bool
737 io_open(struct io *io, const char *fmt, ...)
739 char name[SIZEOF_STR] = "";
740 bool fits;
741 va_list args;
743 io_init(io, NULL, IO_FD);
745 va_start(args, fmt);
746 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
747 va_end(args);
749 if (!fits) {
750 io->error = ENAMETOOLONG;
751 return FALSE;
753 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
754 if (io->pipe == -1)
755 io->error = errno;
756 return io->pipe != -1;
759 static bool
760 io_kill(struct io *io)
762 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
765 static bool
766 io_done(struct io *io)
768 pid_t pid = io->pid;
770 if (io->pipe != -1)
771 close(io->pipe);
772 free(io->buf);
773 io_reset(io);
775 while (pid > 0) {
776 int status;
777 pid_t waiting = waitpid(pid, &status, 0);
779 if (waiting < 0) {
780 if (errno == EINTR)
781 continue;
782 io->error = errno;
783 return FALSE;
786 return waiting == pid &&
787 !WIFSIGNALED(status) &&
788 WIFEXITED(status) &&
789 !WEXITSTATUS(status);
792 return TRUE;
795 static bool
796 io_start(struct io *io)
798 int pipefds[2] = { -1, -1 };
800 if (io->type == IO_FD)
801 return TRUE;
803 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
804 io->error = errno;
805 return FALSE;
806 } else if (io->type == IO_AP) {
807 pipefds[1] = io->pipe;
810 if ((io->pid = fork())) {
811 if (io->pid == -1)
812 io->error = errno;
813 if (pipefds[!(io->type == IO_WR)] != -1)
814 close(pipefds[!(io->type == IO_WR)]);
815 if (io->pid != -1) {
816 io->pipe = pipefds[!!(io->type == IO_WR)];
817 return TRUE;
820 } else {
821 if (io->type != IO_FG) {
822 int devnull = open("/dev/null", O_RDWR);
823 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
824 int writefd = (io->type == IO_RD || io->type == IO_AP)
825 ? pipefds[1] : devnull;
827 dup2(readfd, STDIN_FILENO);
828 dup2(writefd, STDOUT_FILENO);
829 dup2(devnull, STDERR_FILENO);
831 close(devnull);
832 if (pipefds[0] != -1)
833 close(pipefds[0]);
834 if (pipefds[1] != -1)
835 close(pipefds[1]);
838 if (io->dir && *io->dir && chdir(io->dir) == -1)
839 exit(errno);
841 execvp(io->argv[0], (char *const*) io->argv);
842 exit(errno);
845 if (pipefds[!!(io->type == IO_WR)] != -1)
846 close(pipefds[!!(io->type == IO_WR)]);
847 return FALSE;
850 static bool
851 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
853 io_init(io, dir, type);
854 if (!format_argv(io->argv, argv, FORMAT_NONE))
855 return FALSE;
856 return io_start(io);
859 static int
860 io_complete(struct io *io)
862 return io_start(io) && io_done(io);
865 static int
866 io_run_bg(const char **argv)
868 struct io io = {};
870 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
871 return FALSE;
872 return io_complete(&io);
875 static bool
876 io_run_fg(const char **argv, const char *dir)
878 struct io io = {};
880 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
881 return FALSE;
882 return io_complete(&io);
885 static bool
886 io_run_append(const char **argv, enum format_flags flags, int fd)
888 struct io io = {};
890 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
891 close(fd);
892 return FALSE;
895 io.pipe = fd;
896 return io_complete(&io);
899 static bool
900 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
902 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
905 static bool
906 io_eof(struct io *io)
908 return io->eof;
911 static int
912 io_error(struct io *io)
914 return io->error;
917 static char *
918 io_strerror(struct io *io)
920 return strerror(io->error);
923 static bool
924 io_can_read(struct io *io)
926 struct timeval tv = { 0, 500 };
927 fd_set fds;
929 FD_ZERO(&fds);
930 FD_SET(io->pipe, &fds);
932 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
938 do {
939 ssize_t readsize = read(io->pipe, buf, bufsize);
941 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942 continue;
943 else if (readsize == -1)
944 io->error = errno;
945 else if (readsize == 0)
946 io->eof = 1;
947 return readsize;
948 } while (1);
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
956 char *eol;
957 ssize_t readsize;
959 while (TRUE) {
960 if (io->bufsize > 0) {
961 eol = memchr(io->bufpos, c, io->bufsize);
962 if (eol) {
963 char *line = io->bufpos;
965 *eol = 0;
966 io->bufpos = eol + 1;
967 io->bufsize -= io->bufpos - line;
968 return line;
972 if (io_eof(io)) {
973 if (io->bufsize) {
974 io->bufpos[io->bufsize] = 0;
975 io->bufsize = 0;
976 return io->bufpos;
978 return NULL;
981 if (!can_read)
982 return NULL;
984 if (io->bufsize > 0 && io->bufpos > io->buf)
985 memmove(io->buf, io->bufpos, io->bufsize);
987 if (io->bufalloc == io->bufsize) {
988 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989 return NULL;
990 io->bufalloc += BUFSIZ;
993 io->bufpos = io->buf;
994 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995 if (io_error(io))
996 return NULL;
997 io->bufsize += readsize;
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1004 size_t written = 0;
1006 while (!io_error(io) && written < bufsize) {
1007 ssize_t size;
1009 size = write(io->pipe, buf + written, bufsize - written);
1010 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011 continue;
1012 else if (size == -1)
1013 io->error = errno;
1014 else
1015 written += size;
1018 return written == bufsize;
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024 char *result = io_get(io, '\n', TRUE);
1026 if (result) {
1027 result = chomp_string(result);
1028 string_ncopy_do(buf, bufsize, result, strlen(result));
1031 return io_done(io) && result;
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037 struct io io = {};
1039 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1040 && io_read_buf(&io, buf, bufsize);
1043 static int
1044 io_load(struct io *io, const char *separators,
1045 int (*read_property)(char *, size_t, char *, size_t))
1047 char *name;
1048 int state = OK;
1050 if (!io_start(io))
1051 return ERR;
1053 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054 char *value;
1055 size_t namelen;
1056 size_t valuelen;
1058 name = chomp_string(name);
1059 namelen = strcspn(name, separators);
1061 if (name[namelen]) {
1062 name[namelen] = 0;
1063 value = chomp_string(name + namelen + 1);
1064 valuelen = strlen(value);
1066 } else {
1067 value = "";
1068 valuelen = 0;
1071 state = read_property(name, namelen, value, valuelen);
1074 if (state != ERR && io_error(io))
1075 state = ERR;
1076 io_done(io);
1078 return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083 int (*read_property)(char *, size_t, char *, size_t))
1085 struct io io = {};
1087 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1088 ? io_load(&io, separators, read_property) : ERR;
1093 * User requests
1096 #define REQ_INFO \
1097 /* XXX: Keep the view request first and in sync with views[]. */ \
1098 REQ_GROUP("View switching") \
1099 REQ_(VIEW_MAIN, "Show main view"), \
1100 REQ_(VIEW_DIFF, "Show diff view"), \
1101 REQ_(VIEW_LOG, "Show log view"), \
1102 REQ_(VIEW_TREE, "Show tree view"), \
1103 REQ_(VIEW_BLOB, "Show blob view"), \
1104 REQ_(VIEW_BLAME, "Show blame view"), \
1105 REQ_(VIEW_BRANCH, "Show branch view"), \
1106 REQ_(VIEW_HELP, "Show help page"), \
1107 REQ_(VIEW_PAGER, "Show pager view"), \
1108 REQ_(VIEW_STATUS, "Show status view"), \
1109 REQ_(VIEW_STAGE, "Show stage view"), \
1111 REQ_GROUP("View manipulation") \
1112 REQ_(ENTER, "Enter current line and scroll"), \
1113 REQ_(NEXT, "Move to next"), \
1114 REQ_(PREVIOUS, "Move to previous"), \
1115 REQ_(PARENT, "Move to parent"), \
1116 REQ_(VIEW_NEXT, "Move focus to next view"), \
1117 REQ_(REFRESH, "Reload and refresh"), \
1118 REQ_(MAXIMIZE, "Maximize the current view"), \
1119 REQ_(VIEW_CLOSE, "Close the current view"), \
1120 REQ_(QUIT, "Close all views and quit"), \
1122 REQ_GROUP("View specific requests") \
1123 REQ_(STATUS_UPDATE, "Update file status"), \
1124 REQ_(STATUS_REVERT, "Revert file changes"), \
1125 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1126 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1128 REQ_GROUP("Cursor navigation") \
1129 REQ_(MOVE_UP, "Move cursor one line up"), \
1130 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1131 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1132 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1133 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1134 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1136 REQ_GROUP("Scrolling") \
1137 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1138 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1139 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1140 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1141 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1142 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1144 REQ_GROUP("Searching") \
1145 REQ_(SEARCH, "Search the view"), \
1146 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1147 REQ_(FIND_NEXT, "Find next search match"), \
1148 REQ_(FIND_PREV, "Find previous search match"), \
1150 REQ_GROUP("Option manipulation") \
1151 REQ_(OPTIONS, "Open option menu"), \
1152 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1153 REQ_(TOGGLE_DATE, "Toggle date display"), \
1154 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1156 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1157 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1158 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1161 REQ_GROUP("Misc") \
1162 REQ_(PROMPT, "Bring up the prompt"), \
1163 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1164 REQ_(SHOW_VERSION, "Show version information"), \
1165 REQ_(STOP_LOADING, "Stop all loading views"), \
1166 REQ_(EDIT, "Open in editor"), \
1167 REQ_(NONE, "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175 /* Offset all requests to avoid conflicts with ncurses getch values. */
1176 REQ_UNKNOWN = KEY_MAX + 1,
1177 REQ_OFFSET,
1178 REQ_INFO
1180 #undef REQ_GROUP
1181 #undef REQ_
1184 struct request_info {
1185 enum request request;
1186 const char *name;
1187 int namelen;
1188 const char *help;
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194 REQ_INFO
1195 #undef REQ_GROUP
1196 #undef REQ_
1199 static enum request
1200 get_request(const char *name)
1202 int namelen = strlen(name);
1203 int i;
1205 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206 if (enum_equals(req_info[i], name, namelen))
1207 return req_info[i].request;
1209 return REQ_UNKNOWN;
1214 * Options
1217 /* Option and state variables. */
1218 static enum date opt_date = DATE_DEFAULT;
1219 static enum author opt_author = AUTHOR_DEFAULT;
1220 static bool opt_line_number = FALSE;
1221 static bool opt_line_graphics = TRUE;
1222 static bool opt_rev_graph = FALSE;
1223 static bool opt_show_refs = TRUE;
1224 static int opt_num_interval = 5;
1225 static double opt_hscroll = 0.50;
1226 static double opt_scale_split_view = 2.0 / 3.0;
1227 static int opt_tab_size = 8;
1228 static int opt_author_cols = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR] = "";
1230 static char opt_file[SIZEOF_STR] = "";
1231 static char opt_ref[SIZEOF_REF] = "";
1232 static char opt_head[SIZEOF_REF] = "";
1233 static char opt_remote[SIZEOF_REF] = "";
1234 static char opt_encoding[20] = "UTF-8";
1235 static iconv_t opt_iconv_in = ICONV_NONE;
1236 static iconv_t opt_iconv_out = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR] = "";
1238 static char opt_cdup[SIZEOF_STR] = "";
1239 static char opt_prefix[SIZEOF_STR] = "";
1240 static char opt_git_dir[SIZEOF_STR] = "";
1241 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR] = "";
1243 static FILE *opt_tty = NULL;
1245 #define is_initial_commit() (!get_ref_head())
1246 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1250 * Line-oriented content detection.
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1268 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1269 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1270 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1275 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1276 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1278 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1279 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1280 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1283 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1284 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1285 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1287 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1288 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1289 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1290 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1291 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1293 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1295 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1296 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1298 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1299 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1300 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1301 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1302 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1303 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1304 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1305 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1306 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1307 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1308 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1309 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1310 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1312 enum line_type {
1313 #define LINE(type, line, fg, bg, attr) \
1314 LINE_##type
1315 LINE_INFO,
1316 LINE_NONE
1317 #undef LINE
1320 struct line_info {
1321 const char *name; /* Option name. */
1322 int namelen; /* Size of option name. */
1323 const char *line; /* The start of line to match. */
1324 int linelen; /* Size of string to match. */
1325 int fg, bg, attr; /* Color and text attributes for the lines. */
1328 static struct line_info line_info[] = {
1329 #define LINE(type, line, fg, bg, attr) \
1330 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1331 LINE_INFO
1332 #undef LINE
1335 static enum line_type
1336 get_line_type(const char *line)
1338 int linelen = strlen(line);
1339 enum line_type type;
1341 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1342 /* Case insensitive search matches Signed-off-by lines better. */
1343 if (linelen >= line_info[type].linelen &&
1344 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1345 return type;
1347 return LINE_DEFAULT;
1350 static inline int
1351 get_line_attr(enum line_type type)
1353 assert(type < ARRAY_SIZE(line_info));
1354 return COLOR_PAIR(type) | line_info[type].attr;
1357 static struct line_info *
1358 get_line_info(const char *name)
1360 size_t namelen = strlen(name);
1361 enum line_type type;
1363 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1364 if (enum_equals(line_info[type], name, namelen))
1365 return &line_info[type];
1367 return NULL;
1370 static void
1371 init_colors(void)
1373 int default_bg = line_info[LINE_DEFAULT].bg;
1374 int default_fg = line_info[LINE_DEFAULT].fg;
1375 enum line_type type;
1377 start_color();
1379 if (assume_default_colors(default_fg, default_bg) == ERR) {
1380 default_bg = COLOR_BLACK;
1381 default_fg = COLOR_WHITE;
1384 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1385 struct line_info *info = &line_info[type];
1386 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1387 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1389 init_pair(type, fg, bg);
1393 struct line {
1394 enum line_type type;
1396 /* State flags */
1397 unsigned int selected:1;
1398 unsigned int dirty:1;
1399 unsigned int cleareol:1;
1400 unsigned int other:16;
1402 void *data; /* User data */
1407 * Keys
1410 struct keybinding {
1411 int alias;
1412 enum request request;
1415 static struct keybinding default_keybindings[] = {
1416 /* View switching */
1417 { 'm', REQ_VIEW_MAIN },
1418 { 'd', REQ_VIEW_DIFF },
1419 { 'l', REQ_VIEW_LOG },
1420 { 't', REQ_VIEW_TREE },
1421 { 'f', REQ_VIEW_BLOB },
1422 { 'B', REQ_VIEW_BLAME },
1423 { 'H', REQ_VIEW_BRANCH },
1424 { 'p', REQ_VIEW_PAGER },
1425 { 'h', REQ_VIEW_HELP },
1426 { 'S', REQ_VIEW_STATUS },
1427 { 'c', REQ_VIEW_STAGE },
1429 /* View manipulation */
1430 { 'q', REQ_VIEW_CLOSE },
1431 { KEY_TAB, REQ_VIEW_NEXT },
1432 { KEY_RETURN, REQ_ENTER },
1433 { KEY_UP, REQ_PREVIOUS },
1434 { KEY_DOWN, REQ_NEXT },
1435 { 'R', REQ_REFRESH },
1436 { KEY_F(5), REQ_REFRESH },
1437 { 'O', REQ_MAXIMIZE },
1439 /* Cursor navigation */
1440 { 'k', REQ_MOVE_UP },
1441 { 'j', REQ_MOVE_DOWN },
1442 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1443 { KEY_END, REQ_MOVE_LAST_LINE },
1444 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1445 { ' ', REQ_MOVE_PAGE_DOWN },
1446 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1447 { 'b', REQ_MOVE_PAGE_UP },
1448 { '-', REQ_MOVE_PAGE_UP },
1450 /* Scrolling */
1451 { KEY_LEFT, REQ_SCROLL_LEFT },
1452 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1453 { KEY_IC, REQ_SCROLL_LINE_UP },
1454 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1455 { 'w', REQ_SCROLL_PAGE_UP },
1456 { 's', REQ_SCROLL_PAGE_DOWN },
1458 /* Searching */
1459 { '/', REQ_SEARCH },
1460 { '?', REQ_SEARCH_BACK },
1461 { 'n', REQ_FIND_NEXT },
1462 { 'N', REQ_FIND_PREV },
1464 /* Misc */
1465 { 'Q', REQ_QUIT },
1466 { 'z', REQ_STOP_LOADING },
1467 { 'v', REQ_SHOW_VERSION },
1468 { 'r', REQ_SCREEN_REDRAW },
1469 { 'o', REQ_OPTIONS },
1470 { '.', REQ_TOGGLE_LINENO },
1471 { 'D', REQ_TOGGLE_DATE },
1472 { 'A', REQ_TOGGLE_AUTHOR },
1473 { 'g', REQ_TOGGLE_REV_GRAPH },
1474 { 'F', REQ_TOGGLE_REFS },
1475 { 'I', REQ_TOGGLE_SORT_ORDER },
1476 { 'i', REQ_TOGGLE_SORT_FIELD },
1477 { ':', REQ_PROMPT },
1478 { 'u', REQ_STATUS_UPDATE },
1479 { '!', REQ_STATUS_REVERT },
1480 { 'M', REQ_STATUS_MERGE },
1481 { '@', REQ_STAGE_NEXT },
1482 { ',', REQ_PARENT },
1483 { 'e', REQ_EDIT },
1486 #define KEYMAP_INFO \
1487 KEYMAP_(GENERIC), \
1488 KEYMAP_(MAIN), \
1489 KEYMAP_(DIFF), \
1490 KEYMAP_(LOG), \
1491 KEYMAP_(TREE), \
1492 KEYMAP_(BLOB), \
1493 KEYMAP_(BLAME), \
1494 KEYMAP_(BRANCH), \
1495 KEYMAP_(PAGER), \
1496 KEYMAP_(HELP), \
1497 KEYMAP_(STATUS), \
1498 KEYMAP_(STAGE)
1500 enum keymap {
1501 #define KEYMAP_(name) KEYMAP_##name
1502 KEYMAP_INFO
1503 #undef KEYMAP_
1506 static const struct enum_map keymap_table[] = {
1507 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1508 KEYMAP_INFO
1509 #undef KEYMAP_
1512 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1514 struct keybinding_table {
1515 struct keybinding *data;
1516 size_t size;
1519 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1521 static void
1522 add_keybinding(enum keymap keymap, enum request request, int key)
1524 struct keybinding_table *table = &keybindings[keymap];
1526 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1527 if (!table->data)
1528 die("Failed to allocate keybinding");
1529 table->data[table->size].alias = key;
1530 table->data[table->size++].request = request;
1532 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1533 int i;
1535 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1536 if (default_keybindings[i].alias == key)
1537 default_keybindings[i].request = REQ_NONE;
1541 /* Looks for a key binding first in the given map, then in the generic map, and
1542 * lastly in the default keybindings. */
1543 static enum request
1544 get_keybinding(enum keymap keymap, int key)
1546 size_t i;
1548 for (i = 0; i < keybindings[keymap].size; i++)
1549 if (keybindings[keymap].data[i].alias == key)
1550 return keybindings[keymap].data[i].request;
1552 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1553 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1554 return keybindings[KEYMAP_GENERIC].data[i].request;
1556 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1557 if (default_keybindings[i].alias == key)
1558 return default_keybindings[i].request;
1560 return (enum request) key;
1564 struct key {
1565 const char *name;
1566 int value;
1569 static const struct key key_table[] = {
1570 { "Enter", KEY_RETURN },
1571 { "Space", ' ' },
1572 { "Backspace", KEY_BACKSPACE },
1573 { "Tab", KEY_TAB },
1574 { "Escape", KEY_ESC },
1575 { "Left", KEY_LEFT },
1576 { "Right", KEY_RIGHT },
1577 { "Up", KEY_UP },
1578 { "Down", KEY_DOWN },
1579 { "Insert", KEY_IC },
1580 { "Delete", KEY_DC },
1581 { "Hash", '#' },
1582 { "Home", KEY_HOME },
1583 { "End", KEY_END },
1584 { "PageUp", KEY_PPAGE },
1585 { "PageDown", KEY_NPAGE },
1586 { "F1", KEY_F(1) },
1587 { "F2", KEY_F(2) },
1588 { "F3", KEY_F(3) },
1589 { "F4", KEY_F(4) },
1590 { "F5", KEY_F(5) },
1591 { "F6", KEY_F(6) },
1592 { "F7", KEY_F(7) },
1593 { "F8", KEY_F(8) },
1594 { "F9", KEY_F(9) },
1595 { "F10", KEY_F(10) },
1596 { "F11", KEY_F(11) },
1597 { "F12", KEY_F(12) },
1600 static int
1601 get_key_value(const char *name)
1603 int i;
1605 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1606 if (!strcasecmp(key_table[i].name, name))
1607 return key_table[i].value;
1609 if (strlen(name) == 1 && isprint(*name))
1610 return (int) *name;
1612 return ERR;
1615 static const char *
1616 get_key_name(int key_value)
1618 static char key_char[] = "'X'";
1619 const char *seq = NULL;
1620 int key;
1622 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1623 if (key_table[key].value == key_value)
1624 seq = key_table[key].name;
1626 if (seq == NULL &&
1627 key_value < 127 &&
1628 isprint(key_value)) {
1629 key_char[1] = (char) key_value;
1630 seq = key_char;
1633 return seq ? seq : "(no key)";
1636 static bool
1637 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1639 const char *sep = *pos > 0 ? ", " : "";
1640 const char *keyname = get_key_name(keybinding->alias);
1642 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1645 static bool
1646 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1647 enum keymap keymap, bool all)
1649 int i;
1651 for (i = 0; i < keybindings[keymap].size; i++) {
1652 if (keybindings[keymap].data[i].request == request) {
1653 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1654 return FALSE;
1655 if (!all)
1656 break;
1660 return TRUE;
1663 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1665 static const char *
1666 get_keys(enum keymap keymap, enum request request, bool all)
1668 static char buf[BUFSIZ];
1669 size_t pos = 0;
1670 int i;
1672 buf[pos] = 0;
1674 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1675 return "Too many keybindings!";
1676 if (pos > 0 && !all)
1677 return buf;
1679 if (keymap != KEYMAP_GENERIC) {
1680 /* Only the generic keymap includes the default keybindings when
1681 * listing all keys. */
1682 if (all)
1683 return buf;
1685 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1686 return "Too many keybindings!";
1687 if (pos)
1688 return buf;
1691 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1692 if (default_keybindings[i].request == request) {
1693 if (!append_key(buf, &pos, &default_keybindings[i]))
1694 return "Too many keybindings!";
1695 if (!all)
1696 return buf;
1700 return buf;
1703 struct run_request {
1704 enum keymap keymap;
1705 int key;
1706 const char *argv[SIZEOF_ARG];
1709 static struct run_request *run_request;
1710 static size_t run_requests;
1712 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1714 static enum request
1715 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1717 struct run_request *req;
1719 if (argc >= ARRAY_SIZE(req->argv) - 1)
1720 return REQ_NONE;
1722 if (!realloc_run_requests(&run_request, run_requests, 1))
1723 return REQ_NONE;
1725 req = &run_request[run_requests];
1726 req->keymap = keymap;
1727 req->key = key;
1728 req->argv[0] = NULL;
1730 if (!format_argv(req->argv, argv, FORMAT_NONE))
1731 return REQ_NONE;
1733 return REQ_NONE + ++run_requests;
1736 static struct run_request *
1737 get_run_request(enum request request)
1739 if (request <= REQ_NONE)
1740 return NULL;
1741 return &run_request[request - REQ_NONE - 1];
1744 static void
1745 add_builtin_run_requests(void)
1747 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1748 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1749 const char *commit[] = { "git", "commit", NULL };
1750 const char *gc[] = { "git", "gc", NULL };
1751 struct {
1752 enum keymap keymap;
1753 int key;
1754 int argc;
1755 const char **argv;
1756 } reqs[] = {
1757 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1758 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1759 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1760 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1762 int i;
1764 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1765 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1767 if (req != reqs[i].key)
1768 continue;
1769 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1770 if (req != REQ_NONE)
1771 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1776 * User config file handling.
1779 static int config_lineno;
1780 static bool config_errors;
1781 static const char *config_msg;
1783 static const struct enum_map color_map[] = {
1784 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1785 COLOR_MAP(DEFAULT),
1786 COLOR_MAP(BLACK),
1787 COLOR_MAP(BLUE),
1788 COLOR_MAP(CYAN),
1789 COLOR_MAP(GREEN),
1790 COLOR_MAP(MAGENTA),
1791 COLOR_MAP(RED),
1792 COLOR_MAP(WHITE),
1793 COLOR_MAP(YELLOW),
1796 static const struct enum_map attr_map[] = {
1797 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1798 ATTR_MAP(NORMAL),
1799 ATTR_MAP(BLINK),
1800 ATTR_MAP(BOLD),
1801 ATTR_MAP(DIM),
1802 ATTR_MAP(REVERSE),
1803 ATTR_MAP(STANDOUT),
1804 ATTR_MAP(UNDERLINE),
1807 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1809 static int parse_step(double *opt, const char *arg)
1811 *opt = atoi(arg);
1812 if (!strchr(arg, '%'))
1813 return OK;
1815 /* "Shift down" so 100% and 1 does not conflict. */
1816 *opt = (*opt - 1) / 100;
1817 if (*opt >= 1.0) {
1818 *opt = 0.99;
1819 config_msg = "Step value larger than 100%";
1820 return ERR;
1822 if (*opt < 0.0) {
1823 *opt = 1;
1824 config_msg = "Invalid step value";
1825 return ERR;
1827 return OK;
1830 static int
1831 parse_int(int *opt, const char *arg, int min, int max)
1833 int value = atoi(arg);
1835 if (min <= value && value <= max) {
1836 *opt = value;
1837 return OK;
1840 config_msg = "Integer value out of bound";
1841 return ERR;
1844 static bool
1845 set_color(int *color, const char *name)
1847 if (map_enum(color, color_map, name))
1848 return TRUE;
1849 if (!prefixcmp(name, "color"))
1850 return parse_int(color, name + 5, 0, 255) == OK;
1851 return FALSE;
1854 /* Wants: object fgcolor bgcolor [attribute] */
1855 static int
1856 option_color_command(int argc, const char *argv[])
1858 struct line_info *info;
1860 if (argc < 3) {
1861 config_msg = "Wrong number of arguments given to color command";
1862 return ERR;
1865 info = get_line_info(argv[0]);
1866 if (!info) {
1867 static const struct enum_map obsolete[] = {
1868 ENUM_MAP("main-delim", LINE_DELIMITER),
1869 ENUM_MAP("main-date", LINE_DATE),
1870 ENUM_MAP("main-author", LINE_AUTHOR),
1872 int index;
1874 if (!map_enum(&index, obsolete, argv[0])) {
1875 config_msg = "Unknown color name";
1876 return ERR;
1878 info = &line_info[index];
1881 if (!set_color(&info->fg, argv[1]) ||
1882 !set_color(&info->bg, argv[2])) {
1883 config_msg = "Unknown color";
1884 return ERR;
1887 info->attr = 0;
1888 while (argc-- > 3) {
1889 int attr;
1891 if (!set_attribute(&attr, argv[argc])) {
1892 config_msg = "Unknown attribute";
1893 return ERR;
1895 info->attr |= attr;
1898 return OK;
1901 static int parse_bool(bool *opt, const char *arg)
1903 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1904 ? TRUE : FALSE;
1905 return OK;
1908 static int parse_enum_do(unsigned int *opt, const char *arg,
1909 const struct enum_map *map, size_t map_size)
1911 bool is_true;
1913 assert(map_size > 1);
1915 if (map_enum_do(map, map_size, (int *) opt, arg))
1916 return OK;
1918 if (parse_bool(&is_true, arg) != OK)
1919 return ERR;
1921 *opt = is_true ? map[1].value : map[0].value;
1922 return OK;
1925 #define parse_enum(opt, arg, map) \
1926 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1928 static int
1929 parse_string(char *opt, const char *arg, size_t optsize)
1931 int arglen = strlen(arg);
1933 switch (arg[0]) {
1934 case '\"':
1935 case '\'':
1936 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1937 config_msg = "Unmatched quotation";
1938 return ERR;
1940 arg += 1; arglen -= 2;
1941 default:
1942 string_ncopy_do(opt, optsize, arg, arglen);
1943 return OK;
1947 /* Wants: name = value */
1948 static int
1949 option_set_command(int argc, const char *argv[])
1951 if (argc != 3) {
1952 config_msg = "Wrong number of arguments given to set command";
1953 return ERR;
1956 if (strcmp(argv[1], "=")) {
1957 config_msg = "No value assigned";
1958 return ERR;
1961 if (!strcmp(argv[0], "show-author"))
1962 return parse_enum(&opt_author, argv[2], author_map);
1964 if (!strcmp(argv[0], "show-date"))
1965 return parse_enum(&opt_date, argv[2], date_map);
1967 if (!strcmp(argv[0], "show-rev-graph"))
1968 return parse_bool(&opt_rev_graph, argv[2]);
1970 if (!strcmp(argv[0], "show-refs"))
1971 return parse_bool(&opt_show_refs, argv[2]);
1973 if (!strcmp(argv[0], "show-line-numbers"))
1974 return parse_bool(&opt_line_number, argv[2]);
1976 if (!strcmp(argv[0], "line-graphics"))
1977 return parse_bool(&opt_line_graphics, argv[2]);
1979 if (!strcmp(argv[0], "line-number-interval"))
1980 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1982 if (!strcmp(argv[0], "author-width"))
1983 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1985 if (!strcmp(argv[0], "horizontal-scroll"))
1986 return parse_step(&opt_hscroll, argv[2]);
1988 if (!strcmp(argv[0], "split-view-height"))
1989 return parse_step(&opt_scale_split_view, argv[2]);
1991 if (!strcmp(argv[0], "tab-size"))
1992 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1994 if (!strcmp(argv[0], "commit-encoding"))
1995 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1997 config_msg = "Unknown variable name";
1998 return ERR;
2001 /* Wants: mode request key */
2002 static int
2003 option_bind_command(int argc, const char *argv[])
2005 enum request request;
2006 int keymap = -1;
2007 int key;
2009 if (argc < 3) {
2010 config_msg = "Wrong number of arguments given to bind command";
2011 return ERR;
2014 if (!set_keymap(&keymap, argv[0])) {
2015 config_msg = "Unknown key map";
2016 return ERR;
2019 key = get_key_value(argv[1]);
2020 if (key == ERR) {
2021 config_msg = "Unknown key";
2022 return ERR;
2025 request = get_request(argv[2]);
2026 if (request == REQ_UNKNOWN) {
2027 static const struct enum_map obsolete[] = {
2028 ENUM_MAP("cherry-pick", REQ_NONE),
2029 ENUM_MAP("screen-resize", REQ_NONE),
2030 ENUM_MAP("tree-parent", REQ_PARENT),
2032 int alias;
2034 if (map_enum(&alias, obsolete, argv[2])) {
2035 if (alias != REQ_NONE)
2036 add_keybinding(keymap, alias, key);
2037 config_msg = "Obsolete request name";
2038 return ERR;
2041 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2042 request = add_run_request(keymap, key, argc - 2, argv + 2);
2043 if (request == REQ_UNKNOWN) {
2044 config_msg = "Unknown request name";
2045 return ERR;
2048 add_keybinding(keymap, request, key);
2050 return OK;
2053 static int
2054 set_option(const char *opt, char *value)
2056 const char *argv[SIZEOF_ARG];
2057 int argc = 0;
2059 if (!argv_from_string(argv, &argc, value)) {
2060 config_msg = "Too many option arguments";
2061 return ERR;
2064 if (!strcmp(opt, "color"))
2065 return option_color_command(argc, argv);
2067 if (!strcmp(opt, "set"))
2068 return option_set_command(argc, argv);
2070 if (!strcmp(opt, "bind"))
2071 return option_bind_command(argc, argv);
2073 config_msg = "Unknown option command";
2074 return ERR;
2077 static int
2078 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2080 int status = OK;
2082 config_lineno++;
2083 config_msg = "Internal error";
2085 /* Check for comment markers, since read_properties() will
2086 * only ensure opt and value are split at first " \t". */
2087 optlen = strcspn(opt, "#");
2088 if (optlen == 0)
2089 return OK;
2091 if (opt[optlen] != 0) {
2092 config_msg = "No option value";
2093 status = ERR;
2095 } else {
2096 /* Look for comment endings in the value. */
2097 size_t len = strcspn(value, "#");
2099 if (len < valuelen) {
2100 valuelen = len;
2101 value[valuelen] = 0;
2104 status = set_option(opt, value);
2107 if (status == ERR) {
2108 warn("Error on line %d, near '%.*s': %s",
2109 config_lineno, (int) optlen, opt, config_msg);
2110 config_errors = TRUE;
2113 /* Always keep going if errors are encountered. */
2114 return OK;
2117 static void
2118 load_option_file(const char *path)
2120 struct io io = {};
2122 /* It's OK that the file doesn't exist. */
2123 if (!io_open(&io, "%s", path))
2124 return;
2126 config_lineno = 0;
2127 config_errors = FALSE;
2129 if (io_load(&io, " \t", read_option) == ERR ||
2130 config_errors == TRUE)
2131 warn("Errors while loading %s.", path);
2134 static int
2135 load_options(void)
2137 const char *home = getenv("HOME");
2138 const char *tigrc_user = getenv("TIGRC_USER");
2139 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2140 char buf[SIZEOF_STR];
2142 if (!tigrc_system)
2143 tigrc_system = SYSCONFDIR "/tigrc";
2144 load_option_file(tigrc_system);
2146 if (!tigrc_user) {
2147 if (!home || !string_format(buf, "%s/.tigrc", home))
2148 return ERR;
2149 tigrc_user = buf;
2151 load_option_file(tigrc_user);
2153 /* Add _after_ loading config files to avoid adding run requests
2154 * that conflict with keybindings. */
2155 add_builtin_run_requests();
2157 return OK;
2162 * The viewer
2165 struct view;
2166 struct view_ops;
2168 /* The display array of active views and the index of the current view. */
2169 static struct view *display[2];
2170 static unsigned int current_view;
2172 #define foreach_displayed_view(view, i) \
2173 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2175 #define displayed_views() (display[1] != NULL ? 2 : 1)
2177 /* Current head and commit ID */
2178 static char ref_blob[SIZEOF_REF] = "";
2179 static char ref_commit[SIZEOF_REF] = "HEAD";
2180 static char ref_head[SIZEOF_REF] = "HEAD";
2181 static char ref_branch[SIZEOF_REF] = "";
2183 enum view_type {
2184 VIEW_MAIN,
2185 VIEW_DIFF,
2186 VIEW_LOG,
2187 VIEW_TREE,
2188 VIEW_BLOB,
2189 VIEW_BLAME,
2190 VIEW_BRANCH,
2191 VIEW_HELP,
2192 VIEW_PAGER,
2193 VIEW_STATUS,
2194 VIEW_STAGE,
2197 struct view {
2198 enum view_type type; /* View type */
2199 const char *name; /* View name */
2200 const char *cmd_env; /* Command line set via environment */
2201 const char *id; /* Points to either of ref_{head,commit,blob} */
2203 struct view_ops *ops; /* View operations */
2205 enum keymap keymap; /* What keymap does this view have */
2206 bool git_dir; /* Whether the view requires a git directory. */
2207 bool refresh; /* Whether the view supports refreshing. */
2209 char ref[SIZEOF_REF]; /* Hovered commit reference */
2210 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2212 int height, width; /* The width and height of the main window */
2213 WINDOW *win; /* The main window */
2214 WINDOW *title; /* The title window living below the main window */
2216 /* Navigation */
2217 unsigned long offset; /* Offset of the window top */
2218 unsigned long yoffset; /* Offset from the window side. */
2219 unsigned long lineno; /* Current line number */
2220 unsigned long p_offset; /* Previous offset of the window top */
2221 unsigned long p_yoffset;/* Previous offset from the window side */
2222 unsigned long p_lineno; /* Previous current line number */
2223 bool p_restore; /* Should the previous position be restored. */
2225 /* Searching */
2226 char grep[SIZEOF_STR]; /* Search string */
2227 regex_t *regex; /* Pre-compiled regexp */
2229 /* If non-NULL, points to the view that opened this view. If this view
2230 * is closed tig will switch back to the parent view. */
2231 struct view *parent;
2233 /* Buffering */
2234 size_t lines; /* Total number of lines */
2235 struct line *line; /* Line index */
2236 unsigned int digits; /* Number of digits in the lines member. */
2238 /* Drawing */
2239 struct line *curline; /* Line currently being drawn. */
2240 enum line_type curtype; /* Attribute currently used for drawing. */
2241 unsigned long col; /* Column when drawing. */
2242 bool has_scrolled; /* View was scrolled. */
2244 /* Loading */
2245 struct io io;
2246 struct io *pipe;
2247 time_t start_time;
2248 time_t update_secs;
2251 struct view_ops {
2252 /* What type of content being displayed. Used in the title bar. */
2253 const char *type;
2254 /* Default command arguments. */
2255 const char **argv;
2256 /* Open and reads in all view content. */
2257 bool (*open)(struct view *view);
2258 /* Read one line; updates view->line. */
2259 bool (*read)(struct view *view, char *data);
2260 /* Draw one line; @lineno must be < view->height. */
2261 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2262 /* Depending on view handle a special requests. */
2263 enum request (*request)(struct view *view, enum request request, struct line *line);
2264 /* Search for regexp in a line. */
2265 bool (*grep)(struct view *view, struct line *line);
2266 /* Select line */
2267 void (*select)(struct view *view, struct line *line);
2268 /* Prepare view for loading */
2269 bool (*prepare)(struct view *view);
2272 static struct view_ops blame_ops;
2273 static struct view_ops blob_ops;
2274 static struct view_ops diff_ops;
2275 static struct view_ops help_ops;
2276 static struct view_ops log_ops;
2277 static struct view_ops main_ops;
2278 static struct view_ops pager_ops;
2279 static struct view_ops stage_ops;
2280 static struct view_ops status_ops;
2281 static struct view_ops tree_ops;
2282 static struct view_ops branch_ops;
2284 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2285 { type, name, #env, ref, ops, map, git, refresh }
2287 #define VIEW_(id, name, ops, git, refresh, ref) \
2288 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2290 static struct view views[] = {
2291 VIEW_(MAIN, "main", &main_ops, TRUE, TRUE, ref_head),
2292 VIEW_(DIFF, "diff", &diff_ops, TRUE, FALSE, ref_commit),
2293 VIEW_(LOG, "log", &log_ops, TRUE, TRUE, ref_head),
2294 VIEW_(TREE, "tree", &tree_ops, TRUE, FALSE, ref_commit),
2295 VIEW_(BLOB, "blob", &blob_ops, TRUE, FALSE, ref_blob),
2296 VIEW_(BLAME, "blame", &blame_ops, TRUE, FALSE, ref_commit),
2297 VIEW_(BRANCH, "branch", &branch_ops, TRUE, TRUE, ref_head),
2298 VIEW_(HELP, "help", &help_ops, FALSE, FALSE, ""),
2299 VIEW_(PAGER, "pager", &pager_ops, FALSE, FALSE, "stdin"),
2300 VIEW_(STATUS, "status", &status_ops, TRUE, TRUE, ""),
2301 VIEW_(STAGE, "stage", &stage_ops, TRUE, TRUE, ""),
2304 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2306 #define foreach_view(view, i) \
2307 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2309 #define view_is_displayed(view) \
2310 (view == display[0] || view == display[1])
2312 #define view_has_parent(view, child_type, parent_type) \
2313 (view->type == child_type && view->parent && view->parent->type == parent_type)
2315 static inline void
2316 set_view_attr(struct view *view, enum line_type type)
2318 if (!view->curline->selected && view->curtype != type) {
2319 (void) wattrset(view->win, get_line_attr(type));
2320 wchgat(view->win, -1, 0, type, NULL);
2321 view->curtype = type;
2325 static int
2326 draw_chars(struct view *view, enum line_type type, const char *string,
2327 int max_len, bool use_tilde)
2329 static char out_buffer[BUFSIZ * 2];
2330 int len = 0;
2331 int col = 0;
2332 int trimmed = FALSE;
2333 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2335 if (max_len <= 0)
2336 return 0;
2338 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2340 set_view_attr(view, type);
2341 if (len > 0) {
2342 if (opt_iconv_out != ICONV_NONE) {
2343 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2344 size_t inlen = len + 1;
2346 char *outbuf = out_buffer;
2347 size_t outlen = sizeof(out_buffer);
2349 size_t ret;
2351 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2352 if (ret != (size_t) -1) {
2353 string = out_buffer;
2354 len = sizeof(out_buffer) - outlen;
2358 waddnstr(view->win, string, len);
2360 if (trimmed && use_tilde) {
2361 set_view_attr(view, LINE_DELIMITER);
2362 waddch(view->win, '~');
2363 col++;
2366 return col;
2369 static int
2370 draw_space(struct view *view, enum line_type type, int max, int spaces)
2372 static char space[] = " ";
2373 int col = 0;
2375 spaces = MIN(max, spaces);
2377 while (spaces > 0) {
2378 int len = MIN(spaces, sizeof(space) - 1);
2380 col += draw_chars(view, type, space, len, FALSE);
2381 spaces -= len;
2384 return col;
2387 static bool
2388 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2390 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2391 return view->width + view->yoffset <= view->col;
2394 static bool
2395 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2397 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2398 int max = view->width + view->yoffset - view->col;
2399 int i;
2401 if (max < size)
2402 size = max;
2404 set_view_attr(view, type);
2405 /* Using waddch() instead of waddnstr() ensures that
2406 * they'll be rendered correctly for the cursor line. */
2407 for (i = skip; i < size; i++)
2408 waddch(view->win, graphic[i]);
2410 view->col += size;
2411 if (size < max && skip <= size)
2412 waddch(view->win, ' ');
2413 view->col++;
2415 return view->width + view->yoffset <= view->col;
2418 static bool
2419 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2421 int max = MIN(view->width + view->yoffset - view->col, len);
2422 int col;
2424 if (text)
2425 col = draw_chars(view, type, text, max - 1, trim);
2426 else
2427 col = draw_space(view, type, max - 1, max - 1);
2429 view->col += col;
2430 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2431 return view->width + view->yoffset <= view->col;
2434 static bool
2435 draw_date(struct view *view, struct time *time)
2437 const char *date = mkdate(time, opt_date);
2438 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2440 return draw_field(view, LINE_DATE, date, cols, FALSE);
2443 static bool
2444 draw_author(struct view *view, const char *author)
2446 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2447 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2449 if (abbreviate && author)
2450 author = get_author_initials(author);
2452 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2455 static bool
2456 draw_mode(struct view *view, mode_t mode)
2458 const char *str;
2460 if (S_ISDIR(mode))
2461 str = "drwxr-xr-x";
2462 else if (S_ISLNK(mode))
2463 str = "lrwxrwxrwx";
2464 else if (S_ISGITLINK(mode))
2465 str = "m---------";
2466 else if (S_ISREG(mode) && mode & S_IXUSR)
2467 str = "-rwxr-xr-x";
2468 else if (S_ISREG(mode))
2469 str = "-rw-r--r--";
2470 else
2471 str = "----------";
2473 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2476 static bool
2477 draw_lineno(struct view *view, unsigned int lineno)
2479 char number[10];
2480 int digits3 = view->digits < 3 ? 3 : view->digits;
2481 int max = MIN(view->width + view->yoffset - view->col, digits3);
2482 char *text = NULL;
2483 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2485 lineno += view->offset + 1;
2486 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2487 static char fmt[] = "%1ld";
2489 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2490 if (string_format(number, fmt, lineno))
2491 text = number;
2493 if (text)
2494 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2495 else
2496 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2497 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2500 static bool
2501 draw_view_line(struct view *view, unsigned int lineno)
2503 struct line *line;
2504 bool selected = (view->offset + lineno == view->lineno);
2506 assert(view_is_displayed(view));
2508 if (view->offset + lineno >= view->lines)
2509 return FALSE;
2511 line = &view->line[view->offset + lineno];
2513 wmove(view->win, lineno, 0);
2514 if (line->cleareol)
2515 wclrtoeol(view->win);
2516 view->col = 0;
2517 view->curline = line;
2518 view->curtype = LINE_NONE;
2519 line->selected = FALSE;
2520 line->dirty = line->cleareol = 0;
2522 if (selected) {
2523 set_view_attr(view, LINE_CURSOR);
2524 line->selected = TRUE;
2525 view->ops->select(view, line);
2528 return view->ops->draw(view, line, lineno);
2531 static void
2532 redraw_view_dirty(struct view *view)
2534 bool dirty = FALSE;
2535 int lineno;
2537 for (lineno = 0; lineno < view->height; lineno++) {
2538 if (view->offset + lineno >= view->lines)
2539 break;
2540 if (!view->line[view->offset + lineno].dirty)
2541 continue;
2542 dirty = TRUE;
2543 if (!draw_view_line(view, lineno))
2544 break;
2547 if (!dirty)
2548 return;
2549 wnoutrefresh(view->win);
2552 static void
2553 redraw_view_from(struct view *view, int lineno)
2555 assert(0 <= lineno && lineno < view->height);
2557 for (; lineno < view->height; lineno++) {
2558 if (!draw_view_line(view, lineno))
2559 break;
2562 wnoutrefresh(view->win);
2565 static void
2566 redraw_view(struct view *view)
2568 werase(view->win);
2569 redraw_view_from(view, 0);
2573 static void
2574 update_view_title(struct view *view)
2576 char buf[SIZEOF_STR];
2577 char state[SIZEOF_STR];
2578 size_t bufpos = 0, statelen = 0;
2580 assert(view_is_displayed(view));
2582 if (view->type != VIEW_STATUS && view->lines) {
2583 unsigned int view_lines = view->offset + view->height;
2584 unsigned int lines = view->lines
2585 ? MIN(view_lines, view->lines) * 100 / view->lines
2586 : 0;
2588 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2589 view->ops->type,
2590 view->lineno + 1,
2591 view->lines,
2592 lines);
2596 if (view->pipe) {
2597 time_t secs = time(NULL) - view->start_time;
2599 /* Three git seconds are a long time ... */
2600 if (secs > 2)
2601 string_format_from(state, &statelen, " loading %lds", secs);
2604 string_format_from(buf, &bufpos, "[%s]", view->name);
2605 if (*view->ref && bufpos < view->width) {
2606 size_t refsize = strlen(view->ref);
2607 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2609 if (minsize < view->width)
2610 refsize = view->width - minsize + 7;
2611 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2614 if (statelen && bufpos < view->width) {
2615 string_format_from(buf, &bufpos, "%s", state);
2618 if (view == display[current_view])
2619 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2620 else
2621 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2623 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2624 wclrtoeol(view->title);
2625 wnoutrefresh(view->title);
2628 static int
2629 apply_step(double step, int value)
2631 if (step >= 1)
2632 return (int) step;
2633 value *= step + 0.01;
2634 return value ? value : 1;
2637 static void
2638 resize_display(void)
2640 int offset, i;
2641 struct view *base = display[0];
2642 struct view *view = display[1] ? display[1] : display[0];
2644 /* Setup window dimensions */
2646 getmaxyx(stdscr, base->height, base->width);
2648 /* Make room for the status window. */
2649 base->height -= 1;
2651 if (view != base) {
2652 /* Horizontal split. */
2653 view->width = base->width;
2654 view->height = apply_step(opt_scale_split_view, base->height);
2655 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2656 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2657 base->height -= view->height;
2659 /* Make room for the title bar. */
2660 view->height -= 1;
2663 /* Make room for the title bar. */
2664 base->height -= 1;
2666 offset = 0;
2668 foreach_displayed_view (view, i) {
2669 if (!view->win) {
2670 view->win = newwin(view->height, 0, offset, 0);
2671 if (!view->win)
2672 die("Failed to create %s view", view->name);
2674 scrollok(view->win, FALSE);
2676 view->title = newwin(1, 0, offset + view->height, 0);
2677 if (!view->title)
2678 die("Failed to create title window");
2680 } else {
2681 wresize(view->win, view->height, view->width);
2682 mvwin(view->win, offset, 0);
2683 mvwin(view->title, offset + view->height, 0);
2686 offset += view->height + 1;
2690 static void
2691 redraw_display(bool clear)
2693 struct view *view;
2694 int i;
2696 foreach_displayed_view (view, i) {
2697 if (clear)
2698 wclear(view->win);
2699 redraw_view(view);
2700 update_view_title(view);
2704 static void
2705 toggle_enum_option_do(unsigned int *opt, const char *help,
2706 const struct enum_map *map, size_t size)
2708 *opt = (*opt + 1) % size;
2709 redraw_display(FALSE);
2710 report("Displaying %s %s", enum_name(map[*opt]), help);
2713 #define toggle_enum_option(opt, help, map) \
2714 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2716 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2717 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2719 static void
2720 toggle_view_option(bool *option, const char *help)
2722 *option = !*option;
2723 redraw_display(FALSE);
2724 report("%sabling %s", *option ? "En" : "Dis", help);
2727 static void
2728 open_option_menu(void)
2730 const struct menu_item menu[] = {
2731 { '.', "line numbers", &opt_line_number },
2732 { 'D', "date display", &opt_date },
2733 { 'A', "author display", &opt_author },
2734 { 'g', "revision graph display", &opt_rev_graph },
2735 { 'F', "reference display", &opt_show_refs },
2736 { 0 }
2738 int selected = 0;
2740 if (prompt_menu("Toggle option", menu, &selected)) {
2741 if (menu[selected].data == &opt_date)
2742 toggle_date();
2743 else if (menu[selected].data == &opt_author)
2744 toggle_author();
2745 else
2746 toggle_view_option(menu[selected].data, menu[selected].text);
2750 static void
2751 maximize_view(struct view *view)
2753 memset(display, 0, sizeof(display));
2754 current_view = 0;
2755 display[current_view] = view;
2756 resize_display();
2757 redraw_display(FALSE);
2758 report("");
2763 * Navigation
2766 static bool
2767 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2769 if (lineno >= view->lines)
2770 lineno = view->lines > 0 ? view->lines - 1 : 0;
2772 if (offset > lineno || offset + view->height <= lineno) {
2773 unsigned long half = view->height / 2;
2775 if (lineno > half)
2776 offset = lineno - half;
2777 else
2778 offset = 0;
2781 if (offset != view->offset || lineno != view->lineno) {
2782 view->offset = offset;
2783 view->lineno = lineno;
2784 return TRUE;
2787 return FALSE;
2790 /* Scrolling backend */
2791 static void
2792 do_scroll_view(struct view *view, int lines)
2794 bool redraw_current_line = FALSE;
2796 /* The rendering expects the new offset. */
2797 view->offset += lines;
2799 assert(0 <= view->offset && view->offset < view->lines);
2800 assert(lines);
2802 /* Move current line into the view. */
2803 if (view->lineno < view->offset) {
2804 view->lineno = view->offset;
2805 redraw_current_line = TRUE;
2806 } else if (view->lineno >= view->offset + view->height) {
2807 view->lineno = view->offset + view->height - 1;
2808 redraw_current_line = TRUE;
2811 assert(view->offset <= view->lineno && view->lineno < view->lines);
2813 /* Redraw the whole screen if scrolling is pointless. */
2814 if (view->height < ABS(lines)) {
2815 redraw_view(view);
2817 } else {
2818 int line = lines > 0 ? view->height - lines : 0;
2819 int end = line + ABS(lines);
2821 scrollok(view->win, TRUE);
2822 wscrl(view->win, lines);
2823 scrollok(view->win, FALSE);
2825 while (line < end && draw_view_line(view, line))
2826 line++;
2828 if (redraw_current_line)
2829 draw_view_line(view, view->lineno - view->offset);
2830 wnoutrefresh(view->win);
2833 view->has_scrolled = TRUE;
2834 report("");
2837 /* Scroll frontend */
2838 static void
2839 scroll_view(struct view *view, enum request request)
2841 int lines = 1;
2843 assert(view_is_displayed(view));
2845 switch (request) {
2846 case REQ_SCROLL_LEFT:
2847 if (view->yoffset == 0) {
2848 report("Cannot scroll beyond the first column");
2849 return;
2851 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2852 view->yoffset = 0;
2853 else
2854 view->yoffset -= apply_step(opt_hscroll, view->width);
2855 redraw_view_from(view, 0);
2856 report("");
2857 return;
2858 case REQ_SCROLL_RIGHT:
2859 view->yoffset += apply_step(opt_hscroll, view->width);
2860 redraw_view(view);
2861 report("");
2862 return;
2863 case REQ_SCROLL_PAGE_DOWN:
2864 lines = view->height;
2865 case REQ_SCROLL_LINE_DOWN:
2866 if (view->offset + lines > view->lines)
2867 lines = view->lines - view->offset;
2869 if (lines == 0 || view->offset + view->height >= view->lines) {
2870 report("Cannot scroll beyond the last line");
2871 return;
2873 break;
2875 case REQ_SCROLL_PAGE_UP:
2876 lines = view->height;
2877 case REQ_SCROLL_LINE_UP:
2878 if (lines > view->offset)
2879 lines = view->offset;
2881 if (lines == 0) {
2882 report("Cannot scroll beyond the first line");
2883 return;
2886 lines = -lines;
2887 break;
2889 default:
2890 die("request %d not handled in switch", request);
2893 do_scroll_view(view, lines);
2896 /* Cursor moving */
2897 static void
2898 move_view(struct view *view, enum request request)
2900 int scroll_steps = 0;
2901 int steps;
2903 switch (request) {
2904 case REQ_MOVE_FIRST_LINE:
2905 steps = -view->lineno;
2906 break;
2908 case REQ_MOVE_LAST_LINE:
2909 steps = view->lines - view->lineno - 1;
2910 break;
2912 case REQ_MOVE_PAGE_UP:
2913 steps = view->height > view->lineno
2914 ? -view->lineno : -view->height;
2915 break;
2917 case REQ_MOVE_PAGE_DOWN:
2918 steps = view->lineno + view->height >= view->lines
2919 ? view->lines - view->lineno - 1 : view->height;
2920 break;
2922 case REQ_MOVE_UP:
2923 steps = -1;
2924 break;
2926 case REQ_MOVE_DOWN:
2927 steps = 1;
2928 break;
2930 default:
2931 die("request %d not handled in switch", request);
2934 if (steps <= 0 && view->lineno == 0) {
2935 report("Cannot move beyond the first line");
2936 return;
2938 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2939 report("Cannot move beyond the last line");
2940 return;
2943 /* Move the current line */
2944 view->lineno += steps;
2945 assert(0 <= view->lineno && view->lineno < view->lines);
2947 /* Check whether the view needs to be scrolled */
2948 if (view->lineno < view->offset ||
2949 view->lineno >= view->offset + view->height) {
2950 scroll_steps = steps;
2951 if (steps < 0 && -steps > view->offset) {
2952 scroll_steps = -view->offset;
2954 } else if (steps > 0) {
2955 if (view->lineno == view->lines - 1 &&
2956 view->lines > view->height) {
2957 scroll_steps = view->lines - view->offset - 1;
2958 if (scroll_steps >= view->height)
2959 scroll_steps -= view->height - 1;
2964 if (!view_is_displayed(view)) {
2965 view->offset += scroll_steps;
2966 assert(0 <= view->offset && view->offset < view->lines);
2967 view->ops->select(view, &view->line[view->lineno]);
2968 return;
2971 /* Repaint the old "current" line if we be scrolling */
2972 if (ABS(steps) < view->height)
2973 draw_view_line(view, view->lineno - steps - view->offset);
2975 if (scroll_steps) {
2976 do_scroll_view(view, scroll_steps);
2977 return;
2980 /* Draw the current line */
2981 draw_view_line(view, view->lineno - view->offset);
2983 wnoutrefresh(view->win);
2984 report("");
2989 * Searching
2992 static void search_view(struct view *view, enum request request);
2994 static bool
2995 grep_text(struct view *view, const char *text[])
2997 regmatch_t pmatch;
2998 size_t i;
3000 for (i = 0; text[i]; i++)
3001 if (*text[i] &&
3002 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3003 return TRUE;
3004 return FALSE;
3007 static void
3008 select_view_line(struct view *view, unsigned long lineno)
3010 unsigned long old_lineno = view->lineno;
3011 unsigned long old_offset = view->offset;
3013 if (goto_view_line(view, view->offset, lineno)) {
3014 if (view_is_displayed(view)) {
3015 if (old_offset != view->offset) {
3016 redraw_view(view);
3017 } else {
3018 draw_view_line(view, old_lineno - view->offset);
3019 draw_view_line(view, view->lineno - view->offset);
3020 wnoutrefresh(view->win);
3022 } else {
3023 view->ops->select(view, &view->line[view->lineno]);
3028 static void
3029 find_next(struct view *view, enum request request)
3031 unsigned long lineno = view->lineno;
3032 int direction;
3034 if (!*view->grep) {
3035 if (!*opt_search)
3036 report("No previous search");
3037 else
3038 search_view(view, request);
3039 return;
3042 switch (request) {
3043 case REQ_SEARCH:
3044 case REQ_FIND_NEXT:
3045 direction = 1;
3046 break;
3048 case REQ_SEARCH_BACK:
3049 case REQ_FIND_PREV:
3050 direction = -1;
3051 break;
3053 default:
3054 return;
3057 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3058 lineno += direction;
3060 /* Note, lineno is unsigned long so will wrap around in which case it
3061 * will become bigger than view->lines. */
3062 for (; lineno < view->lines; lineno += direction) {
3063 if (view->ops->grep(view, &view->line[lineno])) {
3064 select_view_line(view, lineno);
3065 report("Line %ld matches '%s'", lineno + 1, view->grep);
3066 return;
3070 report("No match found for '%s'", view->grep);
3073 static void
3074 search_view(struct view *view, enum request request)
3076 int regex_err;
3078 if (view->regex) {
3079 regfree(view->regex);
3080 *view->grep = 0;
3081 } else {
3082 view->regex = calloc(1, sizeof(*view->regex));
3083 if (!view->regex)
3084 return;
3087 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3088 if (regex_err != 0) {
3089 char buf[SIZEOF_STR] = "unknown error";
3091 regerror(regex_err, view->regex, buf, sizeof(buf));
3092 report("Search failed: %s", buf);
3093 return;
3096 string_copy(view->grep, opt_search);
3098 find_next(view, request);
3102 * Incremental updating
3105 static void
3106 reset_view(struct view *view)
3108 int i;
3110 for (i = 0; i < view->lines; i++)
3111 free(view->line[i].data);
3112 free(view->line);
3114 view->p_offset = view->offset;
3115 view->p_yoffset = view->yoffset;
3116 view->p_lineno = view->lineno;
3118 view->line = NULL;
3119 view->offset = 0;
3120 view->yoffset = 0;
3121 view->lines = 0;
3122 view->lineno = 0;
3123 view->vid[0] = 0;
3124 view->update_secs = 0;
3127 static void
3128 free_argv(const char *argv[])
3130 int argc;
3132 for (argc = 0; argv[argc]; argc++)
3133 free((void *) argv[argc]);
3136 static const char *
3137 format_arg(const char *name)
3139 static struct {
3140 const char *name;
3141 size_t namelen;
3142 const char *value;
3143 const char *value_if_empty;
3144 } vars[] = {
3145 #define FORMAT_VAR(name, value, value_if_empty) \
3146 { name, STRING_SIZE(name), value, value_if_empty }
3147 FORMAT_VAR("%(directory)", opt_path, ""),
3148 FORMAT_VAR("%(file)", opt_file, ""),
3149 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3150 FORMAT_VAR("%(head)", ref_head, ""),
3151 FORMAT_VAR("%(commit)", ref_commit, ""),
3152 FORMAT_VAR("%(blob)", ref_blob, ""),
3153 FORMAT_VAR("%(branch)", ref_branch, ""),
3155 int i;
3157 for (i = 0; i < ARRAY_SIZE(vars); i++)
3158 if (!strncmp(name, vars[i].name, vars[i].namelen))
3159 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3161 report("Unknown replacement: `%s`", name);
3162 return NULL;
3165 static bool
3166 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3168 char buf[SIZEOF_STR];
3169 int argc;
3170 bool noreplace = flags == FORMAT_NONE;
3172 free_argv(dst_argv);
3174 for (argc = 0; src_argv[argc]; argc++) {
3175 const char *arg = src_argv[argc];
3176 size_t bufpos = 0;
3178 while (arg) {
3179 char *next = strstr(arg, "%(");
3180 int len = next - arg;
3181 const char *value;
3183 if (!next || noreplace) {
3184 len = strlen(arg);
3185 value = "";
3187 } else {
3188 value = format_arg(next);
3190 if (!value) {
3191 return FALSE;
3195 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3196 return FALSE;
3198 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3201 dst_argv[argc] = strdup(buf);
3202 if (!dst_argv[argc])
3203 break;
3206 dst_argv[argc] = NULL;
3208 return src_argv[argc] == NULL;
3211 static bool
3212 restore_view_position(struct view *view)
3214 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3215 return FALSE;
3217 /* Changing the view position cancels the restoring. */
3218 /* FIXME: Changing back to the first line is not detected. */
3219 if (view->offset != 0 || view->lineno != 0) {
3220 view->p_restore = FALSE;
3221 return FALSE;
3224 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3225 view_is_displayed(view))
3226 werase(view->win);
3228 view->yoffset = view->p_yoffset;
3229 view->p_restore = FALSE;
3231 return TRUE;
3234 static void
3235 end_update(struct view *view, bool force)
3237 if (!view->pipe)
3238 return;
3239 while (!view->ops->read(view, NULL))
3240 if (!force)
3241 return;
3242 if (force)
3243 io_kill(view->pipe);
3244 io_done(view->pipe);
3245 view->pipe = NULL;
3248 static void
3249 setup_update(struct view *view, const char *vid)
3251 reset_view(view);
3252 string_copy_rev(view->vid, vid);
3253 view->pipe = &view->io;
3254 view->start_time = time(NULL);
3257 static bool
3258 prepare_update(struct view *view, const char *argv[], const char *dir)
3260 if (view->pipe)
3261 end_update(view, TRUE);
3262 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3265 static bool
3266 prepare_update_file(struct view *view, const char *name)
3268 if (view->pipe)
3269 end_update(view, TRUE);
3270 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3273 static bool
3274 begin_update(struct view *view, bool refresh)
3276 if (view->pipe)
3277 end_update(view, TRUE);
3279 if (!refresh) {
3280 if (view->ops->prepare) {
3281 if (!view->ops->prepare(view))
3282 return FALSE;
3283 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3284 return FALSE;
3287 /* Put the current ref_* value to the view title ref
3288 * member. This is needed by the blob view. Most other
3289 * views sets it automatically after loading because the
3290 * first line is a commit line. */
3291 string_copy_rev(view->ref, view->id);
3294 if (!io_start(&view->io))
3295 return FALSE;
3297 setup_update(view, view->id);
3299 return TRUE;
3302 static bool
3303 update_view(struct view *view)
3305 char out_buffer[BUFSIZ * 2];
3306 char *line;
3307 /* Clear the view and redraw everything since the tree sorting
3308 * might have rearranged things. */
3309 bool redraw = view->lines == 0;
3310 bool can_read = TRUE;
3312 if (!view->pipe)
3313 return TRUE;
3315 if (!io_can_read(view->pipe)) {
3316 if (view->lines == 0 && view_is_displayed(view)) {
3317 time_t secs = time(NULL) - view->start_time;
3319 if (secs > 1 && secs > view->update_secs) {
3320 if (view->update_secs == 0)
3321 redraw_view(view);
3322 update_view_title(view);
3323 view->update_secs = secs;
3326 return TRUE;
3329 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3330 if (opt_iconv_in != ICONV_NONE) {
3331 ICONV_CONST char *inbuf = line;
3332 size_t inlen = strlen(line) + 1;
3334 char *outbuf = out_buffer;
3335 size_t outlen = sizeof(out_buffer);
3337 size_t ret;
3339 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3340 if (ret != (size_t) -1)
3341 line = out_buffer;
3344 if (!view->ops->read(view, line)) {
3345 report("Allocation failure");
3346 end_update(view, TRUE);
3347 return FALSE;
3352 unsigned long lines = view->lines;
3353 int digits;
3355 for (digits = 0; lines; digits++)
3356 lines /= 10;
3358 /* Keep the displayed view in sync with line number scaling. */
3359 if (digits != view->digits) {
3360 view->digits = digits;
3361 if (opt_line_number || view->type == VIEW_BLAME)
3362 redraw = TRUE;
3366 if (io_error(view->pipe)) {
3367 report("Failed to read: %s", io_strerror(view->pipe));
3368 end_update(view, TRUE);
3370 } else if (io_eof(view->pipe)) {
3371 report("");
3372 end_update(view, FALSE);
3375 if (restore_view_position(view))
3376 redraw = TRUE;
3378 if (!view_is_displayed(view))
3379 return TRUE;
3381 if (redraw)
3382 redraw_view_from(view, 0);
3383 else
3384 redraw_view_dirty(view);
3386 /* Update the title _after_ the redraw so that if the redraw picks up a
3387 * commit reference in view->ref it'll be available here. */
3388 update_view_title(view);
3389 return TRUE;
3392 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3394 static struct line *
3395 add_line_data(struct view *view, void *data, enum line_type type)
3397 struct line *line;
3399 if (!realloc_lines(&view->line, view->lines, 1))
3400 return NULL;
3402 line = &view->line[view->lines++];
3403 memset(line, 0, sizeof(*line));
3404 line->type = type;
3405 line->data = data;
3406 line->dirty = 1;
3408 return line;
3411 static struct line *
3412 add_line_text(struct view *view, const char *text, enum line_type type)
3414 char *data = text ? strdup(text) : NULL;
3416 return data ? add_line_data(view, data, type) : NULL;
3419 static struct line *
3420 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3422 char buf[SIZEOF_STR];
3423 va_list args;
3425 va_start(args, fmt);
3426 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3427 buf[0] = 0;
3428 va_end(args);
3430 return buf[0] ? add_line_text(view, buf, type) : NULL;
3434 * View opening
3437 enum open_flags {
3438 OPEN_DEFAULT = 0, /* Use default view switching. */
3439 OPEN_SPLIT = 1, /* Split current view. */
3440 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3441 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3442 OPEN_PREPARED = 32, /* Open already prepared command. */
3445 static void
3446 open_view(struct view *prev, enum request request, enum open_flags flags)
3448 bool split = !!(flags & OPEN_SPLIT);
3449 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3450 bool nomaximize = !!(flags & OPEN_REFRESH);
3451 struct view *view = VIEW(request);
3452 int nviews = displayed_views();
3453 struct view *base_view = display[0];
3455 if (view == prev && nviews == 1 && !reload) {
3456 report("Already in %s view", view->name);
3457 return;
3460 if (view->git_dir && !opt_git_dir[0]) {
3461 report("The %s view is disabled in pager view", view->name);
3462 return;
3465 if (split) {
3466 display[1] = view;
3467 current_view = 1;
3468 } else if (!nomaximize) {
3469 /* Maximize the current view. */
3470 memset(display, 0, sizeof(display));
3471 current_view = 0;
3472 display[current_view] = view;
3475 /* No parent signals that this is the first loaded view. */
3476 if (prev && view != prev) {
3477 view->parent = prev;
3480 /* Resize the view when switching between split- and full-screen,
3481 * or when switching between two different full-screen views. */
3482 if (nviews != displayed_views() ||
3483 (nviews == 1 && base_view != display[0]))
3484 resize_display();
3486 if (view->ops->open) {
3487 if (view->pipe)
3488 end_update(view, TRUE);
3489 if (!view->ops->open(view)) {
3490 report("Failed to load %s view", view->name);
3491 return;
3493 restore_view_position(view);
3495 } else if ((reload || strcmp(view->vid, view->id)) &&
3496 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3497 report("Failed to load %s view", view->name);
3498 return;
3501 if (split && prev->lineno - prev->offset >= prev->height) {
3502 /* Take the title line into account. */
3503 int lines = prev->lineno - prev->offset - prev->height + 1;
3505 /* Scroll the view that was split if the current line is
3506 * outside the new limited view. */
3507 do_scroll_view(prev, lines);
3510 if (prev && view != prev && split && view_is_displayed(prev)) {
3511 /* "Blur" the previous view. */
3512 update_view_title(prev);
3515 if (view->pipe && view->lines == 0) {
3516 /* Clear the old view and let the incremental updating refill
3517 * the screen. */
3518 werase(view->win);
3519 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3520 report("");
3521 } else if (view_is_displayed(view)) {
3522 redraw_view(view);
3523 report("");
3527 static void
3528 open_external_viewer(const char *argv[], const char *dir)
3530 def_prog_mode(); /* save current tty modes */
3531 endwin(); /* restore original tty modes */
3532 io_run_fg(argv, dir);
3533 fprintf(stderr, "Press Enter to continue");
3534 getc(opt_tty);
3535 reset_prog_mode();
3536 redraw_display(TRUE);
3539 static void
3540 open_mergetool(const char *file)
3542 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3544 open_external_viewer(mergetool_argv, opt_cdup);
3547 static void
3548 open_editor(const char *file)
3550 const char *editor_argv[] = { "vi", file, NULL };
3551 const char *editor;
3553 editor = getenv("GIT_EDITOR");
3554 if (!editor && *opt_editor)
3555 editor = opt_editor;
3556 if (!editor)
3557 editor = getenv("VISUAL");
3558 if (!editor)
3559 editor = getenv("EDITOR");
3560 if (!editor)
3561 editor = "vi";
3563 editor_argv[0] = editor;
3564 open_external_viewer(editor_argv, opt_cdup);
3567 static void
3568 open_run_request(enum request request)
3570 struct run_request *req = get_run_request(request);
3571 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3573 if (!req) {
3574 report("Unknown run request");
3575 return;
3578 if (format_argv(argv, req->argv, FORMAT_ALL))
3579 open_external_viewer(argv, NULL);
3580 free_argv(argv);
3584 * User request switch noodle
3587 static int
3588 view_driver(struct view *view, enum request request)
3590 int i;
3592 if (request == REQ_NONE)
3593 return TRUE;
3595 if (request > REQ_NONE) {
3596 open_run_request(request);
3597 /* FIXME: When all views can refresh always do this. */
3598 if (view->refresh)
3599 request = REQ_REFRESH;
3600 else
3601 return TRUE;
3604 if (view && view->lines) {
3605 request = view->ops->request(view, request, &view->line[view->lineno]);
3606 if (request == REQ_NONE)
3607 return TRUE;
3610 switch (request) {
3611 case REQ_MOVE_UP:
3612 case REQ_MOVE_DOWN:
3613 case REQ_MOVE_PAGE_UP:
3614 case REQ_MOVE_PAGE_DOWN:
3615 case REQ_MOVE_FIRST_LINE:
3616 case REQ_MOVE_LAST_LINE:
3617 move_view(view, request);
3618 break;
3620 case REQ_SCROLL_LEFT:
3621 case REQ_SCROLL_RIGHT:
3622 case REQ_SCROLL_LINE_DOWN:
3623 case REQ_SCROLL_LINE_UP:
3624 case REQ_SCROLL_PAGE_DOWN:
3625 case REQ_SCROLL_PAGE_UP:
3626 scroll_view(view, request);
3627 break;
3629 case REQ_VIEW_BLAME:
3630 if (!opt_file[0]) {
3631 report("No file chosen, press %s to open tree view",
3632 get_key(view->keymap, REQ_VIEW_TREE));
3633 break;
3635 open_view(view, request, OPEN_DEFAULT);
3636 break;
3638 case REQ_VIEW_BLOB:
3639 if (!ref_blob[0]) {
3640 report("No file chosen, press %s to open tree view",
3641 get_key(view->keymap, REQ_VIEW_TREE));
3642 break;
3644 open_view(view, request, OPEN_DEFAULT);
3645 break;
3647 case REQ_VIEW_PAGER:
3648 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3649 report("No pager content, press %s to run command from prompt",
3650 get_key(view->keymap, REQ_PROMPT));
3651 break;
3653 open_view(view, request, OPEN_DEFAULT);
3654 break;
3656 case REQ_VIEW_STAGE:
3657 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3658 report("No stage content, press %s to open the status view and choose file",
3659 get_key(view->keymap, REQ_VIEW_STATUS));
3660 break;
3662 open_view(view, request, OPEN_DEFAULT);
3663 break;
3665 case REQ_VIEW_STATUS:
3666 if (opt_is_inside_work_tree == FALSE) {
3667 report("The status view requires a working tree");
3668 break;
3670 open_view(view, request, OPEN_DEFAULT);
3671 break;
3673 case REQ_VIEW_MAIN:
3674 case REQ_VIEW_DIFF:
3675 case REQ_VIEW_LOG:
3676 case REQ_VIEW_TREE:
3677 case REQ_VIEW_HELP:
3678 case REQ_VIEW_BRANCH:
3679 open_view(view, request, OPEN_DEFAULT);
3680 break;
3682 case REQ_NEXT:
3683 case REQ_PREVIOUS:
3684 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3686 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3687 view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3688 view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3689 view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3690 view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3691 int line;
3693 view = view->parent;
3694 line = view->lineno;
3695 move_view(view, request);
3696 if (view_is_displayed(view))
3697 update_view_title(view);
3698 if (line != view->lineno)
3699 view->ops->request(view, REQ_ENTER,
3700 &view->line[view->lineno]);
3702 } else {
3703 move_view(view, request);
3705 break;
3707 case REQ_VIEW_NEXT:
3709 int nviews = displayed_views();
3710 int next_view = (current_view + 1) % nviews;
3712 if (next_view == current_view) {
3713 report("Only one view is displayed");
3714 break;
3717 current_view = next_view;
3718 /* Blur out the title of the previous view. */
3719 update_view_title(view);
3720 report("");
3721 break;
3723 case REQ_REFRESH:
3724 report("Refreshing is not yet supported for the %s view", view->name);
3725 break;
3727 case REQ_MAXIMIZE:
3728 if (displayed_views() == 2)
3729 maximize_view(view);
3730 break;
3732 case REQ_OPTIONS:
3733 open_option_menu();
3734 break;
3736 case REQ_TOGGLE_LINENO:
3737 toggle_view_option(&opt_line_number, "line numbers");
3738 break;
3740 case REQ_TOGGLE_DATE:
3741 toggle_date();
3742 break;
3744 case REQ_TOGGLE_AUTHOR:
3745 toggle_author();
3746 break;
3748 case REQ_TOGGLE_REV_GRAPH:
3749 toggle_view_option(&opt_rev_graph, "revision graph display");
3750 break;
3752 case REQ_TOGGLE_REFS:
3753 toggle_view_option(&opt_show_refs, "reference display");
3754 break;
3756 case REQ_TOGGLE_SORT_FIELD:
3757 case REQ_TOGGLE_SORT_ORDER:
3758 report("Sorting is not yet supported for the %s view", view->name);
3759 break;
3761 case REQ_SEARCH:
3762 case REQ_SEARCH_BACK:
3763 search_view(view, request);
3764 break;
3766 case REQ_FIND_NEXT:
3767 case REQ_FIND_PREV:
3768 find_next(view, request);
3769 break;
3771 case REQ_STOP_LOADING:
3772 foreach_view(view, i) {
3773 if (view->pipe)
3774 report("Stopped loading the %s view", view->name),
3775 end_update(view, TRUE);
3777 break;
3779 case REQ_SHOW_VERSION:
3780 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3781 return TRUE;
3783 case REQ_SCREEN_REDRAW:
3784 redraw_display(TRUE);
3785 break;
3787 case REQ_EDIT:
3788 report("Nothing to edit");
3789 break;
3791 case REQ_ENTER:
3792 report("Nothing to enter");
3793 break;
3795 case REQ_VIEW_CLOSE:
3796 /* XXX: Mark closed views by letting view->parent point to the
3797 * view itself. Parents to closed view should never be
3798 * followed. */
3799 if (view->parent &&
3800 view->parent->parent != view->parent) {
3801 maximize_view(view->parent);
3802 view->parent = view;
3803 break;
3805 /* Fall-through */
3806 case REQ_QUIT:
3807 return FALSE;
3809 default:
3810 report("Unknown key, press %s for help",
3811 get_key(view->keymap, REQ_VIEW_HELP));
3812 return TRUE;
3815 return TRUE;
3820 * View backend utilities
3823 enum sort_field {
3824 ORDERBY_NAME,
3825 ORDERBY_DATE,
3826 ORDERBY_AUTHOR,
3829 struct sort_state {
3830 const enum sort_field *fields;
3831 size_t size, current;
3832 bool reverse;
3835 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3836 #define get_sort_field(state) ((state).fields[(state).current])
3837 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3839 static void
3840 sort_view(struct view *view, enum request request, struct sort_state *state,
3841 int (*compare)(const void *, const void *))
3843 switch (request) {
3844 case REQ_TOGGLE_SORT_FIELD:
3845 state->current = (state->current + 1) % state->size;
3846 break;
3848 case REQ_TOGGLE_SORT_ORDER:
3849 state->reverse = !state->reverse;
3850 break;
3851 default:
3852 die("Not a sort request");
3855 qsort(view->line, view->lines, sizeof(*view->line), compare);
3856 redraw_view(view);
3859 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3861 /* Small author cache to reduce memory consumption. It uses binary
3862 * search to lookup or find place to position new entries. No entries
3863 * are ever freed. */
3864 static const char *
3865 get_author(const char *name)
3867 static const char **authors;
3868 static size_t authors_size;
3869 int from = 0, to = authors_size - 1;
3871 while (from <= to) {
3872 size_t pos = (to + from) / 2;
3873 int cmp = strcmp(name, authors[pos]);
3875 if (!cmp)
3876 return authors[pos];
3878 if (cmp < 0)
3879 to = pos - 1;
3880 else
3881 from = pos + 1;
3884 if (!realloc_authors(&authors, authors_size, 1))
3885 return NULL;
3886 name = strdup(name);
3887 if (!name)
3888 return NULL;
3890 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3891 authors[from] = name;
3892 authors_size++;
3894 return name;
3897 static void
3898 parse_timesec(struct time *time, const char *sec)
3900 time->sec = (time_t) atol(sec);
3903 static void
3904 parse_timezone(struct time *time, const char *zone)
3906 long tz;
3908 tz = ('0' - zone[1]) * 60 * 60 * 10;
3909 tz += ('0' - zone[2]) * 60 * 60;
3910 tz += ('0' - zone[3]) * 60 * 10;
3911 tz += ('0' - zone[4]) * 60;
3913 if (zone[0] == '-')
3914 tz = -tz;
3916 time->tz = tz;
3917 time->sec -= tz;
3920 /* Parse author lines where the name may be empty:
3921 * author <email@address.tld> 1138474660 +0100
3923 static void
3924 parse_author_line(char *ident, const char **author, struct time *time)
3926 char *nameend = strchr(ident, '<');
3927 char *emailend = strchr(ident, '>');
3929 if (nameend && emailend)
3930 *nameend = *emailend = 0;
3931 ident = chomp_string(ident);
3932 if (!*ident) {
3933 if (nameend)
3934 ident = chomp_string(nameend + 1);
3935 if (!*ident)
3936 ident = "Unknown";
3939 *author = get_author(ident);
3941 /* Parse epoch and timezone */
3942 if (emailend && emailend[1] == ' ') {
3943 char *secs = emailend + 2;
3944 char *zone = strchr(secs, ' ');
3946 parse_timesec(time, secs);
3948 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3949 parse_timezone(time, zone + 1);
3953 static bool
3954 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3956 char rev[SIZEOF_REV];
3957 const char *revlist_argv[] = {
3958 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3960 struct menu_item *items;
3961 char text[SIZEOF_STR];
3962 bool ok = TRUE;
3963 int i;
3965 items = calloc(*parents + 1, sizeof(*items));
3966 if (!items)
3967 return FALSE;
3969 for (i = 0; i < *parents; i++) {
3970 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3971 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3972 !(items[i].text = strdup(text))) {
3973 ok = FALSE;
3974 break;
3978 if (ok) {
3979 *parents = 0;
3980 ok = prompt_menu("Select parent", items, parents);
3982 for (i = 0; items[i].text; i++)
3983 free((char *) items[i].text);
3984 free(items);
3985 return ok;
3988 static bool
3989 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3991 char buf[SIZEOF_STR * 4];
3992 const char *revlist_argv[] = {
3993 "git", "log", "--no-color", "-1",
3994 "--pretty=format:%P", id, "--", path, NULL
3996 int parents;
3998 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3999 (parents = strlen(buf) / 40) < 0) {
4000 report("Failed to get parent information");
4001 return FALSE;
4003 } else if (parents == 0) {
4004 if (path)
4005 report("Path '%s' does not exist in the parent", path);
4006 else
4007 report("The selected commit has no parents");
4008 return FALSE;
4011 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4012 return FALSE;
4014 string_copy_rev(rev, &buf[41 * parents]);
4015 return TRUE;
4019 * Pager backend
4022 static bool
4023 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4025 char text[SIZEOF_STR];
4027 if (opt_line_number && draw_lineno(view, lineno))
4028 return TRUE;
4030 string_expand(text, sizeof(text), line->data, opt_tab_size);
4031 draw_text(view, line->type, text, TRUE);
4032 return TRUE;
4035 static bool
4036 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4038 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4039 char ref[SIZEOF_STR];
4041 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4042 return TRUE;
4044 /* This is the only fatal call, since it can "corrupt" the buffer. */
4045 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4046 return FALSE;
4048 return TRUE;
4051 static void
4052 add_pager_refs(struct view *view, struct line *line)
4054 char buf[SIZEOF_STR];
4055 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4056 struct ref_list *list;
4057 size_t bufpos = 0, i;
4058 const char *sep = "Refs: ";
4059 bool is_tag = FALSE;
4061 assert(line->type == LINE_COMMIT);
4063 list = get_ref_list(commit_id);
4064 if (!list) {
4065 if (view->type == VIEW_DIFF)
4066 goto try_add_describe_ref;
4067 return;
4070 for (i = 0; i < list->size; i++) {
4071 struct ref *ref = list->refs[i];
4072 const char *fmt = ref->tag ? "%s[%s]" :
4073 ref->remote ? "%s<%s>" : "%s%s";
4075 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4076 return;
4077 sep = ", ";
4078 if (ref->tag)
4079 is_tag = TRUE;
4082 if (!is_tag && view->type == VIEW_DIFF) {
4083 try_add_describe_ref:
4084 /* Add <tag>-g<commit_id> "fake" reference. */
4085 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4086 return;
4089 if (bufpos == 0)
4090 return;
4092 add_line_text(view, buf, LINE_PP_REFS);
4095 static bool
4096 pager_read(struct view *view, char *data)
4098 struct line *line;
4100 if (!data)
4101 return TRUE;
4103 line = add_line_text(view, data, get_line_type(data));
4104 if (!line)
4105 return FALSE;
4107 if (line->type == LINE_COMMIT &&
4108 (view->type == VIEW_DIFF ||
4109 view->type == VIEW_LOG))
4110 add_pager_refs(view, line);
4112 return TRUE;
4115 static enum request
4116 pager_request(struct view *view, enum request request, struct line *line)
4118 int split = 0;
4120 if (request != REQ_ENTER)
4121 return request;
4123 if (line->type == LINE_COMMIT &&
4124 (view->type == VIEW_LOG ||
4125 view->type == VIEW_PAGER)) {
4126 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4127 split = 1;
4130 /* Always scroll the view even if it was split. That way
4131 * you can use Enter to scroll through the log view and
4132 * split open each commit diff. */
4133 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4135 /* FIXME: A minor workaround. Scrolling the view will call report("")
4136 * but if we are scrolling a non-current view this won't properly
4137 * update the view title. */
4138 if (split)
4139 update_view_title(view);
4141 return REQ_NONE;
4144 static bool
4145 pager_grep(struct view *view, struct line *line)
4147 const char *text[] = { line->data, NULL };
4149 return grep_text(view, text);
4152 static void
4153 pager_select(struct view *view, struct line *line)
4155 if (line->type == LINE_COMMIT) {
4156 char *text = (char *)line->data + STRING_SIZE("commit ");
4158 if (view->type != VIEW_PAGER)
4159 string_copy_rev(view->ref, text);
4160 string_copy_rev(ref_commit, text);
4164 static struct view_ops pager_ops = {
4165 "line",
4166 NULL,
4167 NULL,
4168 pager_read,
4169 pager_draw,
4170 pager_request,
4171 pager_grep,
4172 pager_select,
4175 static const char *log_argv[SIZEOF_ARG] = {
4176 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4179 static enum request
4180 log_request(struct view *view, enum request request, struct line *line)
4182 switch (request) {
4183 case REQ_REFRESH:
4184 load_refs();
4185 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4186 return REQ_NONE;
4187 default:
4188 return pager_request(view, request, line);
4192 static struct view_ops log_ops = {
4193 "line",
4194 log_argv,
4195 NULL,
4196 pager_read,
4197 pager_draw,
4198 log_request,
4199 pager_grep,
4200 pager_select,
4203 static const char *diff_argv[SIZEOF_ARG] = {
4204 "git", "show", "--pretty=fuller", "--no-color", "--root",
4205 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4208 static struct view_ops diff_ops = {
4209 "line",
4210 diff_argv,
4211 NULL,
4212 pager_read,
4213 pager_draw,
4214 pager_request,
4215 pager_grep,
4216 pager_select,
4220 * Help backend
4223 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4225 static bool
4226 help_open_keymap_title(struct view *view, enum keymap keymap)
4228 struct line *line;
4230 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4231 help_keymap_hidden[keymap] ? '+' : '-',
4232 enum_name(keymap_table[keymap]));
4233 if (line)
4234 line->other = keymap;
4236 return help_keymap_hidden[keymap];
4239 static void
4240 help_open_keymap(struct view *view, enum keymap keymap)
4242 const char *group = NULL;
4243 char buf[SIZEOF_STR];
4244 size_t bufpos;
4245 bool add_title = TRUE;
4246 int i;
4248 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4249 const char *key = NULL;
4251 if (req_info[i].request == REQ_NONE)
4252 continue;
4254 if (!req_info[i].request) {
4255 group = req_info[i].help;
4256 continue;
4259 key = get_keys(keymap, req_info[i].request, TRUE);
4260 if (!key || !*key)
4261 continue;
4263 if (add_title && help_open_keymap_title(view, keymap))
4264 return;
4265 add_title = FALSE;
4267 if (group) {
4268 add_line_text(view, group, LINE_HELP_GROUP);
4269 group = NULL;
4272 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4273 enum_name(req_info[i]), req_info[i].help);
4276 group = "External commands:";
4278 for (i = 0; i < run_requests; i++) {
4279 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4280 const char *key;
4281 int argc;
4283 if (!req || req->keymap != keymap)
4284 continue;
4286 key = get_key_name(req->key);
4287 if (!*key)
4288 key = "(no key defined)";
4290 if (add_title && help_open_keymap_title(view, keymap))
4291 return;
4292 if (group) {
4293 add_line_text(view, group, LINE_HELP_GROUP);
4294 group = NULL;
4297 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4298 if (!string_format_from(buf, &bufpos, "%s%s",
4299 argc ? " " : "", req->argv[argc]))
4300 return;
4302 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4306 static bool
4307 help_open(struct view *view)
4309 enum keymap keymap;
4311 reset_view(view);
4312 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4313 add_line_text(view, "", LINE_DEFAULT);
4315 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4316 help_open_keymap(view, keymap);
4318 return TRUE;
4321 static enum request
4322 help_request(struct view *view, enum request request, struct line *line)
4324 switch (request) {
4325 case REQ_ENTER:
4326 if (line->type == LINE_HELP_KEYMAP) {
4327 help_keymap_hidden[line->other] =
4328 !help_keymap_hidden[line->other];
4329 view->p_restore = TRUE;
4330 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4333 return REQ_NONE;
4334 default:
4335 return pager_request(view, request, line);
4339 static struct view_ops help_ops = {
4340 "line",
4341 NULL,
4342 help_open,
4343 NULL,
4344 pager_draw,
4345 help_request,
4346 pager_grep,
4347 pager_select,
4352 * Tree backend
4355 struct tree_stack_entry {
4356 struct tree_stack_entry *prev; /* Entry below this in the stack */
4357 unsigned long lineno; /* Line number to restore */
4358 char *name; /* Position of name in opt_path */
4361 /* The top of the path stack. */
4362 static struct tree_stack_entry *tree_stack = NULL;
4363 unsigned long tree_lineno = 0;
4365 static void
4366 pop_tree_stack_entry(void)
4368 struct tree_stack_entry *entry = tree_stack;
4370 tree_lineno = entry->lineno;
4371 entry->name[0] = 0;
4372 tree_stack = entry->prev;
4373 free(entry);
4376 static void
4377 push_tree_stack_entry(const char *name, unsigned long lineno)
4379 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4380 size_t pathlen = strlen(opt_path);
4382 if (!entry)
4383 return;
4385 entry->prev = tree_stack;
4386 entry->name = opt_path + pathlen;
4387 tree_stack = entry;
4389 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4390 pop_tree_stack_entry();
4391 return;
4394 /* Move the current line to the first tree entry. */
4395 tree_lineno = 1;
4396 entry->lineno = lineno;
4399 /* Parse output from git-ls-tree(1):
4401 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4404 #define SIZEOF_TREE_ATTR \
4405 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4407 #define SIZEOF_TREE_MODE \
4408 STRING_SIZE("100644 ")
4410 #define TREE_ID_OFFSET \
4411 STRING_SIZE("100644 blob ")
4413 struct tree_entry {
4414 char id[SIZEOF_REV];
4415 mode_t mode;
4416 struct time time; /* Date from the author ident. */
4417 const char *author; /* Author of the commit. */
4418 char name[1];
4421 static const char *
4422 tree_path(const struct line *line)
4424 return ((struct tree_entry *) line->data)->name;
4427 static int
4428 tree_compare_entry(const struct line *line1, const struct line *line2)
4430 if (line1->type != line2->type)
4431 return line1->type == LINE_TREE_DIR ? -1 : 1;
4432 return strcmp(tree_path(line1), tree_path(line2));
4435 static const enum sort_field tree_sort_fields[] = {
4436 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4438 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4440 static int
4441 tree_compare(const void *l1, const void *l2)
4443 const struct line *line1 = (const struct line *) l1;
4444 const struct line *line2 = (const struct line *) l2;
4445 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4446 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4448 if (line1->type == LINE_TREE_HEAD)
4449 return -1;
4450 if (line2->type == LINE_TREE_HEAD)
4451 return 1;
4453 switch (get_sort_field(tree_sort_state)) {
4454 case ORDERBY_DATE:
4455 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4457 case ORDERBY_AUTHOR:
4458 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4460 case ORDERBY_NAME:
4461 default:
4462 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4467 static struct line *
4468 tree_entry(struct view *view, enum line_type type, const char *path,
4469 const char *mode, const char *id)
4471 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4472 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4474 if (!entry || !line) {
4475 free(entry);
4476 return NULL;
4479 strncpy(entry->name, path, strlen(path));
4480 if (mode)
4481 entry->mode = strtoul(mode, NULL, 8);
4482 if (id)
4483 string_copy_rev(entry->id, id);
4485 return line;
4488 static bool
4489 tree_read_date(struct view *view, char *text, bool *read_date)
4491 static const char *author_name;
4492 static struct time author_time;
4494 if (!text && *read_date) {
4495 *read_date = FALSE;
4496 return TRUE;
4498 } else if (!text) {
4499 char *path = *opt_path ? opt_path : ".";
4500 /* Find next entry to process */
4501 const char *log_file[] = {
4502 "git", "log", "--no-color", "--pretty=raw",
4503 "--cc", "--raw", view->id, "--", path, NULL
4505 struct io io = {};
4507 if (!view->lines) {
4508 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4509 report("Tree is empty");
4510 return TRUE;
4513 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4514 report("Failed to load tree data");
4515 return TRUE;
4518 io_done(view->pipe);
4519 view->io = io;
4520 *read_date = TRUE;
4521 return FALSE;
4523 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4524 parse_author_line(text + STRING_SIZE("author "),
4525 &author_name, &author_time);
4527 } else if (*text == ':') {
4528 char *pos;
4529 size_t annotated = 1;
4530 size_t i;
4532 pos = strchr(text, '\t');
4533 if (!pos)
4534 return TRUE;
4535 text = pos + 1;
4536 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4537 text += strlen(opt_path);
4538 pos = strchr(text, '/');
4539 if (pos)
4540 *pos = 0;
4542 for (i = 1; i < view->lines; i++) {
4543 struct line *line = &view->line[i];
4544 struct tree_entry *entry = line->data;
4546 annotated += !!entry->author;
4547 if (entry->author || strcmp(entry->name, text))
4548 continue;
4550 entry->author = author_name;
4551 entry->time = author_time;
4552 line->dirty = 1;
4553 break;
4556 if (annotated == view->lines)
4557 io_kill(view->pipe);
4559 return TRUE;
4562 static bool
4563 tree_read(struct view *view, char *text)
4565 static bool read_date = FALSE;
4566 struct tree_entry *data;
4567 struct line *entry, *line;
4568 enum line_type type;
4569 size_t textlen = text ? strlen(text) : 0;
4570 char *path = text + SIZEOF_TREE_ATTR;
4572 if (read_date || !text)
4573 return tree_read_date(view, text, &read_date);
4575 if (textlen <= SIZEOF_TREE_ATTR)
4576 return FALSE;
4577 if (view->lines == 0 &&
4578 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4579 return FALSE;
4581 /* Strip the path part ... */
4582 if (*opt_path) {
4583 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4584 size_t striplen = strlen(opt_path);
4586 if (pathlen > striplen)
4587 memmove(path, path + striplen,
4588 pathlen - striplen + 1);
4590 /* Insert "link" to parent directory. */
4591 if (view->lines == 1 &&
4592 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4593 return FALSE;
4596 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4597 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4598 if (!entry)
4599 return FALSE;
4600 data = entry->data;
4602 /* Skip "Directory ..." and ".." line. */
4603 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4604 if (tree_compare_entry(line, entry) <= 0)
4605 continue;
4607 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4609 line->data = data;
4610 line->type = type;
4611 for (; line <= entry; line++)
4612 line->dirty = line->cleareol = 1;
4613 return TRUE;
4616 if (tree_lineno > view->lineno) {
4617 view->lineno = tree_lineno;
4618 tree_lineno = 0;
4621 return TRUE;
4624 static bool
4625 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4627 struct tree_entry *entry = line->data;
4629 if (line->type == LINE_TREE_HEAD) {
4630 if (draw_text(view, line->type, "Directory path /", TRUE))
4631 return TRUE;
4632 } else {
4633 if (draw_mode(view, entry->mode))
4634 return TRUE;
4636 if (opt_author && draw_author(view, entry->author))
4637 return TRUE;
4639 if (opt_date && draw_date(view, &entry->time))
4640 return TRUE;
4642 if (draw_text(view, line->type, entry->name, TRUE))
4643 return TRUE;
4644 return TRUE;
4647 static void
4648 open_blob_editor()
4650 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4651 int fd = mkstemp(file);
4653 if (fd == -1)
4654 report("Failed to create temporary file");
4655 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4656 report("Failed to save blob data to file");
4657 else
4658 open_editor(file);
4659 if (fd != -1)
4660 unlink(file);
4663 static enum request
4664 tree_request(struct view *view, enum request request, struct line *line)
4666 enum open_flags flags;
4668 switch (request) {
4669 case REQ_VIEW_BLAME:
4670 if (line->type != LINE_TREE_FILE) {
4671 report("Blame only supported for files");
4672 return REQ_NONE;
4675 string_copy(opt_ref, view->vid);
4676 return request;
4678 case REQ_EDIT:
4679 if (line->type != LINE_TREE_FILE) {
4680 report("Edit only supported for files");
4681 } else if (!is_head_commit(view->vid)) {
4682 open_blob_editor();
4683 } else {
4684 open_editor(opt_file);
4686 return REQ_NONE;
4688 case REQ_TOGGLE_SORT_FIELD:
4689 case REQ_TOGGLE_SORT_ORDER:
4690 sort_view(view, request, &tree_sort_state, tree_compare);
4691 return REQ_NONE;
4693 case REQ_PARENT:
4694 if (!*opt_path) {
4695 /* quit view if at top of tree */
4696 return REQ_VIEW_CLOSE;
4698 /* fake 'cd ..' */
4699 line = &view->line[1];
4700 break;
4702 case REQ_ENTER:
4703 break;
4705 default:
4706 return request;
4709 /* Cleanup the stack if the tree view is at a different tree. */
4710 while (!*opt_path && tree_stack)
4711 pop_tree_stack_entry();
4713 switch (line->type) {
4714 case LINE_TREE_DIR:
4715 /* Depending on whether it is a subdirectory or parent link
4716 * mangle the path buffer. */
4717 if (line == &view->line[1] && *opt_path) {
4718 pop_tree_stack_entry();
4720 } else {
4721 const char *basename = tree_path(line);
4723 push_tree_stack_entry(basename, view->lineno);
4726 /* Trees and subtrees share the same ID, so they are not not
4727 * unique like blobs. */
4728 flags = OPEN_RELOAD;
4729 request = REQ_VIEW_TREE;
4730 break;
4732 case LINE_TREE_FILE:
4733 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4734 request = REQ_VIEW_BLOB;
4735 break;
4737 default:
4738 return REQ_NONE;
4741 open_view(view, request, flags);
4742 if (request == REQ_VIEW_TREE)
4743 view->lineno = tree_lineno;
4745 return REQ_NONE;
4748 static bool
4749 tree_grep(struct view *view, struct line *line)
4751 struct tree_entry *entry = line->data;
4752 const char *text[] = {
4753 entry->name,
4754 opt_author ? entry->author : "",
4755 mkdate(&entry->time, opt_date),
4756 NULL
4759 return grep_text(view, text);
4762 static void
4763 tree_select(struct view *view, struct line *line)
4765 struct tree_entry *entry = line->data;
4767 if (line->type == LINE_TREE_FILE) {
4768 string_copy_rev(ref_blob, entry->id);
4769 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4771 } else if (line->type != LINE_TREE_DIR) {
4772 return;
4775 string_copy_rev(view->ref, entry->id);
4778 static bool
4779 tree_prepare(struct view *view)
4781 if (view->lines == 0 && opt_prefix[0]) {
4782 char *pos = opt_prefix;
4784 while (pos && *pos) {
4785 char *end = strchr(pos, '/');
4787 if (end)
4788 *end = 0;
4789 push_tree_stack_entry(pos, 0);
4790 pos = end;
4791 if (end) {
4792 *end = '/';
4793 pos++;
4797 } else if (strcmp(view->vid, view->id)) {
4798 opt_path[0] = 0;
4801 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4804 static const char *tree_argv[SIZEOF_ARG] = {
4805 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4808 static struct view_ops tree_ops = {
4809 "file",
4810 tree_argv,
4811 NULL,
4812 tree_read,
4813 tree_draw,
4814 tree_request,
4815 tree_grep,
4816 tree_select,
4817 tree_prepare,
4820 static bool
4821 blob_read(struct view *view, char *line)
4823 if (!line)
4824 return TRUE;
4825 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4828 static enum request
4829 blob_request(struct view *view, enum request request, struct line *line)
4831 switch (request) {
4832 case REQ_EDIT:
4833 open_blob_editor();
4834 return REQ_NONE;
4835 default:
4836 return pager_request(view, request, line);
4840 static const char *blob_argv[SIZEOF_ARG] = {
4841 "git", "cat-file", "blob", "%(blob)", NULL
4844 static struct view_ops blob_ops = {
4845 "line",
4846 blob_argv,
4847 NULL,
4848 blob_read,
4849 pager_draw,
4850 blob_request,
4851 pager_grep,
4852 pager_select,
4856 * Blame backend
4858 * Loading the blame view is a two phase job:
4860 * 1. File content is read either using opt_file from the
4861 * filesystem or using git-cat-file.
4862 * 2. Then blame information is incrementally added by
4863 * reading output from git-blame.
4866 static const char *blame_head_argv[] = {
4867 "git", "blame", "--incremental", "--", "%(file)", NULL
4870 static const char *blame_ref_argv[] = {
4871 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4874 static const char *blame_cat_file_argv[] = {
4875 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4878 struct blame_commit {
4879 char id[SIZEOF_REV]; /* SHA1 ID. */
4880 char title[128]; /* First line of the commit message. */
4881 const char *author; /* Author of the commit. */
4882 struct time time; /* Date from the author ident. */
4883 char filename[128]; /* Name of file. */
4884 bool has_previous; /* Was a "previous" line detected. */
4887 struct blame {
4888 struct blame_commit *commit;
4889 unsigned long lineno;
4890 char text[1];
4893 static bool
4894 blame_open(struct view *view)
4896 char path[SIZEOF_STR];
4898 if (!view->parent && *opt_prefix) {
4899 string_copy(path, opt_file);
4900 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4901 return FALSE;
4904 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4905 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4906 return FALSE;
4909 setup_update(view, opt_file);
4910 string_format(view->ref, "%s ...", opt_file);
4912 return TRUE;
4915 static struct blame_commit *
4916 get_blame_commit(struct view *view, const char *id)
4918 size_t i;
4920 for (i = 0; i < view->lines; i++) {
4921 struct blame *blame = view->line[i].data;
4923 if (!blame->commit)
4924 continue;
4926 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4927 return blame->commit;
4931 struct blame_commit *commit = calloc(1, sizeof(*commit));
4933 if (commit)
4934 string_ncopy(commit->id, id, SIZEOF_REV);
4935 return commit;
4939 static bool
4940 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4942 const char *pos = *posref;
4944 *posref = NULL;
4945 pos = strchr(pos + 1, ' ');
4946 if (!pos || !isdigit(pos[1]))
4947 return FALSE;
4948 *number = atoi(pos + 1);
4949 if (*number < min || *number > max)
4950 return FALSE;
4952 *posref = pos;
4953 return TRUE;
4956 static struct blame_commit *
4957 parse_blame_commit(struct view *view, const char *text, int *blamed)
4959 struct blame_commit *commit;
4960 struct blame *blame;
4961 const char *pos = text + SIZEOF_REV - 2;
4962 size_t orig_lineno = 0;
4963 size_t lineno;
4964 size_t group;
4966 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4967 return NULL;
4969 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4970 !parse_number(&pos, &lineno, 1, view->lines) ||
4971 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4972 return NULL;
4974 commit = get_blame_commit(view, text);
4975 if (!commit)
4976 return NULL;
4978 *blamed += group;
4979 while (group--) {
4980 struct line *line = &view->line[lineno + group - 1];
4982 blame = line->data;
4983 blame->commit = commit;
4984 blame->lineno = orig_lineno + group - 1;
4985 line->dirty = 1;
4988 return commit;
4991 static bool
4992 blame_read_file(struct view *view, const char *line, bool *read_file)
4994 if (!line) {
4995 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4996 struct io io = {};
4998 if (view->lines == 0 && !view->parent)
4999 die("No blame exist for %s", view->vid);
5001 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5002 report("Failed to load blame data");
5003 return TRUE;
5006 io_done(view->pipe);
5007 view->io = io;
5008 *read_file = FALSE;
5009 return FALSE;
5011 } else {
5012 size_t linelen = strlen(line);
5013 struct blame *blame = malloc(sizeof(*blame) + linelen);
5015 if (!blame)
5016 return FALSE;
5018 blame->commit = NULL;
5019 strncpy(blame->text, line, linelen);
5020 blame->text[linelen] = 0;
5021 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5025 static bool
5026 match_blame_header(const char *name, char **line)
5028 size_t namelen = strlen(name);
5029 bool matched = !strncmp(name, *line, namelen);
5031 if (matched)
5032 *line += namelen;
5034 return matched;
5037 static bool
5038 blame_read(struct view *view, char *line)
5040 static struct blame_commit *commit = NULL;
5041 static int blamed = 0;
5042 static bool read_file = TRUE;
5044 if (read_file)
5045 return blame_read_file(view, line, &read_file);
5047 if (!line) {
5048 /* Reset all! */
5049 commit = NULL;
5050 blamed = 0;
5051 read_file = TRUE;
5052 string_format(view->ref, "%s", view->vid);
5053 if (view_is_displayed(view)) {
5054 update_view_title(view);
5055 redraw_view_from(view, 0);
5057 return TRUE;
5060 if (!commit) {
5061 commit = parse_blame_commit(view, line, &blamed);
5062 string_format(view->ref, "%s %2d%%", view->vid,
5063 view->lines ? blamed * 100 / view->lines : 0);
5065 } else if (match_blame_header("author ", &line)) {
5066 commit->author = get_author(line);
5068 } else if (match_blame_header("author-time ", &line)) {
5069 parse_timesec(&commit->time, line);
5071 } else if (match_blame_header("author-tz ", &line)) {
5072 parse_timezone(&commit->time, line);
5074 } else if (match_blame_header("summary ", &line)) {
5075 string_ncopy(commit->title, line, strlen(line));
5077 } else if (match_blame_header("previous ", &line)) {
5078 commit->has_previous = TRUE;
5080 } else if (match_blame_header("filename ", &line)) {
5081 string_ncopy(commit->filename, line, strlen(line));
5082 commit = NULL;
5085 return TRUE;
5088 static bool
5089 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5091 struct blame *blame = line->data;
5092 struct time *time = NULL;
5093 const char *id = NULL, *author = NULL;
5094 char text[SIZEOF_STR];
5096 if (blame->commit && *blame->commit->filename) {
5097 id = blame->commit->id;
5098 author = blame->commit->author;
5099 time = &blame->commit->time;
5102 if (opt_date && draw_date(view, time))
5103 return TRUE;
5105 if (opt_author && draw_author(view, author))
5106 return TRUE;
5108 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5109 return TRUE;
5111 if (draw_lineno(view, lineno))
5112 return TRUE;
5114 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5115 draw_text(view, LINE_DEFAULT, text, TRUE);
5116 return TRUE;
5119 static bool
5120 check_blame_commit(struct blame *blame, bool check_null_id)
5122 if (!blame->commit)
5123 report("Commit data not loaded yet");
5124 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5125 report("No commit exist for the selected line");
5126 else
5127 return TRUE;
5128 return FALSE;
5131 static void
5132 setup_blame_parent_line(struct view *view, struct blame *blame)
5134 const char *diff_tree_argv[] = {
5135 "git", "diff-tree", "-U0", blame->commit->id,
5136 "--", blame->commit->filename, NULL
5138 struct io io = {};
5139 int parent_lineno = -1;
5140 int blamed_lineno = -1;
5141 char *line;
5143 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5144 return;
5146 while ((line = io_get(&io, '\n', TRUE))) {
5147 if (*line == '@') {
5148 char *pos = strchr(line, '+');
5150 parent_lineno = atoi(line + 4);
5151 if (pos)
5152 blamed_lineno = atoi(pos + 1);
5154 } else if (*line == '+' && parent_lineno != -1) {
5155 if (blame->lineno == blamed_lineno - 1 &&
5156 !strcmp(blame->text, line + 1)) {
5157 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5158 break;
5160 blamed_lineno++;
5164 io_done(&io);
5167 static enum request
5168 blame_request(struct view *view, enum request request, struct line *line)
5170 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5171 struct blame *blame = line->data;
5173 switch (request) {
5174 case REQ_VIEW_BLAME:
5175 if (check_blame_commit(blame, TRUE)) {
5176 string_copy(opt_ref, blame->commit->id);
5177 string_copy(opt_file, blame->commit->filename);
5178 if (blame->lineno)
5179 view->lineno = blame->lineno;
5180 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5182 break;
5184 case REQ_PARENT:
5185 if (check_blame_commit(blame, TRUE) &&
5186 select_commit_parent(blame->commit->id, opt_ref,
5187 blame->commit->filename)) {
5188 string_copy(opt_file, blame->commit->filename);
5189 setup_blame_parent_line(view, blame);
5190 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5192 break;
5194 case REQ_ENTER:
5195 if (!check_blame_commit(blame, FALSE))
5196 break;
5198 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5199 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5200 break;
5202 if (!strcmp(blame->commit->id, NULL_ID)) {
5203 struct view *diff = VIEW(REQ_VIEW_DIFF);
5204 const char *diff_index_argv[] = {
5205 "git", "diff-index", "--root", "--patch-with-stat",
5206 "-C", "-M", "HEAD", "--", view->vid, NULL
5209 if (!blame->commit->has_previous) {
5210 diff_index_argv[1] = "diff";
5211 diff_index_argv[2] = "--no-color";
5212 diff_index_argv[6] = "--";
5213 diff_index_argv[7] = "/dev/null";
5216 if (!prepare_update(diff, diff_index_argv, NULL)) {
5217 report("Failed to allocate diff command");
5218 break;
5220 flags |= OPEN_PREPARED;
5223 open_view(view, REQ_VIEW_DIFF, flags);
5224 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5225 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5226 break;
5228 default:
5229 return request;
5232 return REQ_NONE;
5235 static bool
5236 blame_grep(struct view *view, struct line *line)
5238 struct blame *blame = line->data;
5239 struct blame_commit *commit = blame->commit;
5240 const char *text[] = {
5241 blame->text,
5242 commit ? commit->title : "",
5243 commit ? commit->id : "",
5244 commit && opt_author ? commit->author : "",
5245 commit ? mkdate(&commit->time, opt_date) : "",
5246 NULL
5249 return grep_text(view, text);
5252 static void
5253 blame_select(struct view *view, struct line *line)
5255 struct blame *blame = line->data;
5256 struct blame_commit *commit = blame->commit;
5258 if (!commit)
5259 return;
5261 if (!strcmp(commit->id, NULL_ID))
5262 string_ncopy(ref_commit, "HEAD", 4);
5263 else
5264 string_copy_rev(ref_commit, commit->id);
5267 static struct view_ops blame_ops = {
5268 "line",
5269 NULL,
5270 blame_open,
5271 blame_read,
5272 blame_draw,
5273 blame_request,
5274 blame_grep,
5275 blame_select,
5279 * Branch backend
5282 struct branch {
5283 const char *author; /* Author of the last commit. */
5284 struct time time; /* Date of the last activity. */
5285 const struct ref *ref; /* Name and commit ID information. */
5288 static const struct ref branch_all;
5290 static const enum sort_field branch_sort_fields[] = {
5291 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5293 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5295 static int
5296 branch_compare(const void *l1, const void *l2)
5298 const struct branch *branch1 = ((const struct line *) l1)->data;
5299 const struct branch *branch2 = ((const struct line *) l2)->data;
5301 switch (get_sort_field(branch_sort_state)) {
5302 case ORDERBY_DATE:
5303 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5305 case ORDERBY_AUTHOR:
5306 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5308 case ORDERBY_NAME:
5309 default:
5310 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5314 static bool
5315 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5317 struct branch *branch = line->data;
5318 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5320 if (opt_date && draw_date(view, &branch->time))
5321 return TRUE;
5323 if (opt_author && draw_author(view, branch->author))
5324 return TRUE;
5326 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5327 return TRUE;
5330 static enum request
5331 branch_request(struct view *view, enum request request, struct line *line)
5333 struct branch *branch = line->data;
5335 switch (request) {
5336 case REQ_REFRESH:
5337 load_refs();
5338 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5339 return REQ_NONE;
5341 case REQ_TOGGLE_SORT_FIELD:
5342 case REQ_TOGGLE_SORT_ORDER:
5343 sort_view(view, request, &branch_sort_state, branch_compare);
5344 return REQ_NONE;
5346 case REQ_ENTER:
5347 if (branch->ref == &branch_all) {
5348 const char *all_branches_argv[] = {
5349 "git", "log", "--no-color", "--pretty=raw", "--parents",
5350 "--topo-order", "--all", NULL
5352 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5354 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5355 report("Failed to load view of all branches");
5356 return REQ_NONE;
5358 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5359 } else {
5360 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5362 return REQ_NONE;
5364 default:
5365 return request;
5369 static bool
5370 branch_read(struct view *view, char *line)
5372 static char id[SIZEOF_REV];
5373 struct branch *reference;
5374 size_t i;
5376 if (!line)
5377 return TRUE;
5379 switch (get_line_type(line)) {
5380 case LINE_COMMIT:
5381 string_copy_rev(id, line + STRING_SIZE("commit "));
5382 return TRUE;
5384 case LINE_AUTHOR:
5385 for (i = 0, reference = NULL; i < view->lines; i++) {
5386 struct branch *branch = view->line[i].data;
5388 if (strcmp(branch->ref->id, id))
5389 continue;
5391 view->line[i].dirty = TRUE;
5392 if (reference) {
5393 branch->author = reference->author;
5394 branch->time = reference->time;
5395 continue;
5398 parse_author_line(line + STRING_SIZE("author "),
5399 &branch->author, &branch->time);
5400 reference = branch;
5402 return TRUE;
5404 default:
5405 return TRUE;
5410 static bool
5411 branch_open_visitor(void *data, const struct ref *ref)
5413 struct view *view = data;
5414 struct branch *branch;
5416 if (ref->tag || ref->ltag || ref->remote)
5417 return TRUE;
5419 branch = calloc(1, sizeof(*branch));
5420 if (!branch)
5421 return FALSE;
5423 branch->ref = ref;
5424 return !!add_line_data(view, branch, LINE_DEFAULT);
5427 static bool
5428 branch_open(struct view *view)
5430 const char *branch_log[] = {
5431 "git", "log", "--no-color", "--pretty=raw",
5432 "--simplify-by-decoration", "--all", NULL
5435 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5436 report("Failed to load branch data");
5437 return TRUE;
5440 setup_update(view, view->id);
5441 branch_open_visitor(view, &branch_all);
5442 foreach_ref(branch_open_visitor, view);
5443 view->p_restore = TRUE;
5445 return TRUE;
5448 static bool
5449 branch_grep(struct view *view, struct line *line)
5451 struct branch *branch = line->data;
5452 const char *text[] = {
5453 branch->ref->name,
5454 branch->author,
5455 NULL
5458 return grep_text(view, text);
5461 static void
5462 branch_select(struct view *view, struct line *line)
5464 struct branch *branch = line->data;
5466 string_copy_rev(view->ref, branch->ref->id);
5467 string_copy_rev(ref_commit, branch->ref->id);
5468 string_copy_rev(ref_head, branch->ref->id);
5469 string_copy_rev(ref_branch, branch->ref->name);
5472 static struct view_ops branch_ops = {
5473 "branch",
5474 NULL,
5475 branch_open,
5476 branch_read,
5477 branch_draw,
5478 branch_request,
5479 branch_grep,
5480 branch_select,
5484 * Status backend
5487 struct status {
5488 char status;
5489 struct {
5490 mode_t mode;
5491 char rev[SIZEOF_REV];
5492 char name[SIZEOF_STR];
5493 } old;
5494 struct {
5495 mode_t mode;
5496 char rev[SIZEOF_REV];
5497 char name[SIZEOF_STR];
5498 } new;
5501 static char status_onbranch[SIZEOF_STR];
5502 static struct status stage_status;
5503 static enum line_type stage_line_type;
5504 static size_t stage_chunks;
5505 static int *stage_chunk;
5507 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5509 /* This should work even for the "On branch" line. */
5510 static inline bool
5511 status_has_none(struct view *view, struct line *line)
5513 return line < view->line + view->lines && !line[1].data;
5516 /* Get fields from the diff line:
5517 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5519 static inline bool
5520 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5522 const char *old_mode = buf + 1;
5523 const char *new_mode = buf + 8;
5524 const char *old_rev = buf + 15;
5525 const char *new_rev = buf + 56;
5526 const char *status = buf + 97;
5528 if (bufsize < 98 ||
5529 old_mode[-1] != ':' ||
5530 new_mode[-1] != ' ' ||
5531 old_rev[-1] != ' ' ||
5532 new_rev[-1] != ' ' ||
5533 status[-1] != ' ')
5534 return FALSE;
5536 file->status = *status;
5538 string_copy_rev(file->old.rev, old_rev);
5539 string_copy_rev(file->new.rev, new_rev);
5541 file->old.mode = strtoul(old_mode, NULL, 8);
5542 file->new.mode = strtoul(new_mode, NULL, 8);
5544 file->old.name[0] = file->new.name[0] = 0;
5546 return TRUE;
5549 static bool
5550 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5552 struct status *unmerged = NULL;
5553 char *buf;
5554 struct io io = {};
5556 if (!io_run(&io, argv, opt_cdup, IO_RD))
5557 return FALSE;
5559 add_line_data(view, NULL, type);
5561 while ((buf = io_get(&io, 0, TRUE))) {
5562 struct status *file = unmerged;
5564 if (!file) {
5565 file = calloc(1, sizeof(*file));
5566 if (!file || !add_line_data(view, file, type))
5567 goto error_out;
5570 /* Parse diff info part. */
5571 if (status) {
5572 file->status = status;
5573 if (status == 'A')
5574 string_copy(file->old.rev, NULL_ID);
5576 } else if (!file->status || file == unmerged) {
5577 if (!status_get_diff(file, buf, strlen(buf)))
5578 goto error_out;
5580 buf = io_get(&io, 0, TRUE);
5581 if (!buf)
5582 break;
5584 /* Collapse all modified entries that follow an
5585 * associated unmerged entry. */
5586 if (unmerged == file) {
5587 unmerged->status = 'U';
5588 unmerged = NULL;
5589 } else if (file->status == 'U') {
5590 unmerged = file;
5594 /* Grab the old name for rename/copy. */
5595 if (!*file->old.name &&
5596 (file->status == 'R' || file->status == 'C')) {
5597 string_ncopy(file->old.name, buf, strlen(buf));
5599 buf = io_get(&io, 0, TRUE);
5600 if (!buf)
5601 break;
5604 /* git-ls-files just delivers a NUL separated list of
5605 * file names similar to the second half of the
5606 * git-diff-* output. */
5607 string_ncopy(file->new.name, buf, strlen(buf));
5608 if (!*file->old.name)
5609 string_copy(file->old.name, file->new.name);
5610 file = NULL;
5613 if (io_error(&io)) {
5614 error_out:
5615 io_done(&io);
5616 return FALSE;
5619 if (!view->line[view->lines - 1].data)
5620 add_line_data(view, NULL, LINE_STAT_NONE);
5622 io_done(&io);
5623 return TRUE;
5626 /* Don't show unmerged entries in the staged section. */
5627 static const char *status_diff_index_argv[] = {
5628 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5629 "--cached", "-M", "HEAD", NULL
5632 static const char *status_diff_files_argv[] = {
5633 "git", "diff-files", "-z", NULL
5636 static const char *status_list_other_argv[] = {
5637 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5640 static const char *status_list_no_head_argv[] = {
5641 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5644 static const char *update_index_argv[] = {
5645 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5648 /* Restore the previous line number to stay in the context or select a
5649 * line with something that can be updated. */
5650 static void
5651 status_restore(struct view *view)
5653 if (view->p_lineno >= view->lines)
5654 view->p_lineno = view->lines - 1;
5655 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5656 view->p_lineno++;
5657 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5658 view->p_lineno--;
5660 /* If the above fails, always skip the "On branch" line. */
5661 if (view->p_lineno < view->lines)
5662 view->lineno = view->p_lineno;
5663 else
5664 view->lineno = 1;
5666 if (view->lineno < view->offset)
5667 view->offset = view->lineno;
5668 else if (view->offset + view->height <= view->lineno)
5669 view->offset = view->lineno - view->height + 1;
5671 view->p_restore = FALSE;
5674 static void
5675 status_update_onbranch(void)
5677 static const char *paths[][2] = {
5678 { "rebase-apply/rebasing", "Rebasing" },
5679 { "rebase-apply/applying", "Applying mailbox" },
5680 { "rebase-apply/", "Rebasing mailbox" },
5681 { "rebase-merge/interactive", "Interactive rebase" },
5682 { "rebase-merge/", "Rebase merge" },
5683 { "MERGE_HEAD", "Merging" },
5684 { "BISECT_LOG", "Bisecting" },
5685 { "HEAD", "On branch" },
5687 char buf[SIZEOF_STR];
5688 struct stat stat;
5689 int i;
5691 if (is_initial_commit()) {
5692 string_copy(status_onbranch, "Initial commit");
5693 return;
5696 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5697 char *head = opt_head;
5699 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5700 lstat(buf, &stat) < 0)
5701 continue;
5703 if (!*opt_head) {
5704 struct io io = {};
5706 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5707 io_read_buf(&io, buf, sizeof(buf))) {
5708 head = buf;
5709 if (!prefixcmp(head, "refs/heads/"))
5710 head += STRING_SIZE("refs/heads/");
5714 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5715 string_copy(status_onbranch, opt_head);
5716 return;
5719 string_copy(status_onbranch, "Not currently on any branch");
5722 /* First parse staged info using git-diff-index(1), then parse unstaged
5723 * info using git-diff-files(1), and finally untracked files using
5724 * git-ls-files(1). */
5725 static bool
5726 status_open(struct view *view)
5728 reset_view(view);
5730 add_line_data(view, NULL, LINE_STAT_HEAD);
5731 status_update_onbranch();
5733 io_run_bg(update_index_argv);
5735 if (is_initial_commit()) {
5736 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5737 return FALSE;
5738 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5739 return FALSE;
5742 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5743 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5744 return FALSE;
5746 /* Restore the exact position or use the specialized restore
5747 * mode? */
5748 if (!view->p_restore)
5749 status_restore(view);
5750 return TRUE;
5753 static bool
5754 status_draw(struct view *view, struct line *line, unsigned int lineno)
5756 struct status *status = line->data;
5757 enum line_type type;
5758 const char *text;
5760 if (!status) {
5761 switch (line->type) {
5762 case LINE_STAT_STAGED:
5763 type = LINE_STAT_SECTION;
5764 text = "Changes to be committed:";
5765 break;
5767 case LINE_STAT_UNSTAGED:
5768 type = LINE_STAT_SECTION;
5769 text = "Changed but not updated:";
5770 break;
5772 case LINE_STAT_UNTRACKED:
5773 type = LINE_STAT_SECTION;
5774 text = "Untracked files:";
5775 break;
5777 case LINE_STAT_NONE:
5778 type = LINE_DEFAULT;
5779 text = " (no files)";
5780 break;
5782 case LINE_STAT_HEAD:
5783 type = LINE_STAT_HEAD;
5784 text = status_onbranch;
5785 break;
5787 default:
5788 return FALSE;
5790 } else {
5791 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5793 buf[0] = status->status;
5794 if (draw_text(view, line->type, buf, TRUE))
5795 return TRUE;
5796 type = LINE_DEFAULT;
5797 text = status->new.name;
5800 draw_text(view, type, text, TRUE);
5801 return TRUE;
5804 static enum request
5805 status_load_error(struct view *view, struct view *stage, const char *path)
5807 if (displayed_views() == 2 || display[current_view] != view)
5808 maximize_view(view);
5809 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5810 return REQ_NONE;
5813 static enum request
5814 status_enter(struct view *view, struct line *line)
5816 struct status *status = line->data;
5817 const char *oldpath = status ? status->old.name : NULL;
5818 /* Diffs for unmerged entries are empty when passing the new
5819 * path, so leave it empty. */
5820 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5821 const char *info;
5822 enum open_flags split;
5823 struct view *stage = VIEW(REQ_VIEW_STAGE);
5825 if (line->type == LINE_STAT_NONE ||
5826 (!status && line[1].type == LINE_STAT_NONE)) {
5827 report("No file to diff");
5828 return REQ_NONE;
5831 switch (line->type) {
5832 case LINE_STAT_STAGED:
5833 if (is_initial_commit()) {
5834 const char *no_head_diff_argv[] = {
5835 "git", "diff", "--no-color", "--patch-with-stat",
5836 "--", "/dev/null", newpath, NULL
5839 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5840 return status_load_error(view, stage, newpath);
5841 } else {
5842 const char *index_show_argv[] = {
5843 "git", "diff-index", "--root", "--patch-with-stat",
5844 "-C", "-M", "--cached", "HEAD", "--",
5845 oldpath, newpath, NULL
5848 if (!prepare_update(stage, index_show_argv, opt_cdup))
5849 return status_load_error(view, stage, newpath);
5852 if (status)
5853 info = "Staged changes to %s";
5854 else
5855 info = "Staged changes";
5856 break;
5858 case LINE_STAT_UNSTAGED:
5860 const char *files_show_argv[] = {
5861 "git", "diff-files", "--root", "--patch-with-stat",
5862 "-C", "-M", "--", oldpath, newpath, NULL
5865 if (!prepare_update(stage, files_show_argv, opt_cdup))
5866 return status_load_error(view, stage, newpath);
5867 if (status)
5868 info = "Unstaged changes to %s";
5869 else
5870 info = "Unstaged changes";
5871 break;
5873 case LINE_STAT_UNTRACKED:
5874 if (!newpath) {
5875 report("No file to show");
5876 return REQ_NONE;
5879 if (!suffixcmp(status->new.name, -1, "/")) {
5880 report("Cannot display a directory");
5881 return REQ_NONE;
5884 if (!prepare_update_file(stage, newpath))
5885 return status_load_error(view, stage, newpath);
5886 info = "Untracked file %s";
5887 break;
5889 case LINE_STAT_HEAD:
5890 return REQ_NONE;
5892 default:
5893 die("line type %d not handled in switch", line->type);
5896 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5897 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5898 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5899 if (status) {
5900 stage_status = *status;
5901 } else {
5902 memset(&stage_status, 0, sizeof(stage_status));
5905 stage_line_type = line->type;
5906 stage_chunks = 0;
5907 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5910 return REQ_NONE;
5913 static bool
5914 status_exists(struct status *status, enum line_type type)
5916 struct view *view = VIEW(REQ_VIEW_STATUS);
5917 unsigned long lineno;
5919 for (lineno = 0; lineno < view->lines; lineno++) {
5920 struct line *line = &view->line[lineno];
5921 struct status *pos = line->data;
5923 if (line->type != type)
5924 continue;
5925 if (!pos && (!status || !status->status) && line[1].data) {
5926 select_view_line(view, lineno);
5927 return TRUE;
5929 if (pos && !strcmp(status->new.name, pos->new.name)) {
5930 select_view_line(view, lineno);
5931 return TRUE;
5935 return FALSE;
5939 static bool
5940 status_update_prepare(struct io *io, enum line_type type)
5942 const char *staged_argv[] = {
5943 "git", "update-index", "-z", "--index-info", NULL
5945 const char *others_argv[] = {
5946 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5949 switch (type) {
5950 case LINE_STAT_STAGED:
5951 return io_run(io, staged_argv, opt_cdup, IO_WR);
5953 case LINE_STAT_UNSTAGED:
5954 case LINE_STAT_UNTRACKED:
5955 return io_run(io, others_argv, opt_cdup, IO_WR);
5957 default:
5958 die("line type %d not handled in switch", type);
5959 return FALSE;
5963 static bool
5964 status_update_write(struct io *io, struct status *status, enum line_type type)
5966 char buf[SIZEOF_STR];
5967 size_t bufsize = 0;
5969 switch (type) {
5970 case LINE_STAT_STAGED:
5971 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5972 status->old.mode,
5973 status->old.rev,
5974 status->old.name, 0))
5975 return FALSE;
5976 break;
5978 case LINE_STAT_UNSTAGED:
5979 case LINE_STAT_UNTRACKED:
5980 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5981 return FALSE;
5982 break;
5984 default:
5985 die("line type %d not handled in switch", type);
5988 return io_write(io, buf, bufsize);
5991 static bool
5992 status_update_file(struct status *status, enum line_type type)
5994 struct io io = {};
5995 bool result;
5997 if (!status_update_prepare(&io, type))
5998 return FALSE;
6000 result = status_update_write(&io, status, type);
6001 return io_done(&io) && result;
6004 static bool
6005 status_update_files(struct view *view, struct line *line)
6007 char buf[sizeof(view->ref)];
6008 struct io io = {};
6009 bool result = TRUE;
6010 struct line *pos = view->line + view->lines;
6011 int files = 0;
6012 int file, done;
6013 int cursor_y = -1, cursor_x = -1;
6015 if (!status_update_prepare(&io, line->type))
6016 return FALSE;
6018 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6019 files++;
6021 string_copy(buf, view->ref);
6022 getsyx(cursor_y, cursor_x);
6023 for (file = 0, done = 5; result && file < files; line++, file++) {
6024 int almost_done = file * 100 / files;
6026 if (almost_done > done) {
6027 done = almost_done;
6028 string_format(view->ref, "updating file %u of %u (%d%% done)",
6029 file, files, done);
6030 update_view_title(view);
6031 setsyx(cursor_y, cursor_x);
6032 doupdate();
6034 result = status_update_write(&io, line->data, line->type);
6036 string_copy(view->ref, buf);
6038 return io_done(&io) && result;
6041 static bool
6042 status_update(struct view *view)
6044 struct line *line = &view->line[view->lineno];
6046 assert(view->lines);
6048 if (!line->data) {
6049 /* This should work even for the "On branch" line. */
6050 if (line < view->line + view->lines && !line[1].data) {
6051 report("Nothing to update");
6052 return FALSE;
6055 if (!status_update_files(view, line + 1)) {
6056 report("Failed to update file status");
6057 return FALSE;
6060 } else if (!status_update_file(line->data, line->type)) {
6061 report("Failed to update file status");
6062 return FALSE;
6065 return TRUE;
6068 static bool
6069 status_revert(struct status *status, enum line_type type, bool has_none)
6071 if (!status || type != LINE_STAT_UNSTAGED) {
6072 if (type == LINE_STAT_STAGED) {
6073 report("Cannot revert changes to staged files");
6074 } else if (type == LINE_STAT_UNTRACKED) {
6075 report("Cannot revert changes to untracked files");
6076 } else if (has_none) {
6077 report("Nothing to revert");
6078 } else {
6079 report("Cannot revert changes to multiple files");
6082 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6083 char mode[10] = "100644";
6084 const char *reset_argv[] = {
6085 "git", "update-index", "--cacheinfo", mode,
6086 status->old.rev, status->old.name, NULL
6088 const char *checkout_argv[] = {
6089 "git", "checkout", "--", status->old.name, NULL
6092 if (status->status == 'U') {
6093 string_format(mode, "%5o", status->old.mode);
6095 if (status->old.mode == 0 && status->new.mode == 0) {
6096 reset_argv[2] = "--force-remove";
6097 reset_argv[3] = status->old.name;
6098 reset_argv[4] = NULL;
6101 if (!io_run_fg(reset_argv, opt_cdup))
6102 return FALSE;
6103 if (status->old.mode == 0 && status->new.mode == 0)
6104 return TRUE;
6107 return io_run_fg(checkout_argv, opt_cdup);
6110 return FALSE;
6113 static enum request
6114 status_request(struct view *view, enum request request, struct line *line)
6116 struct status *status = line->data;
6118 switch (request) {
6119 case REQ_STATUS_UPDATE:
6120 if (!status_update(view))
6121 return REQ_NONE;
6122 break;
6124 case REQ_STATUS_REVERT:
6125 if (!status_revert(status, line->type, status_has_none(view, line)))
6126 return REQ_NONE;
6127 break;
6129 case REQ_STATUS_MERGE:
6130 if (!status || status->status != 'U') {
6131 report("Merging only possible for files with unmerged status ('U').");
6132 return REQ_NONE;
6134 open_mergetool(status->new.name);
6135 break;
6137 case REQ_EDIT:
6138 if (!status)
6139 return request;
6140 if (status->status == 'D') {
6141 report("File has been deleted.");
6142 return REQ_NONE;
6145 open_editor(status->new.name);
6146 break;
6148 case REQ_VIEW_BLAME:
6149 if (status)
6150 opt_ref[0] = 0;
6151 return request;
6153 case REQ_ENTER:
6154 /* After returning the status view has been split to
6155 * show the stage view. No further reloading is
6156 * necessary. */
6157 return status_enter(view, line);
6159 case REQ_REFRESH:
6160 /* Simply reload the view. */
6161 break;
6163 default:
6164 return request;
6167 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6169 return REQ_NONE;
6172 static void
6173 status_select(struct view *view, struct line *line)
6175 struct status *status = line->data;
6176 char file[SIZEOF_STR] = "all files";
6177 const char *text;
6178 const char *key;
6180 if (status && !string_format(file, "'%s'", status->new.name))
6181 return;
6183 if (!status && line[1].type == LINE_STAT_NONE)
6184 line++;
6186 switch (line->type) {
6187 case LINE_STAT_STAGED:
6188 text = "Press %s to unstage %s for commit";
6189 break;
6191 case LINE_STAT_UNSTAGED:
6192 text = "Press %s to stage %s for commit";
6193 break;
6195 case LINE_STAT_UNTRACKED:
6196 text = "Press %s to stage %s for addition";
6197 break;
6199 case LINE_STAT_HEAD:
6200 case LINE_STAT_NONE:
6201 text = "Nothing to update";
6202 break;
6204 default:
6205 die("line type %d not handled in switch", line->type);
6208 if (status && status->status == 'U') {
6209 text = "Press %s to resolve conflict in %s";
6210 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6212 } else {
6213 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6216 string_format(view->ref, text, key, file);
6217 if (status)
6218 string_copy(opt_file, status->new.name);
6221 static bool
6222 status_grep(struct view *view, struct line *line)
6224 struct status *status = line->data;
6226 if (status) {
6227 const char buf[2] = { status->status, 0 };
6228 const char *text[] = { status->new.name, buf, NULL };
6230 return grep_text(view, text);
6233 return FALSE;
6236 static struct view_ops status_ops = {
6237 "file",
6238 NULL,
6239 status_open,
6240 NULL,
6241 status_draw,
6242 status_request,
6243 status_grep,
6244 status_select,
6248 static bool
6249 stage_diff_write(struct io *io, struct line *line, struct line *end)
6251 while (line < end) {
6252 if (!io_write(io, line->data, strlen(line->data)) ||
6253 !io_write(io, "\n", 1))
6254 return FALSE;
6255 line++;
6256 if (line->type == LINE_DIFF_CHUNK ||
6257 line->type == LINE_DIFF_HEADER)
6258 break;
6261 return TRUE;
6264 static struct line *
6265 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6267 for (; view->line < line; line--)
6268 if (line->type == type)
6269 return line;
6271 return NULL;
6274 static bool
6275 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6277 const char *apply_argv[SIZEOF_ARG] = {
6278 "git", "apply", "--whitespace=nowarn", NULL
6280 struct line *diff_hdr;
6281 struct io io = {};
6282 int argc = 3;
6284 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6285 if (!diff_hdr)
6286 return FALSE;
6288 if (!revert)
6289 apply_argv[argc++] = "--cached";
6290 if (revert || stage_line_type == LINE_STAT_STAGED)
6291 apply_argv[argc++] = "-R";
6292 apply_argv[argc++] = "-";
6293 apply_argv[argc++] = NULL;
6294 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6295 return FALSE;
6297 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6298 !stage_diff_write(&io, chunk, view->line + view->lines))
6299 chunk = NULL;
6301 io_done(&io);
6302 io_run_bg(update_index_argv);
6304 return chunk ? TRUE : FALSE;
6307 static bool
6308 stage_update(struct view *view, struct line *line)
6310 struct line *chunk = NULL;
6312 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6313 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6315 if (chunk) {
6316 if (!stage_apply_chunk(view, chunk, FALSE)) {
6317 report("Failed to apply chunk");
6318 return FALSE;
6321 } else if (!stage_status.status) {
6322 view = VIEW(REQ_VIEW_STATUS);
6324 for (line = view->line; line < view->line + view->lines; line++)
6325 if (line->type == stage_line_type)
6326 break;
6328 if (!status_update_files(view, line + 1)) {
6329 report("Failed to update files");
6330 return FALSE;
6333 } else if (!status_update_file(&stage_status, stage_line_type)) {
6334 report("Failed to update file");
6335 return FALSE;
6338 return TRUE;
6341 static bool
6342 stage_revert(struct view *view, struct line *line)
6344 struct line *chunk = NULL;
6346 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6347 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6349 if (chunk) {
6350 if (!prompt_yesno("Are you sure you want to revert changes?"))
6351 return FALSE;
6353 if (!stage_apply_chunk(view, chunk, TRUE)) {
6354 report("Failed to revert chunk");
6355 return FALSE;
6357 return TRUE;
6359 } else {
6360 return status_revert(stage_status.status ? &stage_status : NULL,
6361 stage_line_type, FALSE);
6366 static void
6367 stage_next(struct view *view, struct line *line)
6369 int i;
6371 if (!stage_chunks) {
6372 for (line = view->line; line < view->line + view->lines; line++) {
6373 if (line->type != LINE_DIFF_CHUNK)
6374 continue;
6376 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6377 report("Allocation failure");
6378 return;
6381 stage_chunk[stage_chunks++] = line - view->line;
6385 for (i = 0; i < stage_chunks; i++) {
6386 if (stage_chunk[i] > view->lineno) {
6387 do_scroll_view(view, stage_chunk[i] - view->lineno);
6388 report("Chunk %d of %d", i + 1, stage_chunks);
6389 return;
6393 report("No next chunk found");
6396 static enum request
6397 stage_request(struct view *view, enum request request, struct line *line)
6399 switch (request) {
6400 case REQ_STATUS_UPDATE:
6401 if (!stage_update(view, line))
6402 return REQ_NONE;
6403 break;
6405 case REQ_STATUS_REVERT:
6406 if (!stage_revert(view, line))
6407 return REQ_NONE;
6408 break;
6410 case REQ_STAGE_NEXT:
6411 if (stage_line_type == LINE_STAT_UNTRACKED) {
6412 report("File is untracked; press %s to add",
6413 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6414 return REQ_NONE;
6416 stage_next(view, line);
6417 return REQ_NONE;
6419 case REQ_EDIT:
6420 if (!stage_status.new.name[0])
6421 return request;
6422 if (stage_status.status == 'D') {
6423 report("File has been deleted.");
6424 return REQ_NONE;
6427 open_editor(stage_status.new.name);
6428 break;
6430 case REQ_REFRESH:
6431 /* Reload everything ... */
6432 break;
6434 case REQ_VIEW_BLAME:
6435 if (stage_status.new.name[0]) {
6436 string_copy(opt_file, stage_status.new.name);
6437 opt_ref[0] = 0;
6439 return request;
6441 case REQ_ENTER:
6442 return pager_request(view, request, line);
6444 default:
6445 return request;
6448 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6449 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6451 /* Check whether the staged entry still exists, and close the
6452 * stage view if it doesn't. */
6453 if (!status_exists(&stage_status, stage_line_type)) {
6454 status_restore(VIEW(REQ_VIEW_STATUS));
6455 return REQ_VIEW_CLOSE;
6458 if (stage_line_type == LINE_STAT_UNTRACKED) {
6459 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6460 report("Cannot display a directory");
6461 return REQ_NONE;
6464 if (!prepare_update_file(view, stage_status.new.name)) {
6465 report("Failed to open file: %s", strerror(errno));
6466 return REQ_NONE;
6469 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6471 return REQ_NONE;
6474 static struct view_ops stage_ops = {
6475 "line",
6476 NULL,
6477 NULL,
6478 pager_read,
6479 pager_draw,
6480 stage_request,
6481 pager_grep,
6482 pager_select,
6487 * Revision graph
6490 struct commit {
6491 char id[SIZEOF_REV]; /* SHA1 ID. */
6492 char title[128]; /* First line of the commit message. */
6493 const char *author; /* Author of the commit. */
6494 struct time time; /* Date from the author ident. */
6495 struct ref_list *refs; /* Repository references. */
6496 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6497 size_t graph_size; /* The width of the graph array. */
6498 bool has_parents; /* Rewritten --parents seen. */
6501 /* Size of rev graph with no "padding" columns */
6502 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6504 struct rev_graph {
6505 struct rev_graph *prev, *next, *parents;
6506 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6507 size_t size;
6508 struct commit *commit;
6509 size_t pos;
6510 unsigned int boundary:1;
6513 /* Parents of the commit being visualized. */
6514 static struct rev_graph graph_parents[4];
6516 /* The current stack of revisions on the graph. */
6517 static struct rev_graph graph_stacks[4] = {
6518 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6519 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6520 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6521 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6524 static inline bool
6525 graph_parent_is_merge(struct rev_graph *graph)
6527 return graph->parents->size > 1;
6530 static inline void
6531 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6533 struct commit *commit = graph->commit;
6535 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6536 commit->graph[commit->graph_size++] = symbol;
6539 static void
6540 clear_rev_graph(struct rev_graph *graph)
6542 graph->boundary = 0;
6543 graph->size = graph->pos = 0;
6544 graph->commit = NULL;
6545 memset(graph->parents, 0, sizeof(*graph->parents));
6548 static void
6549 done_rev_graph(struct rev_graph *graph)
6551 if (graph_parent_is_merge(graph) &&
6552 graph->pos < graph->size - 1 &&
6553 graph->next->size == graph->size + graph->parents->size - 1) {
6554 size_t i = graph->pos + graph->parents->size - 1;
6556 graph->commit->graph_size = i * 2;
6557 while (i < graph->next->size - 1) {
6558 append_to_rev_graph(graph, ' ');
6559 append_to_rev_graph(graph, '\\');
6560 i++;
6564 clear_rev_graph(graph);
6567 static void
6568 push_rev_graph(struct rev_graph *graph, const char *parent)
6570 int i;
6572 /* "Collapse" duplicate parents lines.
6574 * FIXME: This needs to also update update the drawn graph but
6575 * for now it just serves as a method for pruning graph lines. */
6576 for (i = 0; i < graph->size; i++)
6577 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6578 return;
6580 if (graph->size < SIZEOF_REVITEMS) {
6581 string_copy_rev(graph->rev[graph->size++], parent);
6585 static chtype
6586 get_rev_graph_symbol(struct rev_graph *graph)
6588 chtype symbol;
6590 if (graph->boundary)
6591 symbol = REVGRAPH_BOUND;
6592 else if (graph->parents->size == 0)
6593 symbol = REVGRAPH_INIT;
6594 else if (graph_parent_is_merge(graph))
6595 symbol = REVGRAPH_MERGE;
6596 else if (graph->pos >= graph->size)
6597 symbol = REVGRAPH_BRANCH;
6598 else
6599 symbol = REVGRAPH_COMMIT;
6601 return symbol;
6604 static void
6605 draw_rev_graph(struct rev_graph *graph)
6607 struct rev_filler {
6608 chtype separator, line;
6610 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6611 static struct rev_filler fillers[] = {
6612 { ' ', '|' },
6613 { '`', '.' },
6614 { '\'', ' ' },
6615 { '/', ' ' },
6617 chtype symbol = get_rev_graph_symbol(graph);
6618 struct rev_filler *filler;
6619 size_t i;
6621 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6622 filler = &fillers[DEFAULT];
6624 for (i = 0; i < graph->pos; i++) {
6625 append_to_rev_graph(graph, filler->line);
6626 if (graph_parent_is_merge(graph->prev) &&
6627 graph->prev->pos == i)
6628 filler = &fillers[RSHARP];
6630 append_to_rev_graph(graph, filler->separator);
6633 /* Place the symbol for this revision. */
6634 append_to_rev_graph(graph, symbol);
6636 if (graph->prev->size > graph->size)
6637 filler = &fillers[RDIAG];
6638 else
6639 filler = &fillers[DEFAULT];
6641 i++;
6643 for (; i < graph->size; i++) {
6644 append_to_rev_graph(graph, filler->separator);
6645 append_to_rev_graph(graph, filler->line);
6646 if (graph_parent_is_merge(graph->prev) &&
6647 i < graph->prev->pos + graph->parents->size)
6648 filler = &fillers[RSHARP];
6649 if (graph->prev->size > graph->size)
6650 filler = &fillers[LDIAG];
6653 if (graph->prev->size > graph->size) {
6654 append_to_rev_graph(graph, filler->separator);
6655 if (filler->line != ' ')
6656 append_to_rev_graph(graph, filler->line);
6660 /* Prepare the next rev graph */
6661 static void
6662 prepare_rev_graph(struct rev_graph *graph)
6664 size_t i;
6666 /* First, traverse all lines of revisions up to the active one. */
6667 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6668 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6669 break;
6671 push_rev_graph(graph->next, graph->rev[graph->pos]);
6674 /* Interleave the new revision parent(s). */
6675 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6676 push_rev_graph(graph->next, graph->parents->rev[i]);
6678 /* Lastly, put any remaining revisions. */
6679 for (i = graph->pos + 1; i < graph->size; i++)
6680 push_rev_graph(graph->next, graph->rev[i]);
6683 static void
6684 update_rev_graph(struct view *view, struct rev_graph *graph)
6686 /* If this is the finalizing update ... */
6687 if (graph->commit)
6688 prepare_rev_graph(graph);
6690 /* Graph visualization needs a one rev look-ahead,
6691 * so the first update doesn't visualize anything. */
6692 if (!graph->prev->commit)
6693 return;
6695 if (view->lines > 2)
6696 view->line[view->lines - 3].dirty = 1;
6697 if (view->lines > 1)
6698 view->line[view->lines - 2].dirty = 1;
6699 draw_rev_graph(graph->prev);
6700 done_rev_graph(graph->prev->prev);
6705 * Main view backend
6708 static const char *main_argv[SIZEOF_ARG] = {
6709 "git", "log", "--no-color", "--pretty=raw", "--parents",
6710 "--topo-order", "%(head)", NULL
6713 static bool
6714 main_draw(struct view *view, struct line *line, unsigned int lineno)
6716 struct commit *commit = line->data;
6718 if (!commit->author)
6719 return FALSE;
6721 if (opt_date && draw_date(view, &commit->time))
6722 return TRUE;
6724 if (opt_author && draw_author(view, commit->author))
6725 return TRUE;
6727 if (opt_rev_graph && commit->graph_size &&
6728 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6729 return TRUE;
6731 if (opt_show_refs && commit->refs) {
6732 size_t i;
6734 for (i = 0; i < commit->refs->size; i++) {
6735 struct ref *ref = commit->refs->refs[i];
6736 enum line_type type;
6738 if (ref->head)
6739 type = LINE_MAIN_HEAD;
6740 else if (ref->ltag)
6741 type = LINE_MAIN_LOCAL_TAG;
6742 else if (ref->tag)
6743 type = LINE_MAIN_TAG;
6744 else if (ref->tracked)
6745 type = LINE_MAIN_TRACKED;
6746 else if (ref->remote)
6747 type = LINE_MAIN_REMOTE;
6748 else
6749 type = LINE_MAIN_REF;
6751 if (draw_text(view, type, "[", TRUE) ||
6752 draw_text(view, type, ref->name, TRUE) ||
6753 draw_text(view, type, "]", TRUE))
6754 return TRUE;
6756 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6757 return TRUE;
6761 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6762 return TRUE;
6765 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6766 static bool
6767 main_read(struct view *view, char *line)
6769 static struct rev_graph *graph = graph_stacks;
6770 enum line_type type;
6771 struct commit *commit;
6773 if (!line) {
6774 int i;
6776 if (!view->lines && !view->parent)
6777 die("No revisions match the given arguments.");
6778 if (view->lines > 0) {
6779 commit = view->line[view->lines - 1].data;
6780 view->line[view->lines - 1].dirty = 1;
6781 if (!commit->author) {
6782 view->lines--;
6783 free(commit);
6784 graph->commit = NULL;
6787 update_rev_graph(view, graph);
6789 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6790 clear_rev_graph(&graph_stacks[i]);
6791 return TRUE;
6794 type = get_line_type(line);
6795 if (type == LINE_COMMIT) {
6796 commit = calloc(1, sizeof(struct commit));
6797 if (!commit)
6798 return FALSE;
6800 line += STRING_SIZE("commit ");
6801 if (*line == '-') {
6802 graph->boundary = 1;
6803 line++;
6806 string_copy_rev(commit->id, line);
6807 commit->refs = get_ref_list(commit->id);
6808 graph->commit = commit;
6809 add_line_data(view, commit, LINE_MAIN_COMMIT);
6811 while ((line = strchr(line, ' '))) {
6812 line++;
6813 push_rev_graph(graph->parents, line);
6814 commit->has_parents = TRUE;
6816 return TRUE;
6819 if (!view->lines)
6820 return TRUE;
6821 commit = view->line[view->lines - 1].data;
6823 switch (type) {
6824 case LINE_PARENT:
6825 if (commit->has_parents)
6826 break;
6827 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6828 break;
6830 case LINE_AUTHOR:
6831 parse_author_line(line + STRING_SIZE("author "),
6832 &commit->author, &commit->time);
6833 update_rev_graph(view, graph);
6834 graph = graph->next;
6835 break;
6837 default:
6838 /* Fill in the commit title if it has not already been set. */
6839 if (commit->title[0])
6840 break;
6842 /* Require titles to start with a non-space character at the
6843 * offset used by git log. */
6844 if (strncmp(line, " ", 4))
6845 break;
6846 line += 4;
6847 /* Well, if the title starts with a whitespace character,
6848 * try to be forgiving. Otherwise we end up with no title. */
6849 while (isspace(*line))
6850 line++;
6851 if (*line == '\0')
6852 break;
6853 /* FIXME: More graceful handling of titles; append "..." to
6854 * shortened titles, etc. */
6856 string_expand(commit->title, sizeof(commit->title), line, 1);
6857 view->line[view->lines - 1].dirty = 1;
6860 return TRUE;
6863 static enum request
6864 main_request(struct view *view, enum request request, struct line *line)
6866 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6868 switch (request) {
6869 case REQ_ENTER:
6870 open_view(view, REQ_VIEW_DIFF, flags);
6871 break;
6872 case REQ_REFRESH:
6873 load_refs();
6874 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6875 break;
6876 default:
6877 return request;
6880 return REQ_NONE;
6883 static bool
6884 grep_refs(struct ref_list *list, regex_t *regex)
6886 regmatch_t pmatch;
6887 size_t i;
6889 if (!opt_show_refs || !list)
6890 return FALSE;
6892 for (i = 0; i < list->size; i++) {
6893 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6894 return TRUE;
6897 return FALSE;
6900 static bool
6901 main_grep(struct view *view, struct line *line)
6903 struct commit *commit = line->data;
6904 const char *text[] = {
6905 commit->title,
6906 opt_author ? commit->author : "",
6907 mkdate(&commit->time, opt_date),
6908 NULL
6911 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6914 static void
6915 main_select(struct view *view, struct line *line)
6917 struct commit *commit = line->data;
6919 string_copy_rev(view->ref, commit->id);
6920 string_copy_rev(ref_commit, view->ref);
6923 static struct view_ops main_ops = {
6924 "commit",
6925 main_argv,
6926 NULL,
6927 main_read,
6928 main_draw,
6929 main_request,
6930 main_grep,
6931 main_select,
6936 * Status management
6939 /* Whether or not the curses interface has been initialized. */
6940 static bool cursed = FALSE;
6942 /* Terminal hacks and workarounds. */
6943 static bool use_scroll_redrawwin;
6944 static bool use_scroll_status_wclear;
6946 /* The status window is used for polling keystrokes. */
6947 static WINDOW *status_win;
6949 /* Reading from the prompt? */
6950 static bool input_mode = FALSE;
6952 static bool status_empty = FALSE;
6954 /* Update status and title window. */
6955 static void
6956 report(const char *msg, ...)
6958 struct view *view = display[current_view];
6960 if (input_mode)
6961 return;
6963 if (!view) {
6964 char buf[SIZEOF_STR];
6965 va_list args;
6967 va_start(args, msg);
6968 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6969 buf[sizeof(buf) - 1] = 0;
6970 buf[sizeof(buf) - 2] = '.';
6971 buf[sizeof(buf) - 3] = '.';
6972 buf[sizeof(buf) - 4] = '.';
6974 va_end(args);
6975 die("%s", buf);
6978 if (!status_empty || *msg) {
6979 va_list args;
6981 va_start(args, msg);
6983 wmove(status_win, 0, 0);
6984 if (view->has_scrolled && use_scroll_status_wclear)
6985 wclear(status_win);
6986 if (*msg) {
6987 vwprintw(status_win, msg, args);
6988 status_empty = FALSE;
6989 } else {
6990 status_empty = TRUE;
6992 wclrtoeol(status_win);
6993 wnoutrefresh(status_win);
6995 va_end(args);
6998 update_view_title(view);
7001 static void
7002 init_display(void)
7004 const char *term;
7005 int x, y;
7007 /* Initialize the curses library */
7008 if (isatty(STDIN_FILENO)) {
7009 cursed = !!initscr();
7010 opt_tty = stdin;
7011 } else {
7012 /* Leave stdin and stdout alone when acting as a pager. */
7013 opt_tty = fopen("/dev/tty", "r+");
7014 if (!opt_tty)
7015 die("Failed to open /dev/tty");
7016 cursed = !!newterm(NULL, opt_tty, opt_tty);
7019 if (!cursed)
7020 die("Failed to initialize curses");
7022 nonl(); /* Disable conversion and detect newlines from input. */
7023 cbreak(); /* Take input chars one at a time, no wait for \n */
7024 noecho(); /* Don't echo input */
7025 leaveok(stdscr, FALSE);
7027 if (has_colors())
7028 init_colors();
7030 getmaxyx(stdscr, y, x);
7031 status_win = newwin(1, 0, y - 1, 0);
7032 if (!status_win)
7033 die("Failed to create status window");
7035 /* Enable keyboard mapping */
7036 keypad(status_win, TRUE);
7037 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7039 TABSIZE = opt_tab_size;
7041 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7042 if (term && !strcmp(term, "gnome-terminal")) {
7043 /* In the gnome-terminal-emulator, the message from
7044 * scrolling up one line when impossible followed by
7045 * scrolling down one line causes corruption of the
7046 * status line. This is fixed by calling wclear. */
7047 use_scroll_status_wclear = TRUE;
7048 use_scroll_redrawwin = FALSE;
7050 } else if (term && !strcmp(term, "xrvt-xpm")) {
7051 /* No problems with full optimizations in xrvt-(unicode)
7052 * and aterm. */
7053 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7055 } else {
7056 /* When scrolling in (u)xterm the last line in the
7057 * scrolling direction will update slowly. */
7058 use_scroll_redrawwin = TRUE;
7059 use_scroll_status_wclear = FALSE;
7063 static int
7064 get_input(int prompt_position)
7066 struct view *view;
7067 int i, key, cursor_y, cursor_x;
7068 bool loading = FALSE;
7070 if (prompt_position)
7071 input_mode = TRUE;
7073 while (TRUE) {
7074 foreach_view (view, i) {
7075 update_view(view);
7076 if (view_is_displayed(view) && view->has_scrolled &&
7077 use_scroll_redrawwin)
7078 redrawwin(view->win);
7079 view->has_scrolled = FALSE;
7080 if (view->pipe)
7081 loading = TRUE;
7084 /* Update the cursor position. */
7085 if (prompt_position) {
7086 getbegyx(status_win, cursor_y, cursor_x);
7087 cursor_x = prompt_position;
7088 } else {
7089 view = display[current_view];
7090 getbegyx(view->win, cursor_y, cursor_x);
7091 cursor_x = view->width - 1;
7092 cursor_y += view->lineno - view->offset;
7094 setsyx(cursor_y, cursor_x);
7096 /* Refresh, accept single keystroke of input */
7097 doupdate();
7098 nodelay(status_win, loading);
7099 key = wgetch(status_win);
7101 /* wgetch() with nodelay() enabled returns ERR when
7102 * there's no input. */
7103 if (key == ERR) {
7105 } else if (key == KEY_RESIZE) {
7106 int height, width;
7108 getmaxyx(stdscr, height, width);
7110 wresize(status_win, 1, width);
7111 mvwin(status_win, height - 1, 0);
7112 wnoutrefresh(status_win);
7113 resize_display();
7114 redraw_display(TRUE);
7116 } else {
7117 input_mode = FALSE;
7118 return key;
7123 static char *
7124 prompt_input(const char *prompt, input_handler handler, void *data)
7126 enum input_status status = INPUT_OK;
7127 static char buf[SIZEOF_STR];
7128 size_t pos = 0;
7130 buf[pos] = 0;
7132 while (status == INPUT_OK || status == INPUT_SKIP) {
7133 int key;
7135 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7136 wclrtoeol(status_win);
7138 key = get_input(pos + 1);
7139 switch (key) {
7140 case KEY_RETURN:
7141 case KEY_ENTER:
7142 case '\n':
7143 status = pos ? INPUT_STOP : INPUT_CANCEL;
7144 break;
7146 case KEY_BACKSPACE:
7147 if (pos > 0)
7148 buf[--pos] = 0;
7149 else
7150 status = INPUT_CANCEL;
7151 break;
7153 case KEY_ESC:
7154 status = INPUT_CANCEL;
7155 break;
7157 default:
7158 if (pos >= sizeof(buf)) {
7159 report("Input string too long");
7160 return NULL;
7163 status = handler(data, buf, key);
7164 if (status == INPUT_OK)
7165 buf[pos++] = (char) key;
7169 /* Clear the status window */
7170 status_empty = FALSE;
7171 report("");
7173 if (status == INPUT_CANCEL)
7174 return NULL;
7176 buf[pos++] = 0;
7178 return buf;
7181 static enum input_status
7182 prompt_yesno_handler(void *data, char *buf, int c)
7184 if (c == 'y' || c == 'Y')
7185 return INPUT_STOP;
7186 if (c == 'n' || c == 'N')
7187 return INPUT_CANCEL;
7188 return INPUT_SKIP;
7191 static bool
7192 prompt_yesno(const char *prompt)
7194 char prompt2[SIZEOF_STR];
7196 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7197 return FALSE;
7199 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7202 static enum input_status
7203 read_prompt_handler(void *data, char *buf, int c)
7205 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7208 static char *
7209 read_prompt(const char *prompt)
7211 return prompt_input(prompt, read_prompt_handler, NULL);
7214 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7216 enum input_status status = INPUT_OK;
7217 int size = 0;
7219 while (items[size].text)
7220 size++;
7222 while (status == INPUT_OK) {
7223 const struct menu_item *item = &items[*selected];
7224 int key;
7225 int i;
7227 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7228 prompt, *selected + 1, size);
7229 if (item->hotkey)
7230 wprintw(status_win, "[%c] ", (char) item->hotkey);
7231 wprintw(status_win, "%s", item->text);
7232 wclrtoeol(status_win);
7234 key = get_input(COLS - 1);
7235 switch (key) {
7236 case KEY_RETURN:
7237 case KEY_ENTER:
7238 case '\n':
7239 status = INPUT_STOP;
7240 break;
7242 case KEY_LEFT:
7243 case KEY_UP:
7244 *selected = *selected - 1;
7245 if (*selected < 0)
7246 *selected = size - 1;
7247 break;
7249 case KEY_RIGHT:
7250 case KEY_DOWN:
7251 *selected = (*selected + 1) % size;
7252 break;
7254 case KEY_ESC:
7255 status = INPUT_CANCEL;
7256 break;
7258 default:
7259 for (i = 0; items[i].text; i++)
7260 if (items[i].hotkey == key) {
7261 *selected = i;
7262 status = INPUT_STOP;
7263 break;
7268 /* Clear the status window */
7269 status_empty = FALSE;
7270 report("");
7272 return status != INPUT_CANCEL;
7276 * Repository properties
7279 static struct ref **refs = NULL;
7280 static size_t refs_size = 0;
7281 static struct ref *refs_head = NULL;
7283 static struct ref_list **ref_lists = NULL;
7284 static size_t ref_lists_size = 0;
7286 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7287 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7288 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7290 static int
7291 compare_refs(const void *ref1_, const void *ref2_)
7293 const struct ref *ref1 = *(const struct ref **)ref1_;
7294 const struct ref *ref2 = *(const struct ref **)ref2_;
7296 if (ref1->tag != ref2->tag)
7297 return ref2->tag - ref1->tag;
7298 if (ref1->ltag != ref2->ltag)
7299 return ref2->ltag - ref2->ltag;
7300 if (ref1->head != ref2->head)
7301 return ref2->head - ref1->head;
7302 if (ref1->tracked != ref2->tracked)
7303 return ref2->tracked - ref1->tracked;
7304 if (ref1->remote != ref2->remote)
7305 return ref2->remote - ref1->remote;
7306 return strcmp(ref1->name, ref2->name);
7309 static void
7310 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7312 size_t i;
7314 for (i = 0; i < refs_size; i++)
7315 if (!visitor(data, refs[i]))
7316 break;
7319 static struct ref *
7320 get_ref_head()
7322 return refs_head;
7325 static struct ref_list *
7326 get_ref_list(const char *id)
7328 struct ref_list *list;
7329 size_t i;
7331 for (i = 0; i < ref_lists_size; i++)
7332 if (!strcmp(id, ref_lists[i]->id))
7333 return ref_lists[i];
7335 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7336 return NULL;
7337 list = calloc(1, sizeof(*list));
7338 if (!list)
7339 return NULL;
7341 for (i = 0; i < refs_size; i++) {
7342 if (!strcmp(id, refs[i]->id) &&
7343 realloc_refs_list(&list->refs, list->size, 1))
7344 list->refs[list->size++] = refs[i];
7347 if (!list->refs) {
7348 free(list);
7349 return NULL;
7352 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7353 ref_lists[ref_lists_size++] = list;
7354 return list;
7357 static int
7358 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7360 struct ref *ref = NULL;
7361 bool tag = FALSE;
7362 bool ltag = FALSE;
7363 bool remote = FALSE;
7364 bool tracked = FALSE;
7365 bool head = FALSE;
7366 int from = 0, to = refs_size - 1;
7368 if (!prefixcmp(name, "refs/tags/")) {
7369 if (!suffixcmp(name, namelen, "^{}")) {
7370 namelen -= 3;
7371 name[namelen] = 0;
7372 } else {
7373 ltag = TRUE;
7376 tag = TRUE;
7377 namelen -= STRING_SIZE("refs/tags/");
7378 name += STRING_SIZE("refs/tags/");
7380 } else if (!prefixcmp(name, "refs/remotes/")) {
7381 remote = TRUE;
7382 namelen -= STRING_SIZE("refs/remotes/");
7383 name += STRING_SIZE("refs/remotes/");
7384 tracked = !strcmp(opt_remote, name);
7386 } else if (!prefixcmp(name, "refs/heads/")) {
7387 namelen -= STRING_SIZE("refs/heads/");
7388 name += STRING_SIZE("refs/heads/");
7389 if (!strncmp(opt_head, name, namelen))
7390 return OK;
7392 } else if (!strcmp(name, "HEAD")) {
7393 head = TRUE;
7394 if (*opt_head) {
7395 namelen = strlen(opt_head);
7396 name = opt_head;
7400 /* If we are reloading or it's an annotated tag, replace the
7401 * previous SHA1 with the resolved commit id; relies on the fact
7402 * git-ls-remote lists the commit id of an annotated tag right
7403 * before the commit id it points to. */
7404 while (from <= to) {
7405 size_t pos = (to + from) / 2;
7406 int cmp = strcmp(name, refs[pos]->name);
7408 if (!cmp) {
7409 ref = refs[pos];
7410 break;
7413 if (cmp < 0)
7414 to = pos - 1;
7415 else
7416 from = pos + 1;
7419 if (!ref) {
7420 if (!realloc_refs(&refs, refs_size, 1))
7421 return ERR;
7422 ref = calloc(1, sizeof(*ref) + namelen);
7423 if (!ref)
7424 return ERR;
7425 memmove(refs + from + 1, refs + from,
7426 (refs_size - from) * sizeof(*refs));
7427 refs[from] = ref;
7428 strncpy(ref->name, name, namelen);
7429 refs_size++;
7432 ref->head = head;
7433 ref->tag = tag;
7434 ref->ltag = ltag;
7435 ref->remote = remote;
7436 ref->tracked = tracked;
7437 string_copy_rev(ref->id, id);
7439 if (head)
7440 refs_head = ref;
7441 return OK;
7444 static int
7445 load_refs(void)
7447 const char *head_argv[] = {
7448 "git", "symbolic-ref", "HEAD", NULL
7450 static const char *ls_remote_argv[SIZEOF_ARG] = {
7451 "git", "ls-remote", opt_git_dir, NULL
7453 static bool init = FALSE;
7454 size_t i;
7456 if (!init) {
7457 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7458 die("TIG_LS_REMOTE contains too many arguments");
7459 init = TRUE;
7462 if (!*opt_git_dir)
7463 return OK;
7465 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7466 !prefixcmp(opt_head, "refs/heads/")) {
7467 char *offset = opt_head + STRING_SIZE("refs/heads/");
7469 memmove(opt_head, offset, strlen(offset) + 1);
7472 refs_head = NULL;
7473 for (i = 0; i < refs_size; i++)
7474 refs[i]->id[0] = 0;
7476 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7477 return ERR;
7479 /* Update the ref lists to reflect changes. */
7480 for (i = 0; i < ref_lists_size; i++) {
7481 struct ref_list *list = ref_lists[i];
7482 size_t old, new;
7484 for (old = new = 0; old < list->size; old++)
7485 if (!strcmp(list->id, list->refs[old]->id))
7486 list->refs[new++] = list->refs[old];
7487 list->size = new;
7490 return OK;
7493 static void
7494 set_remote_branch(const char *name, const char *value, size_t valuelen)
7496 if (!strcmp(name, ".remote")) {
7497 string_ncopy(opt_remote, value, valuelen);
7499 } else if (*opt_remote && !strcmp(name, ".merge")) {
7500 size_t from = strlen(opt_remote);
7502 if (!prefixcmp(value, "refs/heads/"))
7503 value += STRING_SIZE("refs/heads/");
7505 if (!string_format_from(opt_remote, &from, "/%s", value))
7506 opt_remote[0] = 0;
7510 static void
7511 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7513 const char *argv[SIZEOF_ARG] = { name, "=" };
7514 int argc = 1 + (cmd == option_set_command);
7515 int error = ERR;
7517 if (!argv_from_string(argv, &argc, value))
7518 config_msg = "Too many option arguments";
7519 else
7520 error = cmd(argc, argv);
7522 if (error == ERR)
7523 warn("Option 'tig.%s': %s", name, config_msg);
7526 static bool
7527 set_environment_variable(const char *name, const char *value)
7529 size_t len = strlen(name) + 1 + strlen(value) + 1;
7530 char *env = malloc(len);
7532 if (env &&
7533 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7534 putenv(env) == 0)
7535 return TRUE;
7536 free(env);
7537 return FALSE;
7540 static void
7541 set_work_tree(const char *value)
7543 char cwd[SIZEOF_STR];
7545 if (!getcwd(cwd, sizeof(cwd)))
7546 die("Failed to get cwd path: %s", strerror(errno));
7547 if (chdir(opt_git_dir) < 0)
7548 die("Failed to chdir(%s): %s", strerror(errno));
7549 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7550 die("Failed to get git path: %s", strerror(errno));
7551 if (chdir(cwd) < 0)
7552 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7553 if (chdir(value) < 0)
7554 die("Failed to chdir(%s): %s", value, strerror(errno));
7555 if (!getcwd(cwd, sizeof(cwd)))
7556 die("Failed to get cwd path: %s", strerror(errno));
7557 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7558 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7559 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7560 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7561 opt_is_inside_work_tree = TRUE;
7564 static int
7565 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7567 if (!strcmp(name, "i18n.commitencoding"))
7568 string_ncopy(opt_encoding, value, valuelen);
7570 else if (!strcmp(name, "core.editor"))
7571 string_ncopy(opt_editor, value, valuelen);
7573 else if (!strcmp(name, "core.worktree"))
7574 set_work_tree(value);
7576 else if (!prefixcmp(name, "tig.color."))
7577 set_repo_config_option(name + 10, value, option_color_command);
7579 else if (!prefixcmp(name, "tig.bind."))
7580 set_repo_config_option(name + 9, value, option_bind_command);
7582 else if (!prefixcmp(name, "tig."))
7583 set_repo_config_option(name + 4, value, option_set_command);
7585 else if (*opt_head && !prefixcmp(name, "branch.") &&
7586 !strncmp(name + 7, opt_head, strlen(opt_head)))
7587 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7589 return OK;
7592 static int
7593 load_git_config(void)
7595 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7597 return io_run_load(config_list_argv, "=", read_repo_config_option);
7600 static int
7601 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7603 if (!opt_git_dir[0]) {
7604 string_ncopy(opt_git_dir, name, namelen);
7606 } else if (opt_is_inside_work_tree == -1) {
7607 /* This can be 3 different values depending on the
7608 * version of git being used. If git-rev-parse does not
7609 * understand --is-inside-work-tree it will simply echo
7610 * the option else either "true" or "false" is printed.
7611 * Default to true for the unknown case. */
7612 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7614 } else if (*name == '.') {
7615 string_ncopy(opt_cdup, name, namelen);
7617 } else {
7618 string_ncopy(opt_prefix, name, namelen);
7621 return OK;
7624 static int
7625 load_repo_info(void)
7627 const char *rev_parse_argv[] = {
7628 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7629 "--show-cdup", "--show-prefix", NULL
7632 return io_run_load(rev_parse_argv, "=", read_repo_info);
7637 * Main
7640 static const char usage[] =
7641 "tig " TIG_VERSION " (" __DATE__ ")\n"
7642 "\n"
7643 "Usage: tig [options] [revs] [--] [paths]\n"
7644 " or: tig show [options] [revs] [--] [paths]\n"
7645 " or: tig blame [rev] path\n"
7646 " or: tig status\n"
7647 " or: tig < [git command output]\n"
7648 "\n"
7649 "Options:\n"
7650 " -v, --version Show version and exit\n"
7651 " -h, --help Show help message and exit";
7653 static void __NORETURN
7654 quit(int sig)
7656 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7657 if (cursed)
7658 endwin();
7659 exit(0);
7662 static void __NORETURN
7663 die(const char *err, ...)
7665 va_list args;
7667 endwin();
7669 va_start(args, err);
7670 fputs("tig: ", stderr);
7671 vfprintf(stderr, err, args);
7672 fputs("\n", stderr);
7673 va_end(args);
7675 exit(1);
7678 static void
7679 warn(const char *msg, ...)
7681 va_list args;
7683 va_start(args, msg);
7684 fputs("tig warning: ", stderr);
7685 vfprintf(stderr, msg, args);
7686 fputs("\n", stderr);
7687 va_end(args);
7690 static enum request
7691 parse_options(int argc, const char *argv[])
7693 enum request request = REQ_VIEW_MAIN;
7694 const char *subcommand;
7695 bool seen_dashdash = FALSE;
7696 /* XXX: This is vulnerable to the user overriding options
7697 * required for the main view parser. */
7698 const char *custom_argv[SIZEOF_ARG] = {
7699 "git", "log", "--no-color", "--pretty=raw", "--parents",
7700 "--topo-order", NULL
7702 int i, j = 6;
7704 if (!isatty(STDIN_FILENO)) {
7705 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7706 return REQ_VIEW_PAGER;
7709 if (argc <= 1)
7710 return REQ_NONE;
7712 subcommand = argv[1];
7713 if (!strcmp(subcommand, "status")) {
7714 if (argc > 2)
7715 warn("ignoring arguments after `%s'", subcommand);
7716 return REQ_VIEW_STATUS;
7718 } else if (!strcmp(subcommand, "blame")) {
7719 if (argc <= 2 || argc > 4)
7720 die("invalid number of options to blame\n\n%s", usage);
7722 i = 2;
7723 if (argc == 4) {
7724 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7725 i++;
7728 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7729 return REQ_VIEW_BLAME;
7731 } else if (!strcmp(subcommand, "show")) {
7732 request = REQ_VIEW_DIFF;
7734 } else {
7735 subcommand = NULL;
7738 if (subcommand) {
7739 custom_argv[1] = subcommand;
7740 j = 2;
7743 for (i = 1 + !!subcommand; i < argc; i++) {
7744 const char *opt = argv[i];
7746 if (seen_dashdash || !strcmp(opt, "--")) {
7747 seen_dashdash = TRUE;
7749 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7750 printf("tig version %s\n", TIG_VERSION);
7751 quit(0);
7753 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7754 printf("%s\n", usage);
7755 quit(0);
7758 custom_argv[j++] = opt;
7759 if (j >= ARRAY_SIZE(custom_argv))
7760 die("command too long");
7763 if (!prepare_update(VIEW(request), custom_argv, NULL))
7764 die("Failed to format arguments");
7766 return request;
7770 main(int argc, const char *argv[])
7772 const char *codeset = "UTF-8";
7773 enum request request = parse_options(argc, argv);
7774 struct view *view;
7775 size_t i;
7777 signal(SIGINT, quit);
7778 signal(SIGPIPE, SIG_IGN);
7780 if (setlocale(LC_ALL, "")) {
7781 codeset = nl_langinfo(CODESET);
7784 if (load_repo_info() == ERR)
7785 die("Failed to load repo info.");
7787 if (load_options() == ERR)
7788 die("Failed to load user config.");
7790 if (load_git_config() == ERR)
7791 die("Failed to load repo config.");
7793 /* Require a git repository unless when running in pager mode. */
7794 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7795 die("Not a git repository");
7797 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7798 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7799 if (opt_iconv_in == ICONV_NONE)
7800 die("Failed to initialize character set conversion");
7803 if (codeset && strcmp(codeset, "UTF-8")) {
7804 opt_iconv_out = iconv_open(codeset, "UTF-8");
7805 if (opt_iconv_out == ICONV_NONE)
7806 die("Failed to initialize character set conversion");
7809 if (load_refs() == ERR)
7810 die("Failed to load refs.");
7812 foreach_view (view, i)
7813 if (!argv_from_env(view->ops->argv, view->cmd_env))
7814 die("Too many arguments in the `%s` environment variable",
7815 view->cmd_env);
7817 init_display();
7819 if (request != REQ_NONE)
7820 open_view(NULL, request, OPEN_PREPARED);
7821 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7823 while (view_driver(display[current_view], request)) {
7824 int key = get_input(0);
7826 view = display[current_view];
7827 request = get_keybinding(view->keymap, key);
7829 /* Some low-level request handling. This keeps access to
7830 * status_win restricted. */
7831 switch (request) {
7832 case REQ_NONE:
7833 report("Unknown key, press %s for help",
7834 get_key(view->keymap, REQ_VIEW_HELP));
7835 break;
7836 case REQ_PROMPT:
7838 char *cmd = read_prompt(":");
7840 if (cmd && isdigit(*cmd)) {
7841 int lineno = view->lineno + 1;
7843 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7844 select_view_line(view, lineno - 1);
7845 report("");
7846 } else {
7847 report("Unable to parse '%s' as a line number", cmd);
7850 } else if (cmd) {
7851 struct view *next = VIEW(REQ_VIEW_PAGER);
7852 const char *argv[SIZEOF_ARG] = { "git" };
7853 int argc = 1;
7855 /* When running random commands, initially show the
7856 * command in the title. However, it maybe later be
7857 * overwritten if a commit line is selected. */
7858 string_ncopy(next->ref, cmd, strlen(cmd));
7860 if (!argv_from_string(argv, &argc, cmd)) {
7861 report("Too many arguments");
7862 } else if (!prepare_update(next, argv, NULL)) {
7863 report("Failed to format command");
7864 } else {
7865 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7869 request = REQ_NONE;
7870 break;
7872 case REQ_SEARCH:
7873 case REQ_SEARCH_BACK:
7875 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7876 char *search = read_prompt(prompt);
7878 if (search)
7879 string_ncopy(opt_search, search, strlen(search));
7880 else if (*opt_search)
7881 request = request == REQ_SEARCH ?
7882 REQ_FIND_NEXT :
7883 REQ_FIND_PREV;
7884 else
7885 request = REQ_NONE;
7886 break;
7888 default:
7889 break;
7893 quit(0);
7895 return 0;