Added support for displaying dates in localtime.
[tig.git] / tig.c
blob7cdd3eaa3371202ed2016eec4cb2e9ec03545295
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;
1767 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1768 if (req != REQ_NONE)
1769 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1774 * User config file handling.
1777 static int config_lineno;
1778 static bool config_errors;
1779 static const char *config_msg;
1781 static const struct enum_map color_map[] = {
1782 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1783 COLOR_MAP(DEFAULT),
1784 COLOR_MAP(BLACK),
1785 COLOR_MAP(BLUE),
1786 COLOR_MAP(CYAN),
1787 COLOR_MAP(GREEN),
1788 COLOR_MAP(MAGENTA),
1789 COLOR_MAP(RED),
1790 COLOR_MAP(WHITE),
1791 COLOR_MAP(YELLOW),
1794 static const struct enum_map attr_map[] = {
1795 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1796 ATTR_MAP(NORMAL),
1797 ATTR_MAP(BLINK),
1798 ATTR_MAP(BOLD),
1799 ATTR_MAP(DIM),
1800 ATTR_MAP(REVERSE),
1801 ATTR_MAP(STANDOUT),
1802 ATTR_MAP(UNDERLINE),
1805 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1807 static int parse_step(double *opt, const char *arg)
1809 *opt = atoi(arg);
1810 if (!strchr(arg, '%'))
1811 return OK;
1813 /* "Shift down" so 100% and 1 does not conflict. */
1814 *opt = (*opt - 1) / 100;
1815 if (*opt >= 1.0) {
1816 *opt = 0.99;
1817 config_msg = "Step value larger than 100%";
1818 return ERR;
1820 if (*opt < 0.0) {
1821 *opt = 1;
1822 config_msg = "Invalid step value";
1823 return ERR;
1825 return OK;
1828 static int
1829 parse_int(int *opt, const char *arg, int min, int max)
1831 int value = atoi(arg);
1833 if (min <= value && value <= max) {
1834 *opt = value;
1835 return OK;
1838 config_msg = "Integer value out of bound";
1839 return ERR;
1842 static bool
1843 set_color(int *color, const char *name)
1845 if (map_enum(color, color_map, name))
1846 return TRUE;
1847 if (!prefixcmp(name, "color"))
1848 return parse_int(color, name + 5, 0, 255) == OK;
1849 return FALSE;
1852 /* Wants: object fgcolor bgcolor [attribute] */
1853 static int
1854 option_color_command(int argc, const char *argv[])
1856 struct line_info *info;
1858 if (argc < 3) {
1859 config_msg = "Wrong number of arguments given to color command";
1860 return ERR;
1863 info = get_line_info(argv[0]);
1864 if (!info) {
1865 static const struct enum_map obsolete[] = {
1866 ENUM_MAP("main-delim", LINE_DELIMITER),
1867 ENUM_MAP("main-date", LINE_DATE),
1868 ENUM_MAP("main-author", LINE_AUTHOR),
1870 int index;
1872 if (!map_enum(&index, obsolete, argv[0])) {
1873 config_msg = "Unknown color name";
1874 return ERR;
1876 info = &line_info[index];
1879 if (!set_color(&info->fg, argv[1]) ||
1880 !set_color(&info->bg, argv[2])) {
1881 config_msg = "Unknown color";
1882 return ERR;
1885 info->attr = 0;
1886 while (argc-- > 3) {
1887 int attr;
1889 if (!set_attribute(&attr, argv[argc])) {
1890 config_msg = "Unknown attribute";
1891 return ERR;
1893 info->attr |= attr;
1896 return OK;
1899 static int parse_bool(bool *opt, const char *arg)
1901 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1902 ? TRUE : FALSE;
1903 return OK;
1906 static int parse_enum_do(unsigned int *opt, const char *arg,
1907 const struct enum_map *map, size_t map_size)
1909 bool is_true;
1911 assert(map_size > 1);
1913 if (map_enum_do(map, map_size, (int *) opt, arg))
1914 return OK;
1916 if (parse_bool(&is_true, arg) != OK)
1917 return ERR;
1919 *opt = is_true ? map[1].value : map[0].value;
1920 return OK;
1923 #define parse_enum(opt, arg, map) \
1924 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1926 static int
1927 parse_string(char *opt, const char *arg, size_t optsize)
1929 int arglen = strlen(arg);
1931 switch (arg[0]) {
1932 case '\"':
1933 case '\'':
1934 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1935 config_msg = "Unmatched quotation";
1936 return ERR;
1938 arg += 1; arglen -= 2;
1939 default:
1940 string_ncopy_do(opt, optsize, arg, arglen);
1941 return OK;
1945 /* Wants: name = value */
1946 static int
1947 option_set_command(int argc, const char *argv[])
1949 if (argc != 3) {
1950 config_msg = "Wrong number of arguments given to set command";
1951 return ERR;
1954 if (strcmp(argv[1], "=")) {
1955 config_msg = "No value assigned";
1956 return ERR;
1959 if (!strcmp(argv[0], "show-author"))
1960 return parse_enum(&opt_author, argv[2], author_map);
1962 if (!strcmp(argv[0], "show-date"))
1963 return parse_enum(&opt_date, argv[2], date_map);
1965 if (!strcmp(argv[0], "show-rev-graph"))
1966 return parse_bool(&opt_rev_graph, argv[2]);
1968 if (!strcmp(argv[0], "show-refs"))
1969 return parse_bool(&opt_show_refs, argv[2]);
1971 if (!strcmp(argv[0], "show-line-numbers"))
1972 return parse_bool(&opt_line_number, argv[2]);
1974 if (!strcmp(argv[0], "line-graphics"))
1975 return parse_bool(&opt_line_graphics, argv[2]);
1977 if (!strcmp(argv[0], "line-number-interval"))
1978 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1980 if (!strcmp(argv[0], "author-width"))
1981 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1983 if (!strcmp(argv[0], "horizontal-scroll"))
1984 return parse_step(&opt_hscroll, argv[2]);
1986 if (!strcmp(argv[0], "split-view-height"))
1987 return parse_step(&opt_scale_split_view, argv[2]);
1989 if (!strcmp(argv[0], "tab-size"))
1990 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1992 if (!strcmp(argv[0], "commit-encoding"))
1993 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1995 config_msg = "Unknown variable name";
1996 return ERR;
1999 /* Wants: mode request key */
2000 static int
2001 option_bind_command(int argc, const char *argv[])
2003 enum request request;
2004 int keymap = -1;
2005 int key;
2007 if (argc < 3) {
2008 config_msg = "Wrong number of arguments given to bind command";
2009 return ERR;
2012 if (set_keymap(&keymap, argv[0]) == ERR) {
2013 config_msg = "Unknown key map";
2014 return ERR;
2017 key = get_key_value(argv[1]);
2018 if (key == ERR) {
2019 config_msg = "Unknown key";
2020 return ERR;
2023 request = get_request(argv[2]);
2024 if (request == REQ_UNKNOWN) {
2025 static const struct enum_map obsolete[] = {
2026 ENUM_MAP("cherry-pick", REQ_NONE),
2027 ENUM_MAP("screen-resize", REQ_NONE),
2028 ENUM_MAP("tree-parent", REQ_PARENT),
2030 int alias;
2032 if (map_enum(&alias, obsolete, argv[2])) {
2033 if (alias != REQ_NONE)
2034 add_keybinding(keymap, alias, key);
2035 config_msg = "Obsolete request name";
2036 return ERR;
2039 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2040 request = add_run_request(keymap, key, argc - 2, argv + 2);
2041 if (request == REQ_UNKNOWN) {
2042 config_msg = "Unknown request name";
2043 return ERR;
2046 add_keybinding(keymap, request, key);
2048 return OK;
2051 static int
2052 set_option(const char *opt, char *value)
2054 const char *argv[SIZEOF_ARG];
2055 int argc = 0;
2057 if (!argv_from_string(argv, &argc, value)) {
2058 config_msg = "Too many option arguments";
2059 return ERR;
2062 if (!strcmp(opt, "color"))
2063 return option_color_command(argc, argv);
2065 if (!strcmp(opt, "set"))
2066 return option_set_command(argc, argv);
2068 if (!strcmp(opt, "bind"))
2069 return option_bind_command(argc, argv);
2071 config_msg = "Unknown option command";
2072 return ERR;
2075 static int
2076 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2078 int status = OK;
2080 config_lineno++;
2081 config_msg = "Internal error";
2083 /* Check for comment markers, since read_properties() will
2084 * only ensure opt and value are split at first " \t". */
2085 optlen = strcspn(opt, "#");
2086 if (optlen == 0)
2087 return OK;
2089 if (opt[optlen] != 0) {
2090 config_msg = "No option value";
2091 status = ERR;
2093 } else {
2094 /* Look for comment endings in the value. */
2095 size_t len = strcspn(value, "#");
2097 if (len < valuelen) {
2098 valuelen = len;
2099 value[valuelen] = 0;
2102 status = set_option(opt, value);
2105 if (status == ERR) {
2106 warn("Error on line %d, near '%.*s': %s",
2107 config_lineno, (int) optlen, opt, config_msg);
2108 config_errors = TRUE;
2111 /* Always keep going if errors are encountered. */
2112 return OK;
2115 static void
2116 load_option_file(const char *path)
2118 struct io io = {};
2120 /* It's OK that the file doesn't exist. */
2121 if (!io_open(&io, "%s", path))
2122 return;
2124 config_lineno = 0;
2125 config_errors = FALSE;
2127 if (io_load(&io, " \t", read_option) == ERR ||
2128 config_errors == TRUE)
2129 warn("Errors while loading %s.", path);
2132 static int
2133 load_options(void)
2135 const char *home = getenv("HOME");
2136 const char *tigrc_user = getenv("TIGRC_USER");
2137 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2138 char buf[SIZEOF_STR];
2140 add_builtin_run_requests();
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 return OK;
2158 * The viewer
2161 struct view;
2162 struct view_ops;
2164 /* The display array of active views and the index of the current view. */
2165 static struct view *display[2];
2166 static unsigned int current_view;
2168 #define foreach_displayed_view(view, i) \
2169 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2171 #define displayed_views() (display[1] != NULL ? 2 : 1)
2173 /* Current head and commit ID */
2174 static char ref_blob[SIZEOF_REF] = "";
2175 static char ref_commit[SIZEOF_REF] = "HEAD";
2176 static char ref_head[SIZEOF_REF] = "HEAD";
2177 static char ref_branch[SIZEOF_REF] = "";
2179 enum view_type {
2180 VIEW_MAIN,
2181 VIEW_DIFF,
2182 VIEW_LOG,
2183 VIEW_TREE,
2184 VIEW_BLOB,
2185 VIEW_BLAME,
2186 VIEW_BRANCH,
2187 VIEW_HELP,
2188 VIEW_PAGER,
2189 VIEW_STATUS,
2190 VIEW_STAGE,
2193 struct view {
2194 enum view_type type; /* View type */
2195 const char *name; /* View name */
2196 const char *cmd_env; /* Command line set via environment */
2197 const char *id; /* Points to either of ref_{head,commit,blob} */
2199 struct view_ops *ops; /* View operations */
2201 enum keymap keymap; /* What keymap does this view have */
2202 bool git_dir; /* Whether the view requires a git directory. */
2203 bool refresh; /* Whether the view supports refreshing. */
2205 char ref[SIZEOF_REF]; /* Hovered commit reference */
2206 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2208 int height, width; /* The width and height of the main window */
2209 WINDOW *win; /* The main window */
2210 WINDOW *title; /* The title window living below the main window */
2212 /* Navigation */
2213 unsigned long offset; /* Offset of the window top */
2214 unsigned long yoffset; /* Offset from the window side. */
2215 unsigned long lineno; /* Current line number */
2216 unsigned long p_offset; /* Previous offset of the window top */
2217 unsigned long p_yoffset;/* Previous offset from the window side */
2218 unsigned long p_lineno; /* Previous current line number */
2219 bool p_restore; /* Should the previous position be restored. */
2221 /* Searching */
2222 char grep[SIZEOF_STR]; /* Search string */
2223 regex_t *regex; /* Pre-compiled regexp */
2225 /* If non-NULL, points to the view that opened this view. If this view
2226 * is closed tig will switch back to the parent view. */
2227 struct view *parent;
2229 /* Buffering */
2230 size_t lines; /* Total number of lines */
2231 struct line *line; /* Line index */
2232 unsigned int digits; /* Number of digits in the lines member. */
2234 /* Drawing */
2235 struct line *curline; /* Line currently being drawn. */
2236 enum line_type curtype; /* Attribute currently used for drawing. */
2237 unsigned long col; /* Column when drawing. */
2238 bool has_scrolled; /* View was scrolled. */
2240 /* Loading */
2241 struct io io;
2242 struct io *pipe;
2243 time_t start_time;
2244 time_t update_secs;
2247 struct view_ops {
2248 /* What type of content being displayed. Used in the title bar. */
2249 const char *type;
2250 /* Default command arguments. */
2251 const char **argv;
2252 /* Open and reads in all view content. */
2253 bool (*open)(struct view *view);
2254 /* Read one line; updates view->line. */
2255 bool (*read)(struct view *view, char *data);
2256 /* Draw one line; @lineno must be < view->height. */
2257 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2258 /* Depending on view handle a special requests. */
2259 enum request (*request)(struct view *view, enum request request, struct line *line);
2260 /* Search for regexp in a line. */
2261 bool (*grep)(struct view *view, struct line *line);
2262 /* Select line */
2263 void (*select)(struct view *view, struct line *line);
2264 /* Prepare view for loading */
2265 bool (*prepare)(struct view *view);
2268 static struct view_ops blame_ops;
2269 static struct view_ops blob_ops;
2270 static struct view_ops diff_ops;
2271 static struct view_ops help_ops;
2272 static struct view_ops log_ops;
2273 static struct view_ops main_ops;
2274 static struct view_ops pager_ops;
2275 static struct view_ops stage_ops;
2276 static struct view_ops status_ops;
2277 static struct view_ops tree_ops;
2278 static struct view_ops branch_ops;
2280 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2281 { type, name, #env, ref, ops, map, git, refresh }
2283 #define VIEW_(id, name, ops, git, refresh, ref) \
2284 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2286 static struct view views[] = {
2287 VIEW_(MAIN, "main", &main_ops, TRUE, TRUE, ref_head),
2288 VIEW_(DIFF, "diff", &diff_ops, TRUE, FALSE, ref_commit),
2289 VIEW_(LOG, "log", &log_ops, TRUE, TRUE, ref_head),
2290 VIEW_(TREE, "tree", &tree_ops, TRUE, FALSE, ref_commit),
2291 VIEW_(BLOB, "blob", &blob_ops, TRUE, FALSE, ref_blob),
2292 VIEW_(BLAME, "blame", &blame_ops, TRUE, FALSE, ref_commit),
2293 VIEW_(BRANCH, "branch", &branch_ops, TRUE, TRUE, ref_head),
2294 VIEW_(HELP, "help", &help_ops, FALSE, FALSE, ""),
2295 VIEW_(PAGER, "pager", &pager_ops, FALSE, FALSE, "stdin"),
2296 VIEW_(STATUS, "status", &status_ops, TRUE, TRUE, ""),
2297 VIEW_(STAGE, "stage", &stage_ops, TRUE, TRUE, ""),
2300 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2302 #define foreach_view(view, i) \
2303 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2305 #define view_is_displayed(view) \
2306 (view == display[0] || view == display[1])
2308 #define view_has_parent(view, child_type, parent_type) \
2309 (view->type == child_type && view->parent && view->parent->type == parent_type)
2311 static inline void
2312 set_view_attr(struct view *view, enum line_type type)
2314 if (!view->curline->selected && view->curtype != type) {
2315 (void) wattrset(view->win, get_line_attr(type));
2316 wchgat(view->win, -1, 0, type, NULL);
2317 view->curtype = type;
2321 static int
2322 draw_chars(struct view *view, enum line_type type, const char *string,
2323 int max_len, bool use_tilde)
2325 static char out_buffer[BUFSIZ * 2];
2326 int len = 0;
2327 int col = 0;
2328 int trimmed = FALSE;
2329 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2331 if (max_len <= 0)
2332 return 0;
2334 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2336 set_view_attr(view, type);
2337 if (len > 0) {
2338 if (opt_iconv_out != ICONV_NONE) {
2339 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2340 size_t inlen = len + 1;
2342 char *outbuf = out_buffer;
2343 size_t outlen = sizeof(out_buffer);
2345 size_t ret;
2347 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2348 if (ret != (size_t) -1) {
2349 string = out_buffer;
2350 len = sizeof(out_buffer) - outlen;
2354 waddnstr(view->win, string, len);
2356 if (trimmed && use_tilde) {
2357 set_view_attr(view, LINE_DELIMITER);
2358 waddch(view->win, '~');
2359 col++;
2362 return col;
2365 static int
2366 draw_space(struct view *view, enum line_type type, int max, int spaces)
2368 static char space[] = " ";
2369 int col = 0;
2371 spaces = MIN(max, spaces);
2373 while (spaces > 0) {
2374 int len = MIN(spaces, sizeof(space) - 1);
2376 col += draw_chars(view, type, space, len, FALSE);
2377 spaces -= len;
2380 return col;
2383 static bool
2384 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2386 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2387 return view->width + view->yoffset <= view->col;
2390 static bool
2391 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2393 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2394 int max = view->width + view->yoffset - view->col;
2395 int i;
2397 if (max < size)
2398 size = max;
2400 set_view_attr(view, type);
2401 /* Using waddch() instead of waddnstr() ensures that
2402 * they'll be rendered correctly for the cursor line. */
2403 for (i = skip; i < size; i++)
2404 waddch(view->win, graphic[i]);
2406 view->col += size;
2407 if (size < max && skip <= size)
2408 waddch(view->win, ' ');
2409 view->col++;
2411 return view->width + view->yoffset <= view->col;
2414 static bool
2415 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2417 int max = MIN(view->width + view->yoffset - view->col, len);
2418 int col;
2420 if (text)
2421 col = draw_chars(view, type, text, max - 1, trim);
2422 else
2423 col = draw_space(view, type, max - 1, max - 1);
2425 view->col += col;
2426 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2427 return view->width + view->yoffset <= view->col;
2430 static bool
2431 draw_date(struct view *view, struct time *time)
2433 const char *date = mkdate(time, opt_date);
2434 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2436 return draw_field(view, LINE_DATE, date, cols, FALSE);
2439 static bool
2440 draw_author(struct view *view, const char *author)
2442 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2443 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2445 if (abbreviate && author)
2446 author = get_author_initials(author);
2448 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2451 static bool
2452 draw_mode(struct view *view, mode_t mode)
2454 const char *str;
2456 if (S_ISDIR(mode))
2457 str = "drwxr-xr-x";
2458 else if (S_ISLNK(mode))
2459 str = "lrwxrwxrwx";
2460 else if (S_ISGITLINK(mode))
2461 str = "m---------";
2462 else if (S_ISREG(mode) && mode & S_IXUSR)
2463 str = "-rwxr-xr-x";
2464 else if (S_ISREG(mode))
2465 str = "-rw-r--r--";
2466 else
2467 str = "----------";
2469 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2472 static bool
2473 draw_lineno(struct view *view, unsigned int lineno)
2475 char number[10];
2476 int digits3 = view->digits < 3 ? 3 : view->digits;
2477 int max = MIN(view->width + view->yoffset - view->col, digits3);
2478 char *text = NULL;
2479 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2481 lineno += view->offset + 1;
2482 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2483 static char fmt[] = "%1ld";
2485 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2486 if (string_format(number, fmt, lineno))
2487 text = number;
2489 if (text)
2490 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2491 else
2492 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2493 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2496 static bool
2497 draw_view_line(struct view *view, unsigned int lineno)
2499 struct line *line;
2500 bool selected = (view->offset + lineno == view->lineno);
2502 assert(view_is_displayed(view));
2504 if (view->offset + lineno >= view->lines)
2505 return FALSE;
2507 line = &view->line[view->offset + lineno];
2509 wmove(view->win, lineno, 0);
2510 if (line->cleareol)
2511 wclrtoeol(view->win);
2512 view->col = 0;
2513 view->curline = line;
2514 view->curtype = LINE_NONE;
2515 line->selected = FALSE;
2516 line->dirty = line->cleareol = 0;
2518 if (selected) {
2519 set_view_attr(view, LINE_CURSOR);
2520 line->selected = TRUE;
2521 view->ops->select(view, line);
2524 return view->ops->draw(view, line, lineno);
2527 static void
2528 redraw_view_dirty(struct view *view)
2530 bool dirty = FALSE;
2531 int lineno;
2533 for (lineno = 0; lineno < view->height; lineno++) {
2534 if (view->offset + lineno >= view->lines)
2535 break;
2536 if (!view->line[view->offset + lineno].dirty)
2537 continue;
2538 dirty = TRUE;
2539 if (!draw_view_line(view, lineno))
2540 break;
2543 if (!dirty)
2544 return;
2545 wnoutrefresh(view->win);
2548 static void
2549 redraw_view_from(struct view *view, int lineno)
2551 assert(0 <= lineno && lineno < view->height);
2553 for (; lineno < view->height; lineno++) {
2554 if (!draw_view_line(view, lineno))
2555 break;
2558 wnoutrefresh(view->win);
2561 static void
2562 redraw_view(struct view *view)
2564 werase(view->win);
2565 redraw_view_from(view, 0);
2569 static void
2570 update_view_title(struct view *view)
2572 char buf[SIZEOF_STR];
2573 char state[SIZEOF_STR];
2574 size_t bufpos = 0, statelen = 0;
2576 assert(view_is_displayed(view));
2578 if (view->type != VIEW_STATUS && view->lines) {
2579 unsigned int view_lines = view->offset + view->height;
2580 unsigned int lines = view->lines
2581 ? MIN(view_lines, view->lines) * 100 / view->lines
2582 : 0;
2584 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2585 view->ops->type,
2586 view->lineno + 1,
2587 view->lines,
2588 lines);
2592 if (view->pipe) {
2593 time_t secs = time(NULL) - view->start_time;
2595 /* Three git seconds are a long time ... */
2596 if (secs > 2)
2597 string_format_from(state, &statelen, " loading %lds", secs);
2600 string_format_from(buf, &bufpos, "[%s]", view->name);
2601 if (*view->ref && bufpos < view->width) {
2602 size_t refsize = strlen(view->ref);
2603 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2605 if (minsize < view->width)
2606 refsize = view->width - minsize + 7;
2607 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2610 if (statelen && bufpos < view->width) {
2611 string_format_from(buf, &bufpos, "%s", state);
2614 if (view == display[current_view])
2615 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2616 else
2617 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2619 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2620 wclrtoeol(view->title);
2621 wnoutrefresh(view->title);
2624 static int
2625 apply_step(double step, int value)
2627 if (step >= 1)
2628 return (int) step;
2629 value *= step + 0.01;
2630 return value ? value : 1;
2633 static void
2634 resize_display(void)
2636 int offset, i;
2637 struct view *base = display[0];
2638 struct view *view = display[1] ? display[1] : display[0];
2640 /* Setup window dimensions */
2642 getmaxyx(stdscr, base->height, base->width);
2644 /* Make room for the status window. */
2645 base->height -= 1;
2647 if (view != base) {
2648 /* Horizontal split. */
2649 view->width = base->width;
2650 view->height = apply_step(opt_scale_split_view, base->height);
2651 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2652 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2653 base->height -= view->height;
2655 /* Make room for the title bar. */
2656 view->height -= 1;
2659 /* Make room for the title bar. */
2660 base->height -= 1;
2662 offset = 0;
2664 foreach_displayed_view (view, i) {
2665 if (!view->win) {
2666 view->win = newwin(view->height, 0, offset, 0);
2667 if (!view->win)
2668 die("Failed to create %s view", view->name);
2670 scrollok(view->win, FALSE);
2672 view->title = newwin(1, 0, offset + view->height, 0);
2673 if (!view->title)
2674 die("Failed to create title window");
2676 } else {
2677 wresize(view->win, view->height, view->width);
2678 mvwin(view->win, offset, 0);
2679 mvwin(view->title, offset + view->height, 0);
2682 offset += view->height + 1;
2686 static void
2687 redraw_display(bool clear)
2689 struct view *view;
2690 int i;
2692 foreach_displayed_view (view, i) {
2693 if (clear)
2694 wclear(view->win);
2695 redraw_view(view);
2696 update_view_title(view);
2700 static void
2701 toggle_enum_option_do(unsigned int *opt, const char *help,
2702 const struct enum_map *map, size_t size)
2704 *opt = (*opt + 1) % size;
2705 redraw_display(FALSE);
2706 report("Displaying %s %s", enum_name(map[*opt]), help);
2709 #define toggle_enum_option(opt, help, map) \
2710 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2712 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2713 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2715 static void
2716 toggle_view_option(bool *option, const char *help)
2718 *option = !*option;
2719 redraw_display(FALSE);
2720 report("%sabling %s", *option ? "En" : "Dis", help);
2723 static void
2724 open_option_menu(void)
2726 const struct menu_item menu[] = {
2727 { '.', "line numbers", &opt_line_number },
2728 { 'D', "date display", &opt_date },
2729 { 'A', "author display", &opt_author },
2730 { 'g', "revision graph display", &opt_rev_graph },
2731 { 'F', "reference display", &opt_show_refs },
2732 { 0 }
2734 int selected = 0;
2736 if (prompt_menu("Toggle option", menu, &selected)) {
2737 if (menu[selected].data == &opt_date)
2738 toggle_date();
2739 else if (menu[selected].data == &opt_author)
2740 toggle_author();
2741 else
2742 toggle_view_option(menu[selected].data, menu[selected].text);
2746 static void
2747 maximize_view(struct view *view)
2749 memset(display, 0, sizeof(display));
2750 current_view = 0;
2751 display[current_view] = view;
2752 resize_display();
2753 redraw_display(FALSE);
2754 report("");
2759 * Navigation
2762 static bool
2763 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2765 if (lineno >= view->lines)
2766 lineno = view->lines > 0 ? view->lines - 1 : 0;
2768 if (offset > lineno || offset + view->height <= lineno) {
2769 unsigned long half = view->height / 2;
2771 if (lineno > half)
2772 offset = lineno - half;
2773 else
2774 offset = 0;
2777 if (offset != view->offset || lineno != view->lineno) {
2778 view->offset = offset;
2779 view->lineno = lineno;
2780 return TRUE;
2783 return FALSE;
2786 /* Scrolling backend */
2787 static void
2788 do_scroll_view(struct view *view, int lines)
2790 bool redraw_current_line = FALSE;
2792 /* The rendering expects the new offset. */
2793 view->offset += lines;
2795 assert(0 <= view->offset && view->offset < view->lines);
2796 assert(lines);
2798 /* Move current line into the view. */
2799 if (view->lineno < view->offset) {
2800 view->lineno = view->offset;
2801 redraw_current_line = TRUE;
2802 } else if (view->lineno >= view->offset + view->height) {
2803 view->lineno = view->offset + view->height - 1;
2804 redraw_current_line = TRUE;
2807 assert(view->offset <= view->lineno && view->lineno < view->lines);
2809 /* Redraw the whole screen if scrolling is pointless. */
2810 if (view->height < ABS(lines)) {
2811 redraw_view(view);
2813 } else {
2814 int line = lines > 0 ? view->height - lines : 0;
2815 int end = line + ABS(lines);
2817 scrollok(view->win, TRUE);
2818 wscrl(view->win, lines);
2819 scrollok(view->win, FALSE);
2821 while (line < end && draw_view_line(view, line))
2822 line++;
2824 if (redraw_current_line)
2825 draw_view_line(view, view->lineno - view->offset);
2826 wnoutrefresh(view->win);
2829 view->has_scrolled = TRUE;
2830 report("");
2833 /* Scroll frontend */
2834 static void
2835 scroll_view(struct view *view, enum request request)
2837 int lines = 1;
2839 assert(view_is_displayed(view));
2841 switch (request) {
2842 case REQ_SCROLL_LEFT:
2843 if (view->yoffset == 0) {
2844 report("Cannot scroll beyond the first column");
2845 return;
2847 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2848 view->yoffset = 0;
2849 else
2850 view->yoffset -= apply_step(opt_hscroll, view->width);
2851 redraw_view_from(view, 0);
2852 report("");
2853 return;
2854 case REQ_SCROLL_RIGHT:
2855 view->yoffset += apply_step(opt_hscroll, view->width);
2856 redraw_view(view);
2857 report("");
2858 return;
2859 case REQ_SCROLL_PAGE_DOWN:
2860 lines = view->height;
2861 case REQ_SCROLL_LINE_DOWN:
2862 if (view->offset + lines > view->lines)
2863 lines = view->lines - view->offset;
2865 if (lines == 0 || view->offset + view->height >= view->lines) {
2866 report("Cannot scroll beyond the last line");
2867 return;
2869 break;
2871 case REQ_SCROLL_PAGE_UP:
2872 lines = view->height;
2873 case REQ_SCROLL_LINE_UP:
2874 if (lines > view->offset)
2875 lines = view->offset;
2877 if (lines == 0) {
2878 report("Cannot scroll beyond the first line");
2879 return;
2882 lines = -lines;
2883 break;
2885 default:
2886 die("request %d not handled in switch", request);
2889 do_scroll_view(view, lines);
2892 /* Cursor moving */
2893 static void
2894 move_view(struct view *view, enum request request)
2896 int scroll_steps = 0;
2897 int steps;
2899 switch (request) {
2900 case REQ_MOVE_FIRST_LINE:
2901 steps = -view->lineno;
2902 break;
2904 case REQ_MOVE_LAST_LINE:
2905 steps = view->lines - view->lineno - 1;
2906 break;
2908 case REQ_MOVE_PAGE_UP:
2909 steps = view->height > view->lineno
2910 ? -view->lineno : -view->height;
2911 break;
2913 case REQ_MOVE_PAGE_DOWN:
2914 steps = view->lineno + view->height >= view->lines
2915 ? view->lines - view->lineno - 1 : view->height;
2916 break;
2918 case REQ_MOVE_UP:
2919 steps = -1;
2920 break;
2922 case REQ_MOVE_DOWN:
2923 steps = 1;
2924 break;
2926 default:
2927 die("request %d not handled in switch", request);
2930 if (steps <= 0 && view->lineno == 0) {
2931 report("Cannot move beyond the first line");
2932 return;
2934 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2935 report("Cannot move beyond the last line");
2936 return;
2939 /* Move the current line */
2940 view->lineno += steps;
2941 assert(0 <= view->lineno && view->lineno < view->lines);
2943 /* Check whether the view needs to be scrolled */
2944 if (view->lineno < view->offset ||
2945 view->lineno >= view->offset + view->height) {
2946 scroll_steps = steps;
2947 if (steps < 0 && -steps > view->offset) {
2948 scroll_steps = -view->offset;
2950 } else if (steps > 0) {
2951 if (view->lineno == view->lines - 1 &&
2952 view->lines > view->height) {
2953 scroll_steps = view->lines - view->offset - 1;
2954 if (scroll_steps >= view->height)
2955 scroll_steps -= view->height - 1;
2960 if (!view_is_displayed(view)) {
2961 view->offset += scroll_steps;
2962 assert(0 <= view->offset && view->offset < view->lines);
2963 view->ops->select(view, &view->line[view->lineno]);
2964 return;
2967 /* Repaint the old "current" line if we be scrolling */
2968 if (ABS(steps) < view->height)
2969 draw_view_line(view, view->lineno - steps - view->offset);
2971 if (scroll_steps) {
2972 do_scroll_view(view, scroll_steps);
2973 return;
2976 /* Draw the current line */
2977 draw_view_line(view, view->lineno - view->offset);
2979 wnoutrefresh(view->win);
2980 report("");
2985 * Searching
2988 static void search_view(struct view *view, enum request request);
2990 static bool
2991 grep_text(struct view *view, const char *text[])
2993 regmatch_t pmatch;
2994 size_t i;
2996 for (i = 0; text[i]; i++)
2997 if (*text[i] &&
2998 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2999 return TRUE;
3000 return FALSE;
3003 static void
3004 select_view_line(struct view *view, unsigned long lineno)
3006 unsigned long old_lineno = view->lineno;
3007 unsigned long old_offset = view->offset;
3009 if (goto_view_line(view, view->offset, lineno)) {
3010 if (view_is_displayed(view)) {
3011 if (old_offset != view->offset) {
3012 redraw_view(view);
3013 } else {
3014 draw_view_line(view, old_lineno - view->offset);
3015 draw_view_line(view, view->lineno - view->offset);
3016 wnoutrefresh(view->win);
3018 } else {
3019 view->ops->select(view, &view->line[view->lineno]);
3024 static void
3025 find_next(struct view *view, enum request request)
3027 unsigned long lineno = view->lineno;
3028 int direction;
3030 if (!*view->grep) {
3031 if (!*opt_search)
3032 report("No previous search");
3033 else
3034 search_view(view, request);
3035 return;
3038 switch (request) {
3039 case REQ_SEARCH:
3040 case REQ_FIND_NEXT:
3041 direction = 1;
3042 break;
3044 case REQ_SEARCH_BACK:
3045 case REQ_FIND_PREV:
3046 direction = -1;
3047 break;
3049 default:
3050 return;
3053 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3054 lineno += direction;
3056 /* Note, lineno is unsigned long so will wrap around in which case it
3057 * will become bigger than view->lines. */
3058 for (; lineno < view->lines; lineno += direction) {
3059 if (view->ops->grep(view, &view->line[lineno])) {
3060 select_view_line(view, lineno);
3061 report("Line %ld matches '%s'", lineno + 1, view->grep);
3062 return;
3066 report("No match found for '%s'", view->grep);
3069 static void
3070 search_view(struct view *view, enum request request)
3072 int regex_err;
3074 if (view->regex) {
3075 regfree(view->regex);
3076 *view->grep = 0;
3077 } else {
3078 view->regex = calloc(1, sizeof(*view->regex));
3079 if (!view->regex)
3080 return;
3083 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3084 if (regex_err != 0) {
3085 char buf[SIZEOF_STR] = "unknown error";
3087 regerror(regex_err, view->regex, buf, sizeof(buf));
3088 report("Search failed: %s", buf);
3089 return;
3092 string_copy(view->grep, opt_search);
3094 find_next(view, request);
3098 * Incremental updating
3101 static void
3102 reset_view(struct view *view)
3104 int i;
3106 for (i = 0; i < view->lines; i++)
3107 free(view->line[i].data);
3108 free(view->line);
3110 view->p_offset = view->offset;
3111 view->p_yoffset = view->yoffset;
3112 view->p_lineno = view->lineno;
3114 view->line = NULL;
3115 view->offset = 0;
3116 view->yoffset = 0;
3117 view->lines = 0;
3118 view->lineno = 0;
3119 view->vid[0] = 0;
3120 view->update_secs = 0;
3123 static void
3124 free_argv(const char *argv[])
3126 int argc;
3128 for (argc = 0; argv[argc]; argc++)
3129 free((void *) argv[argc]);
3132 static const char *
3133 format_arg(const char *name)
3135 static struct {
3136 const char *name;
3137 size_t namelen;
3138 const char *value;
3139 const char *value_if_empty;
3140 } vars[] = {
3141 #define FORMAT_VAR(name, value, value_if_empty) \
3142 { name, STRING_SIZE(name), value, value_if_empty }
3143 FORMAT_VAR("%(directory)", opt_path, ""),
3144 FORMAT_VAR("%(file)", opt_file, ""),
3145 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3146 FORMAT_VAR("%(head)", ref_head, ""),
3147 FORMAT_VAR("%(commit)", ref_commit, ""),
3148 FORMAT_VAR("%(blob)", ref_blob, ""),
3149 FORMAT_VAR("%(branch)", ref_branch, ""),
3151 int i;
3153 for (i = 0; i < ARRAY_SIZE(vars); i++)
3154 if (!strncmp(name, vars[i].name, vars[i].namelen))
3155 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3157 report("Unknown replacement: `%s`", name);
3158 return NULL;
3161 static bool
3162 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3164 char buf[SIZEOF_STR];
3165 int argc;
3166 bool noreplace = flags == FORMAT_NONE;
3168 free_argv(dst_argv);
3170 for (argc = 0; src_argv[argc]; argc++) {
3171 const char *arg = src_argv[argc];
3172 size_t bufpos = 0;
3174 while (arg) {
3175 char *next = strstr(arg, "%(");
3176 int len = next - arg;
3177 const char *value;
3179 if (!next || noreplace) {
3180 len = strlen(arg);
3181 value = "";
3183 } else {
3184 value = format_arg(next);
3186 if (!value) {
3187 return FALSE;
3191 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3192 return FALSE;
3194 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3197 dst_argv[argc] = strdup(buf);
3198 if (!dst_argv[argc])
3199 break;
3202 dst_argv[argc] = NULL;
3204 return src_argv[argc] == NULL;
3207 static bool
3208 restore_view_position(struct view *view)
3210 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3211 return FALSE;
3213 /* Changing the view position cancels the restoring. */
3214 /* FIXME: Changing back to the first line is not detected. */
3215 if (view->offset != 0 || view->lineno != 0) {
3216 view->p_restore = FALSE;
3217 return FALSE;
3220 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3221 view_is_displayed(view))
3222 werase(view->win);
3224 view->yoffset = view->p_yoffset;
3225 view->p_restore = FALSE;
3227 return TRUE;
3230 static void
3231 end_update(struct view *view, bool force)
3233 if (!view->pipe)
3234 return;
3235 while (!view->ops->read(view, NULL))
3236 if (!force)
3237 return;
3238 if (force)
3239 io_kill(view->pipe);
3240 io_done(view->pipe);
3241 view->pipe = NULL;
3244 static void
3245 setup_update(struct view *view, const char *vid)
3247 reset_view(view);
3248 string_copy_rev(view->vid, vid);
3249 view->pipe = &view->io;
3250 view->start_time = time(NULL);
3253 static bool
3254 prepare_update(struct view *view, const char *argv[], const char *dir)
3256 if (view->pipe)
3257 end_update(view, TRUE);
3258 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3261 static bool
3262 prepare_update_file(struct view *view, const char *name)
3264 if (view->pipe)
3265 end_update(view, TRUE);
3266 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3269 static bool
3270 begin_update(struct view *view, bool refresh)
3272 if (view->pipe)
3273 end_update(view, TRUE);
3275 if (!refresh) {
3276 if (view->ops->prepare) {
3277 if (!view->ops->prepare(view))
3278 return FALSE;
3279 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3280 return FALSE;
3283 /* Put the current ref_* value to the view title ref
3284 * member. This is needed by the blob view. Most other
3285 * views sets it automatically after loading because the
3286 * first line is a commit line. */
3287 string_copy_rev(view->ref, view->id);
3290 if (!io_start(&view->io))
3291 return FALSE;
3293 setup_update(view, view->id);
3295 return TRUE;
3298 static bool
3299 update_view(struct view *view)
3301 char out_buffer[BUFSIZ * 2];
3302 char *line;
3303 /* Clear the view and redraw everything since the tree sorting
3304 * might have rearranged things. */
3305 bool redraw = view->lines == 0;
3306 bool can_read = TRUE;
3308 if (!view->pipe)
3309 return TRUE;
3311 if (!io_can_read(view->pipe)) {
3312 if (view->lines == 0 && view_is_displayed(view)) {
3313 time_t secs = time(NULL) - view->start_time;
3315 if (secs > 1 && secs > view->update_secs) {
3316 if (view->update_secs == 0)
3317 redraw_view(view);
3318 update_view_title(view);
3319 view->update_secs = secs;
3322 return TRUE;
3325 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3326 if (opt_iconv_in != ICONV_NONE) {
3327 ICONV_CONST char *inbuf = line;
3328 size_t inlen = strlen(line) + 1;
3330 char *outbuf = out_buffer;
3331 size_t outlen = sizeof(out_buffer);
3333 size_t ret;
3335 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3336 if (ret != (size_t) -1)
3337 line = out_buffer;
3340 if (!view->ops->read(view, line)) {
3341 report("Allocation failure");
3342 end_update(view, TRUE);
3343 return FALSE;
3348 unsigned long lines = view->lines;
3349 int digits;
3351 for (digits = 0; lines; digits++)
3352 lines /= 10;
3354 /* Keep the displayed view in sync with line number scaling. */
3355 if (digits != view->digits) {
3356 view->digits = digits;
3357 if (opt_line_number || view->type == VIEW_BLAME)
3358 redraw = TRUE;
3362 if (io_error(view->pipe)) {
3363 report("Failed to read: %s", io_strerror(view->pipe));
3364 end_update(view, TRUE);
3366 } else if (io_eof(view->pipe)) {
3367 report("");
3368 end_update(view, FALSE);
3371 if (restore_view_position(view))
3372 redraw = TRUE;
3374 if (!view_is_displayed(view))
3375 return TRUE;
3377 if (redraw)
3378 redraw_view_from(view, 0);
3379 else
3380 redraw_view_dirty(view);
3382 /* Update the title _after_ the redraw so that if the redraw picks up a
3383 * commit reference in view->ref it'll be available here. */
3384 update_view_title(view);
3385 return TRUE;
3388 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3390 static struct line *
3391 add_line_data(struct view *view, void *data, enum line_type type)
3393 struct line *line;
3395 if (!realloc_lines(&view->line, view->lines, 1))
3396 return NULL;
3398 line = &view->line[view->lines++];
3399 memset(line, 0, sizeof(*line));
3400 line->type = type;
3401 line->data = data;
3402 line->dirty = 1;
3404 return line;
3407 static struct line *
3408 add_line_text(struct view *view, const char *text, enum line_type type)
3410 char *data = text ? strdup(text) : NULL;
3412 return data ? add_line_data(view, data, type) : NULL;
3415 static struct line *
3416 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3418 char buf[SIZEOF_STR];
3419 va_list args;
3421 va_start(args, fmt);
3422 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3423 buf[0] = 0;
3424 va_end(args);
3426 return buf[0] ? add_line_text(view, buf, type) : NULL;
3430 * View opening
3433 enum open_flags {
3434 OPEN_DEFAULT = 0, /* Use default view switching. */
3435 OPEN_SPLIT = 1, /* Split current view. */
3436 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3437 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3438 OPEN_PREPARED = 32, /* Open already prepared command. */
3441 static void
3442 open_view(struct view *prev, enum request request, enum open_flags flags)
3444 bool split = !!(flags & OPEN_SPLIT);
3445 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3446 bool nomaximize = !!(flags & OPEN_REFRESH);
3447 struct view *view = VIEW(request);
3448 int nviews = displayed_views();
3449 struct view *base_view = display[0];
3451 if (view == prev && nviews == 1 && !reload) {
3452 report("Already in %s view", view->name);
3453 return;
3456 if (view->git_dir && !opt_git_dir[0]) {
3457 report("The %s view is disabled in pager view", view->name);
3458 return;
3461 if (split) {
3462 display[1] = view;
3463 current_view = 1;
3464 } else if (!nomaximize) {
3465 /* Maximize the current view. */
3466 memset(display, 0, sizeof(display));
3467 current_view = 0;
3468 display[current_view] = view;
3471 /* No parent signals that this is the first loaded view. */
3472 if (prev && view != prev) {
3473 view->parent = prev;
3476 /* Resize the view when switching between split- and full-screen,
3477 * or when switching between two different full-screen views. */
3478 if (nviews != displayed_views() ||
3479 (nviews == 1 && base_view != display[0]))
3480 resize_display();
3482 if (view->ops->open) {
3483 if (view->pipe)
3484 end_update(view, TRUE);
3485 if (!view->ops->open(view)) {
3486 report("Failed to load %s view", view->name);
3487 return;
3489 restore_view_position(view);
3491 } else if ((reload || strcmp(view->vid, view->id)) &&
3492 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3493 report("Failed to load %s view", view->name);
3494 return;
3497 if (split && prev->lineno - prev->offset >= prev->height) {
3498 /* Take the title line into account. */
3499 int lines = prev->lineno - prev->offset - prev->height + 1;
3501 /* Scroll the view that was split if the current line is
3502 * outside the new limited view. */
3503 do_scroll_view(prev, lines);
3506 if (prev && view != prev && split && view_is_displayed(prev)) {
3507 /* "Blur" the previous view. */
3508 update_view_title(prev);
3511 if (view->pipe && view->lines == 0) {
3512 /* Clear the old view and let the incremental updating refill
3513 * the screen. */
3514 werase(view->win);
3515 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3516 report("");
3517 } else if (view_is_displayed(view)) {
3518 redraw_view(view);
3519 report("");
3523 static void
3524 open_external_viewer(const char *argv[], const char *dir)
3526 def_prog_mode(); /* save current tty modes */
3527 endwin(); /* restore original tty modes */
3528 io_run_fg(argv, dir);
3529 fprintf(stderr, "Press Enter to continue");
3530 getc(opt_tty);
3531 reset_prog_mode();
3532 redraw_display(TRUE);
3535 static void
3536 open_mergetool(const char *file)
3538 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3540 open_external_viewer(mergetool_argv, opt_cdup);
3543 static void
3544 open_editor(const char *file)
3546 const char *editor_argv[] = { "vi", file, NULL };
3547 const char *editor;
3549 editor = getenv("GIT_EDITOR");
3550 if (!editor && *opt_editor)
3551 editor = opt_editor;
3552 if (!editor)
3553 editor = getenv("VISUAL");
3554 if (!editor)
3555 editor = getenv("EDITOR");
3556 if (!editor)
3557 editor = "vi";
3559 editor_argv[0] = editor;
3560 open_external_viewer(editor_argv, opt_cdup);
3563 static void
3564 open_run_request(enum request request)
3566 struct run_request *req = get_run_request(request);
3567 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3569 if (!req) {
3570 report("Unknown run request");
3571 return;
3574 if (format_argv(argv, req->argv, FORMAT_ALL))
3575 open_external_viewer(argv, NULL);
3576 free_argv(argv);
3580 * User request switch noodle
3583 static int
3584 view_driver(struct view *view, enum request request)
3586 int i;
3588 if (request == REQ_NONE)
3589 return TRUE;
3591 if (request > REQ_NONE) {
3592 open_run_request(request);
3593 /* FIXME: When all views can refresh always do this. */
3594 if (view->refresh)
3595 request = REQ_REFRESH;
3596 else
3597 return TRUE;
3600 if (view && view->lines) {
3601 request = view->ops->request(view, request, &view->line[view->lineno]);
3602 if (request == REQ_NONE)
3603 return TRUE;
3606 switch (request) {
3607 case REQ_MOVE_UP:
3608 case REQ_MOVE_DOWN:
3609 case REQ_MOVE_PAGE_UP:
3610 case REQ_MOVE_PAGE_DOWN:
3611 case REQ_MOVE_FIRST_LINE:
3612 case REQ_MOVE_LAST_LINE:
3613 move_view(view, request);
3614 break;
3616 case REQ_SCROLL_LEFT:
3617 case REQ_SCROLL_RIGHT:
3618 case REQ_SCROLL_LINE_DOWN:
3619 case REQ_SCROLL_LINE_UP:
3620 case REQ_SCROLL_PAGE_DOWN:
3621 case REQ_SCROLL_PAGE_UP:
3622 scroll_view(view, request);
3623 break;
3625 case REQ_VIEW_BLAME:
3626 if (!opt_file[0]) {
3627 report("No file chosen, press %s to open tree view",
3628 get_key(view->keymap, REQ_VIEW_TREE));
3629 break;
3631 open_view(view, request, OPEN_DEFAULT);
3632 break;
3634 case REQ_VIEW_BLOB:
3635 if (!ref_blob[0]) {
3636 report("No file chosen, press %s to open tree view",
3637 get_key(view->keymap, REQ_VIEW_TREE));
3638 break;
3640 open_view(view, request, OPEN_DEFAULT);
3641 break;
3643 case REQ_VIEW_PAGER:
3644 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3645 report("No pager content, press %s to run command from prompt",
3646 get_key(view->keymap, REQ_PROMPT));
3647 break;
3649 open_view(view, request, OPEN_DEFAULT);
3650 break;
3652 case REQ_VIEW_STAGE:
3653 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3654 report("No stage content, press %s to open the status view and choose file",
3655 get_key(view->keymap, REQ_VIEW_STATUS));
3656 break;
3658 open_view(view, request, OPEN_DEFAULT);
3659 break;
3661 case REQ_VIEW_STATUS:
3662 if (opt_is_inside_work_tree == FALSE) {
3663 report("The status view requires a working tree");
3664 break;
3666 open_view(view, request, OPEN_DEFAULT);
3667 break;
3669 case REQ_VIEW_MAIN:
3670 case REQ_VIEW_DIFF:
3671 case REQ_VIEW_LOG:
3672 case REQ_VIEW_TREE:
3673 case REQ_VIEW_HELP:
3674 case REQ_VIEW_BRANCH:
3675 open_view(view, request, OPEN_DEFAULT);
3676 break;
3678 case REQ_NEXT:
3679 case REQ_PREVIOUS:
3680 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3682 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3683 view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3684 view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3685 view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3686 view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3687 int line;
3689 view = view->parent;
3690 line = view->lineno;
3691 move_view(view, request);
3692 if (view_is_displayed(view))
3693 update_view_title(view);
3694 if (line != view->lineno)
3695 view->ops->request(view, REQ_ENTER,
3696 &view->line[view->lineno]);
3698 } else {
3699 move_view(view, request);
3701 break;
3703 case REQ_VIEW_NEXT:
3705 int nviews = displayed_views();
3706 int next_view = (current_view + 1) % nviews;
3708 if (next_view == current_view) {
3709 report("Only one view is displayed");
3710 break;
3713 current_view = next_view;
3714 /* Blur out the title of the previous view. */
3715 update_view_title(view);
3716 report("");
3717 break;
3719 case REQ_REFRESH:
3720 report("Refreshing is not yet supported for the %s view", view->name);
3721 break;
3723 case REQ_MAXIMIZE:
3724 if (displayed_views() == 2)
3725 maximize_view(view);
3726 break;
3728 case REQ_OPTIONS:
3729 open_option_menu();
3730 break;
3732 case REQ_TOGGLE_LINENO:
3733 toggle_view_option(&opt_line_number, "line numbers");
3734 break;
3736 case REQ_TOGGLE_DATE:
3737 toggle_date();
3738 break;
3740 case REQ_TOGGLE_AUTHOR:
3741 toggle_author();
3742 break;
3744 case REQ_TOGGLE_REV_GRAPH:
3745 toggle_view_option(&opt_rev_graph, "revision graph display");
3746 break;
3748 case REQ_TOGGLE_REFS:
3749 toggle_view_option(&opt_show_refs, "reference display");
3750 break;
3752 case REQ_TOGGLE_SORT_FIELD:
3753 case REQ_TOGGLE_SORT_ORDER:
3754 report("Sorting is not yet supported for the %s view", view->name);
3755 break;
3757 case REQ_SEARCH:
3758 case REQ_SEARCH_BACK:
3759 search_view(view, request);
3760 break;
3762 case REQ_FIND_NEXT:
3763 case REQ_FIND_PREV:
3764 find_next(view, request);
3765 break;
3767 case REQ_STOP_LOADING:
3768 foreach_view(view, i) {
3769 if (view->pipe)
3770 report("Stopped loading the %s view", view->name),
3771 end_update(view, TRUE);
3773 break;
3775 case REQ_SHOW_VERSION:
3776 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3777 return TRUE;
3779 case REQ_SCREEN_REDRAW:
3780 redraw_display(TRUE);
3781 break;
3783 case REQ_EDIT:
3784 report("Nothing to edit");
3785 break;
3787 case REQ_ENTER:
3788 report("Nothing to enter");
3789 break;
3791 case REQ_VIEW_CLOSE:
3792 /* XXX: Mark closed views by letting view->parent point to the
3793 * view itself. Parents to closed view should never be
3794 * followed. */
3795 if (view->parent &&
3796 view->parent->parent != view->parent) {
3797 maximize_view(view->parent);
3798 view->parent = view;
3799 break;
3801 /* Fall-through */
3802 case REQ_QUIT:
3803 return FALSE;
3805 default:
3806 report("Unknown key, press %s for help",
3807 get_key(view->keymap, REQ_VIEW_HELP));
3808 return TRUE;
3811 return TRUE;
3816 * View backend utilities
3819 enum sort_field {
3820 ORDERBY_NAME,
3821 ORDERBY_DATE,
3822 ORDERBY_AUTHOR,
3825 struct sort_state {
3826 const enum sort_field *fields;
3827 size_t size, current;
3828 bool reverse;
3831 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3832 #define get_sort_field(state) ((state).fields[(state).current])
3833 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3835 static void
3836 sort_view(struct view *view, enum request request, struct sort_state *state,
3837 int (*compare)(const void *, const void *))
3839 switch (request) {
3840 case REQ_TOGGLE_SORT_FIELD:
3841 state->current = (state->current + 1) % state->size;
3842 break;
3844 case REQ_TOGGLE_SORT_ORDER:
3845 state->reverse = !state->reverse;
3846 break;
3847 default:
3848 die("Not a sort request");
3851 qsort(view->line, view->lines, sizeof(*view->line), compare);
3852 redraw_view(view);
3855 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3857 /* Small author cache to reduce memory consumption. It uses binary
3858 * search to lookup or find place to position new entries. No entries
3859 * are ever freed. */
3860 static const char *
3861 get_author(const char *name)
3863 static const char **authors;
3864 static size_t authors_size;
3865 int from = 0, to = authors_size - 1;
3867 while (from <= to) {
3868 size_t pos = (to + from) / 2;
3869 int cmp = strcmp(name, authors[pos]);
3871 if (!cmp)
3872 return authors[pos];
3874 if (cmp < 0)
3875 to = pos - 1;
3876 else
3877 from = pos + 1;
3880 if (!realloc_authors(&authors, authors_size, 1))
3881 return NULL;
3882 name = strdup(name);
3883 if (!name)
3884 return NULL;
3886 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3887 authors[from] = name;
3888 authors_size++;
3890 return name;
3893 static void
3894 parse_timesec(struct time *time, const char *sec)
3896 time->sec = (time_t) atol(sec);
3899 static void
3900 parse_timezone(struct time *time, const char *zone)
3902 long tz;
3904 tz = ('0' - zone[1]) * 60 * 60 * 10;
3905 tz += ('0' - zone[2]) * 60 * 60;
3906 tz += ('0' - zone[3]) * 60 * 10;
3907 tz += ('0' - zone[4]) * 60;
3909 if (zone[0] == '-')
3910 tz = -tz;
3912 time->tz = tz;
3913 time->sec -= tz;
3916 /* Parse author lines where the name may be empty:
3917 * author <email@address.tld> 1138474660 +0100
3919 static void
3920 parse_author_line(char *ident, const char **author, struct time *time)
3922 char *nameend = strchr(ident, '<');
3923 char *emailend = strchr(ident, '>');
3925 if (nameend && emailend)
3926 *nameend = *emailend = 0;
3927 ident = chomp_string(ident);
3928 if (!*ident) {
3929 if (nameend)
3930 ident = chomp_string(nameend + 1);
3931 if (!*ident)
3932 ident = "Unknown";
3935 *author = get_author(ident);
3937 /* Parse epoch and timezone */
3938 if (emailend && emailend[1] == ' ') {
3939 char *secs = emailend + 2;
3940 char *zone = strchr(secs, ' ');
3942 parse_timesec(time, secs);
3944 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3945 parse_timezone(time, zone + 1);
3949 static bool
3950 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3952 char rev[SIZEOF_REV];
3953 const char *revlist_argv[] = {
3954 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3956 struct menu_item *items;
3957 char text[SIZEOF_STR];
3958 bool ok = TRUE;
3959 int i;
3961 items = calloc(*parents + 1, sizeof(*items));
3962 if (!items)
3963 return FALSE;
3965 for (i = 0; i < *parents; i++) {
3966 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3967 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3968 !(items[i].text = strdup(text))) {
3969 ok = FALSE;
3970 break;
3974 if (ok) {
3975 *parents = 0;
3976 ok = prompt_menu("Select parent", items, parents);
3978 for (i = 0; items[i].text; i++)
3979 free((char *) items[i].text);
3980 free(items);
3981 return ok;
3984 static bool
3985 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3987 char buf[SIZEOF_STR * 4];
3988 const char *revlist_argv[] = {
3989 "git", "log", "--no-color", "-1",
3990 "--pretty=format:%P", id, "--", path, NULL
3992 int parents;
3994 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3995 (parents = strlen(buf) / 40) < 0) {
3996 report("Failed to get parent information");
3997 return FALSE;
3999 } else if (parents == 0) {
4000 if (path)
4001 report("Path '%s' does not exist in the parent", path);
4002 else
4003 report("The selected commit has no parents");
4004 return FALSE;
4007 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4008 return FALSE;
4010 string_copy_rev(rev, &buf[41 * parents]);
4011 return TRUE;
4015 * Pager backend
4018 static bool
4019 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4021 char text[SIZEOF_STR];
4023 if (opt_line_number && draw_lineno(view, lineno))
4024 return TRUE;
4026 string_expand(text, sizeof(text), line->data, opt_tab_size);
4027 draw_text(view, line->type, text, TRUE);
4028 return TRUE;
4031 static bool
4032 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4034 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4035 char ref[SIZEOF_STR];
4037 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4038 return TRUE;
4040 /* This is the only fatal call, since it can "corrupt" the buffer. */
4041 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4042 return FALSE;
4044 return TRUE;
4047 static void
4048 add_pager_refs(struct view *view, struct line *line)
4050 char buf[SIZEOF_STR];
4051 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4052 struct ref_list *list;
4053 size_t bufpos = 0, i;
4054 const char *sep = "Refs: ";
4055 bool is_tag = FALSE;
4057 assert(line->type == LINE_COMMIT);
4059 list = get_ref_list(commit_id);
4060 if (!list) {
4061 if (view->type == VIEW_DIFF)
4062 goto try_add_describe_ref;
4063 return;
4066 for (i = 0; i < list->size; i++) {
4067 struct ref *ref = list->refs[i];
4068 const char *fmt = ref->tag ? "%s[%s]" :
4069 ref->remote ? "%s<%s>" : "%s%s";
4071 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4072 return;
4073 sep = ", ";
4074 if (ref->tag)
4075 is_tag = TRUE;
4078 if (!is_tag && view->type == VIEW_DIFF) {
4079 try_add_describe_ref:
4080 /* Add <tag>-g<commit_id> "fake" reference. */
4081 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4082 return;
4085 if (bufpos == 0)
4086 return;
4088 add_line_text(view, buf, LINE_PP_REFS);
4091 static bool
4092 pager_read(struct view *view, char *data)
4094 struct line *line;
4096 if (!data)
4097 return TRUE;
4099 line = add_line_text(view, data, get_line_type(data));
4100 if (!line)
4101 return FALSE;
4103 if (line->type == LINE_COMMIT &&
4104 (view->type == VIEW_DIFF ||
4105 view->type == VIEW_LOG))
4106 add_pager_refs(view, line);
4108 return TRUE;
4111 static enum request
4112 pager_request(struct view *view, enum request request, struct line *line)
4114 int split = 0;
4116 if (request != REQ_ENTER)
4117 return request;
4119 if (line->type == LINE_COMMIT &&
4120 (view->type == VIEW_LOG ||
4121 view->type == VIEW_PAGER)) {
4122 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4123 split = 1;
4126 /* Always scroll the view even if it was split. That way
4127 * you can use Enter to scroll through the log view and
4128 * split open each commit diff. */
4129 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4131 /* FIXME: A minor workaround. Scrolling the view will call report("")
4132 * but if we are scrolling a non-current view this won't properly
4133 * update the view title. */
4134 if (split)
4135 update_view_title(view);
4137 return REQ_NONE;
4140 static bool
4141 pager_grep(struct view *view, struct line *line)
4143 const char *text[] = { line->data, NULL };
4145 return grep_text(view, text);
4148 static void
4149 pager_select(struct view *view, struct line *line)
4151 if (line->type == LINE_COMMIT) {
4152 char *text = (char *)line->data + STRING_SIZE("commit ");
4154 if (view->type != VIEW_PAGER)
4155 string_copy_rev(view->ref, text);
4156 string_copy_rev(ref_commit, text);
4160 static struct view_ops pager_ops = {
4161 "line",
4162 NULL,
4163 NULL,
4164 pager_read,
4165 pager_draw,
4166 pager_request,
4167 pager_grep,
4168 pager_select,
4171 static const char *log_argv[SIZEOF_ARG] = {
4172 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4175 static enum request
4176 log_request(struct view *view, enum request request, struct line *line)
4178 switch (request) {
4179 case REQ_REFRESH:
4180 load_refs();
4181 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4182 return REQ_NONE;
4183 default:
4184 return pager_request(view, request, line);
4188 static struct view_ops log_ops = {
4189 "line",
4190 log_argv,
4191 NULL,
4192 pager_read,
4193 pager_draw,
4194 log_request,
4195 pager_grep,
4196 pager_select,
4199 static const char *diff_argv[SIZEOF_ARG] = {
4200 "git", "show", "--pretty=fuller", "--no-color", "--root",
4201 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4204 static struct view_ops diff_ops = {
4205 "line",
4206 diff_argv,
4207 NULL,
4208 pager_read,
4209 pager_draw,
4210 pager_request,
4211 pager_grep,
4212 pager_select,
4216 * Help backend
4219 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4221 static bool
4222 help_open_keymap_title(struct view *view, enum keymap keymap)
4224 struct line *line;
4226 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4227 help_keymap_hidden[keymap] ? '+' : '-',
4228 enum_name(keymap_table[keymap]));
4229 if (line)
4230 line->other = keymap;
4232 return help_keymap_hidden[keymap];
4235 static void
4236 help_open_keymap(struct view *view, enum keymap keymap)
4238 const char *group = NULL;
4239 char buf[SIZEOF_STR];
4240 size_t bufpos;
4241 bool add_title = TRUE;
4242 int i;
4244 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4245 const char *key = NULL;
4247 if (req_info[i].request == REQ_NONE)
4248 continue;
4250 if (!req_info[i].request) {
4251 group = req_info[i].help;
4252 continue;
4255 key = get_keys(keymap, req_info[i].request, TRUE);
4256 if (!key || !*key)
4257 continue;
4259 if (add_title && help_open_keymap_title(view, keymap))
4260 return;
4261 add_title = FALSE;
4263 if (group) {
4264 add_line_text(view, group, LINE_HELP_GROUP);
4265 group = NULL;
4268 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4269 enum_name(req_info[i]), req_info[i].help);
4272 group = "External commands:";
4274 for (i = 0; i < run_requests; i++) {
4275 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4276 const char *key;
4277 int argc;
4279 if (!req || req->keymap != keymap)
4280 continue;
4282 key = get_key_name(req->key);
4283 if (!*key)
4284 key = "(no key defined)";
4286 if (add_title && help_open_keymap_title(view, keymap))
4287 return;
4288 if (group) {
4289 add_line_text(view, group, LINE_HELP_GROUP);
4290 group = NULL;
4293 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4294 if (!string_format_from(buf, &bufpos, "%s%s",
4295 argc ? " " : "", req->argv[argc]))
4296 return;
4298 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4302 static bool
4303 help_open(struct view *view)
4305 enum keymap keymap;
4307 reset_view(view);
4308 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4309 add_line_text(view, "", LINE_DEFAULT);
4311 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4312 help_open_keymap(view, keymap);
4314 return TRUE;
4317 static enum request
4318 help_request(struct view *view, enum request request, struct line *line)
4320 switch (request) {
4321 case REQ_ENTER:
4322 if (line->type == LINE_HELP_KEYMAP) {
4323 help_keymap_hidden[line->other] =
4324 !help_keymap_hidden[line->other];
4325 view->p_restore = TRUE;
4326 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4329 return REQ_NONE;
4330 default:
4331 return pager_request(view, request, line);
4335 static struct view_ops help_ops = {
4336 "line",
4337 NULL,
4338 help_open,
4339 NULL,
4340 pager_draw,
4341 help_request,
4342 pager_grep,
4343 pager_select,
4348 * Tree backend
4351 struct tree_stack_entry {
4352 struct tree_stack_entry *prev; /* Entry below this in the stack */
4353 unsigned long lineno; /* Line number to restore */
4354 char *name; /* Position of name in opt_path */
4357 /* The top of the path stack. */
4358 static struct tree_stack_entry *tree_stack = NULL;
4359 unsigned long tree_lineno = 0;
4361 static void
4362 pop_tree_stack_entry(void)
4364 struct tree_stack_entry *entry = tree_stack;
4366 tree_lineno = entry->lineno;
4367 entry->name[0] = 0;
4368 tree_stack = entry->prev;
4369 free(entry);
4372 static void
4373 push_tree_stack_entry(const char *name, unsigned long lineno)
4375 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4376 size_t pathlen = strlen(opt_path);
4378 if (!entry)
4379 return;
4381 entry->prev = tree_stack;
4382 entry->name = opt_path + pathlen;
4383 tree_stack = entry;
4385 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4386 pop_tree_stack_entry();
4387 return;
4390 /* Move the current line to the first tree entry. */
4391 tree_lineno = 1;
4392 entry->lineno = lineno;
4395 /* Parse output from git-ls-tree(1):
4397 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4400 #define SIZEOF_TREE_ATTR \
4401 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4403 #define SIZEOF_TREE_MODE \
4404 STRING_SIZE("100644 ")
4406 #define TREE_ID_OFFSET \
4407 STRING_SIZE("100644 blob ")
4409 struct tree_entry {
4410 char id[SIZEOF_REV];
4411 mode_t mode;
4412 struct time time; /* Date from the author ident. */
4413 const char *author; /* Author of the commit. */
4414 char name[1];
4417 static const char *
4418 tree_path(const struct line *line)
4420 return ((struct tree_entry *) line->data)->name;
4423 static int
4424 tree_compare_entry(const struct line *line1, const struct line *line2)
4426 if (line1->type != line2->type)
4427 return line1->type == LINE_TREE_DIR ? -1 : 1;
4428 return strcmp(tree_path(line1), tree_path(line2));
4431 static const enum sort_field tree_sort_fields[] = {
4432 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4434 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4436 static int
4437 tree_compare(const void *l1, const void *l2)
4439 const struct line *line1 = (const struct line *) l1;
4440 const struct line *line2 = (const struct line *) l2;
4441 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4442 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4444 if (line1->type == LINE_TREE_HEAD)
4445 return -1;
4446 if (line2->type == LINE_TREE_HEAD)
4447 return 1;
4449 switch (get_sort_field(tree_sort_state)) {
4450 case ORDERBY_DATE:
4451 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4453 case ORDERBY_AUTHOR:
4454 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4456 case ORDERBY_NAME:
4457 default:
4458 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4463 static struct line *
4464 tree_entry(struct view *view, enum line_type type, const char *path,
4465 const char *mode, const char *id)
4467 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4468 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4470 if (!entry || !line) {
4471 free(entry);
4472 return NULL;
4475 strncpy(entry->name, path, strlen(path));
4476 if (mode)
4477 entry->mode = strtoul(mode, NULL, 8);
4478 if (id)
4479 string_copy_rev(entry->id, id);
4481 return line;
4484 static bool
4485 tree_read_date(struct view *view, char *text, bool *read_date)
4487 static const char *author_name;
4488 static struct time author_time;
4490 if (!text && *read_date) {
4491 *read_date = FALSE;
4492 return TRUE;
4494 } else if (!text) {
4495 char *path = *opt_path ? opt_path : ".";
4496 /* Find next entry to process */
4497 const char *log_file[] = {
4498 "git", "log", "--no-color", "--pretty=raw",
4499 "--cc", "--raw", view->id, "--", path, NULL
4501 struct io io = {};
4503 if (!view->lines) {
4504 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4505 report("Tree is empty");
4506 return TRUE;
4509 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4510 report("Failed to load tree data");
4511 return TRUE;
4514 io_done(view->pipe);
4515 view->io = io;
4516 *read_date = TRUE;
4517 return FALSE;
4519 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4520 parse_author_line(text + STRING_SIZE("author "),
4521 &author_name, &author_time);
4523 } else if (*text == ':') {
4524 char *pos;
4525 size_t annotated = 1;
4526 size_t i;
4528 pos = strchr(text, '\t');
4529 if (!pos)
4530 return TRUE;
4531 text = pos + 1;
4532 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4533 text += strlen(opt_path);
4534 pos = strchr(text, '/');
4535 if (pos)
4536 *pos = 0;
4538 for (i = 1; i < view->lines; i++) {
4539 struct line *line = &view->line[i];
4540 struct tree_entry *entry = line->data;
4542 annotated += !!entry->author;
4543 if (entry->author || strcmp(entry->name, text))
4544 continue;
4546 entry->author = author_name;
4547 entry->time = author_time;
4548 line->dirty = 1;
4549 break;
4552 if (annotated == view->lines)
4553 io_kill(view->pipe);
4555 return TRUE;
4558 static bool
4559 tree_read(struct view *view, char *text)
4561 static bool read_date = FALSE;
4562 struct tree_entry *data;
4563 struct line *entry, *line;
4564 enum line_type type;
4565 size_t textlen = text ? strlen(text) : 0;
4566 char *path = text + SIZEOF_TREE_ATTR;
4568 if (read_date || !text)
4569 return tree_read_date(view, text, &read_date);
4571 if (textlen <= SIZEOF_TREE_ATTR)
4572 return FALSE;
4573 if (view->lines == 0 &&
4574 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4575 return FALSE;
4577 /* Strip the path part ... */
4578 if (*opt_path) {
4579 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4580 size_t striplen = strlen(opt_path);
4582 if (pathlen > striplen)
4583 memmove(path, path + striplen,
4584 pathlen - striplen + 1);
4586 /* Insert "link" to parent directory. */
4587 if (view->lines == 1 &&
4588 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4589 return FALSE;
4592 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4593 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4594 if (!entry)
4595 return FALSE;
4596 data = entry->data;
4598 /* Skip "Directory ..." and ".." line. */
4599 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4600 if (tree_compare_entry(line, entry) <= 0)
4601 continue;
4603 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4605 line->data = data;
4606 line->type = type;
4607 for (; line <= entry; line++)
4608 line->dirty = line->cleareol = 1;
4609 return TRUE;
4612 if (tree_lineno > view->lineno) {
4613 view->lineno = tree_lineno;
4614 tree_lineno = 0;
4617 return TRUE;
4620 static bool
4621 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4623 struct tree_entry *entry = line->data;
4625 if (line->type == LINE_TREE_HEAD) {
4626 if (draw_text(view, line->type, "Directory path /", TRUE))
4627 return TRUE;
4628 } else {
4629 if (draw_mode(view, entry->mode))
4630 return TRUE;
4632 if (opt_author && draw_author(view, entry->author))
4633 return TRUE;
4635 if (opt_date && draw_date(view, &entry->time))
4636 return TRUE;
4638 if (draw_text(view, line->type, entry->name, TRUE))
4639 return TRUE;
4640 return TRUE;
4643 static void
4644 open_blob_editor()
4646 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4647 int fd = mkstemp(file);
4649 if (fd == -1)
4650 report("Failed to create temporary file");
4651 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4652 report("Failed to save blob data to file");
4653 else
4654 open_editor(file);
4655 if (fd != -1)
4656 unlink(file);
4659 static enum request
4660 tree_request(struct view *view, enum request request, struct line *line)
4662 enum open_flags flags;
4664 switch (request) {
4665 case REQ_VIEW_BLAME:
4666 if (line->type != LINE_TREE_FILE) {
4667 report("Blame only supported for files");
4668 return REQ_NONE;
4671 string_copy(opt_ref, view->vid);
4672 return request;
4674 case REQ_EDIT:
4675 if (line->type != LINE_TREE_FILE) {
4676 report("Edit only supported for files");
4677 } else if (!is_head_commit(view->vid)) {
4678 open_blob_editor();
4679 } else {
4680 open_editor(opt_file);
4682 return REQ_NONE;
4684 case REQ_TOGGLE_SORT_FIELD:
4685 case REQ_TOGGLE_SORT_ORDER:
4686 sort_view(view, request, &tree_sort_state, tree_compare);
4687 return REQ_NONE;
4689 case REQ_PARENT:
4690 if (!*opt_path) {
4691 /* quit view if at top of tree */
4692 return REQ_VIEW_CLOSE;
4694 /* fake 'cd ..' */
4695 line = &view->line[1];
4696 break;
4698 case REQ_ENTER:
4699 break;
4701 default:
4702 return request;
4705 /* Cleanup the stack if the tree view is at a different tree. */
4706 while (!*opt_path && tree_stack)
4707 pop_tree_stack_entry();
4709 switch (line->type) {
4710 case LINE_TREE_DIR:
4711 /* Depending on whether it is a subdirectory or parent link
4712 * mangle the path buffer. */
4713 if (line == &view->line[1] && *opt_path) {
4714 pop_tree_stack_entry();
4716 } else {
4717 const char *basename = tree_path(line);
4719 push_tree_stack_entry(basename, view->lineno);
4722 /* Trees and subtrees share the same ID, so they are not not
4723 * unique like blobs. */
4724 flags = OPEN_RELOAD;
4725 request = REQ_VIEW_TREE;
4726 break;
4728 case LINE_TREE_FILE:
4729 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4730 request = REQ_VIEW_BLOB;
4731 break;
4733 default:
4734 return REQ_NONE;
4737 open_view(view, request, flags);
4738 if (request == REQ_VIEW_TREE)
4739 view->lineno = tree_lineno;
4741 return REQ_NONE;
4744 static bool
4745 tree_grep(struct view *view, struct line *line)
4747 struct tree_entry *entry = line->data;
4748 const char *text[] = {
4749 entry->name,
4750 opt_author ? entry->author : "",
4751 mkdate(&entry->time, opt_date),
4752 NULL
4755 return grep_text(view, text);
4758 static void
4759 tree_select(struct view *view, struct line *line)
4761 struct tree_entry *entry = line->data;
4763 if (line->type == LINE_TREE_FILE) {
4764 string_copy_rev(ref_blob, entry->id);
4765 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4767 } else if (line->type != LINE_TREE_DIR) {
4768 return;
4771 string_copy_rev(view->ref, entry->id);
4774 static bool
4775 tree_prepare(struct view *view)
4777 if (view->lines == 0 && opt_prefix[0]) {
4778 char *pos = opt_prefix;
4780 while (pos && *pos) {
4781 char *end = strchr(pos, '/');
4783 if (end)
4784 *end = 0;
4785 push_tree_stack_entry(pos, 0);
4786 pos = end;
4787 if (end) {
4788 *end = '/';
4789 pos++;
4793 } else if (strcmp(view->vid, view->id)) {
4794 opt_path[0] = 0;
4797 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4800 static const char *tree_argv[SIZEOF_ARG] = {
4801 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4804 static struct view_ops tree_ops = {
4805 "file",
4806 tree_argv,
4807 NULL,
4808 tree_read,
4809 tree_draw,
4810 tree_request,
4811 tree_grep,
4812 tree_select,
4813 tree_prepare,
4816 static bool
4817 blob_read(struct view *view, char *line)
4819 if (!line)
4820 return TRUE;
4821 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4824 static enum request
4825 blob_request(struct view *view, enum request request, struct line *line)
4827 switch (request) {
4828 case REQ_EDIT:
4829 open_blob_editor();
4830 return REQ_NONE;
4831 default:
4832 return pager_request(view, request, line);
4836 static const char *blob_argv[SIZEOF_ARG] = {
4837 "git", "cat-file", "blob", "%(blob)", NULL
4840 static struct view_ops blob_ops = {
4841 "line",
4842 blob_argv,
4843 NULL,
4844 blob_read,
4845 pager_draw,
4846 blob_request,
4847 pager_grep,
4848 pager_select,
4852 * Blame backend
4854 * Loading the blame view is a two phase job:
4856 * 1. File content is read either using opt_file from the
4857 * filesystem or using git-cat-file.
4858 * 2. Then blame information is incrementally added by
4859 * reading output from git-blame.
4862 static const char *blame_head_argv[] = {
4863 "git", "blame", "--incremental", "--", "%(file)", NULL
4866 static const char *blame_ref_argv[] = {
4867 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4870 static const char *blame_cat_file_argv[] = {
4871 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4874 struct blame_commit {
4875 char id[SIZEOF_REV]; /* SHA1 ID. */
4876 char title[128]; /* First line of the commit message. */
4877 const char *author; /* Author of the commit. */
4878 struct time time; /* Date from the author ident. */
4879 char filename[128]; /* Name of file. */
4880 bool has_previous; /* Was a "previous" line detected. */
4883 struct blame {
4884 struct blame_commit *commit;
4885 unsigned long lineno;
4886 char text[1];
4889 static bool
4890 blame_open(struct view *view)
4892 char path[SIZEOF_STR];
4894 if (!view->parent && *opt_prefix) {
4895 string_copy(path, opt_file);
4896 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4897 return FALSE;
4900 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4901 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4902 return FALSE;
4905 setup_update(view, opt_file);
4906 string_format(view->ref, "%s ...", opt_file);
4908 return TRUE;
4911 static struct blame_commit *
4912 get_blame_commit(struct view *view, const char *id)
4914 size_t i;
4916 for (i = 0; i < view->lines; i++) {
4917 struct blame *blame = view->line[i].data;
4919 if (!blame->commit)
4920 continue;
4922 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4923 return blame->commit;
4927 struct blame_commit *commit = calloc(1, sizeof(*commit));
4929 if (commit)
4930 string_ncopy(commit->id, id, SIZEOF_REV);
4931 return commit;
4935 static bool
4936 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4938 const char *pos = *posref;
4940 *posref = NULL;
4941 pos = strchr(pos + 1, ' ');
4942 if (!pos || !isdigit(pos[1]))
4943 return FALSE;
4944 *number = atoi(pos + 1);
4945 if (*number < min || *number > max)
4946 return FALSE;
4948 *posref = pos;
4949 return TRUE;
4952 static struct blame_commit *
4953 parse_blame_commit(struct view *view, const char *text, int *blamed)
4955 struct blame_commit *commit;
4956 struct blame *blame;
4957 const char *pos = text + SIZEOF_REV - 2;
4958 size_t orig_lineno = 0;
4959 size_t lineno;
4960 size_t group;
4962 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4963 return NULL;
4965 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4966 !parse_number(&pos, &lineno, 1, view->lines) ||
4967 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4968 return NULL;
4970 commit = get_blame_commit(view, text);
4971 if (!commit)
4972 return NULL;
4974 *blamed += group;
4975 while (group--) {
4976 struct line *line = &view->line[lineno + group - 1];
4978 blame = line->data;
4979 blame->commit = commit;
4980 blame->lineno = orig_lineno + group - 1;
4981 line->dirty = 1;
4984 return commit;
4987 static bool
4988 blame_read_file(struct view *view, const char *line, bool *read_file)
4990 if (!line) {
4991 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4992 struct io io = {};
4994 if (view->lines == 0 && !view->parent)
4995 die("No blame exist for %s", view->vid);
4997 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4998 report("Failed to load blame data");
4999 return TRUE;
5002 io_done(view->pipe);
5003 view->io = io;
5004 *read_file = FALSE;
5005 return FALSE;
5007 } else {
5008 size_t linelen = strlen(line);
5009 struct blame *blame = malloc(sizeof(*blame) + linelen);
5011 if (!blame)
5012 return FALSE;
5014 blame->commit = NULL;
5015 strncpy(blame->text, line, linelen);
5016 blame->text[linelen] = 0;
5017 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5021 static bool
5022 match_blame_header(const char *name, char **line)
5024 size_t namelen = strlen(name);
5025 bool matched = !strncmp(name, *line, namelen);
5027 if (matched)
5028 *line += namelen;
5030 return matched;
5033 static bool
5034 blame_read(struct view *view, char *line)
5036 static struct blame_commit *commit = NULL;
5037 static int blamed = 0;
5038 static bool read_file = TRUE;
5040 if (read_file)
5041 return blame_read_file(view, line, &read_file);
5043 if (!line) {
5044 /* Reset all! */
5045 commit = NULL;
5046 blamed = 0;
5047 read_file = TRUE;
5048 string_format(view->ref, "%s", view->vid);
5049 if (view_is_displayed(view)) {
5050 update_view_title(view);
5051 redraw_view_from(view, 0);
5053 return TRUE;
5056 if (!commit) {
5057 commit = parse_blame_commit(view, line, &blamed);
5058 string_format(view->ref, "%s %2d%%", view->vid,
5059 view->lines ? blamed * 100 / view->lines : 0);
5061 } else if (match_blame_header("author ", &line)) {
5062 commit->author = get_author(line);
5064 } else if (match_blame_header("author-time ", &line)) {
5065 parse_timesec(&commit->time, line);
5067 } else if (match_blame_header("author-tz ", &line)) {
5068 parse_timezone(&commit->time, line);
5070 } else if (match_blame_header("summary ", &line)) {
5071 string_ncopy(commit->title, line, strlen(line));
5073 } else if (match_blame_header("previous ", &line)) {
5074 commit->has_previous = TRUE;
5076 } else if (match_blame_header("filename ", &line)) {
5077 string_ncopy(commit->filename, line, strlen(line));
5078 commit = NULL;
5081 return TRUE;
5084 static bool
5085 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5087 struct blame *blame = line->data;
5088 struct time *time = NULL;
5089 const char *id = NULL, *author = NULL;
5090 char text[SIZEOF_STR];
5092 if (blame->commit && *blame->commit->filename) {
5093 id = blame->commit->id;
5094 author = blame->commit->author;
5095 time = &blame->commit->time;
5098 if (opt_date && draw_date(view, time))
5099 return TRUE;
5101 if (opt_author && draw_author(view, author))
5102 return TRUE;
5104 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5105 return TRUE;
5107 if (draw_lineno(view, lineno))
5108 return TRUE;
5110 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5111 draw_text(view, LINE_DEFAULT, text, TRUE);
5112 return TRUE;
5115 static bool
5116 check_blame_commit(struct blame *blame, bool check_null_id)
5118 if (!blame->commit)
5119 report("Commit data not loaded yet");
5120 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5121 report("No commit exist for the selected line");
5122 else
5123 return TRUE;
5124 return FALSE;
5127 static void
5128 setup_blame_parent_line(struct view *view, struct blame *blame)
5130 const char *diff_tree_argv[] = {
5131 "git", "diff-tree", "-U0", blame->commit->id,
5132 "--", blame->commit->filename, NULL
5134 struct io io = {};
5135 int parent_lineno = -1;
5136 int blamed_lineno = -1;
5137 char *line;
5139 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5140 return;
5142 while ((line = io_get(&io, '\n', TRUE))) {
5143 if (*line == '@') {
5144 char *pos = strchr(line, '+');
5146 parent_lineno = atoi(line + 4);
5147 if (pos)
5148 blamed_lineno = atoi(pos + 1);
5150 } else if (*line == '+' && parent_lineno != -1) {
5151 if (blame->lineno == blamed_lineno - 1 &&
5152 !strcmp(blame->text, line + 1)) {
5153 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5154 break;
5156 blamed_lineno++;
5160 io_done(&io);
5163 static enum request
5164 blame_request(struct view *view, enum request request, struct line *line)
5166 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5167 struct blame *blame = line->data;
5169 switch (request) {
5170 case REQ_VIEW_BLAME:
5171 if (check_blame_commit(blame, TRUE)) {
5172 string_copy(opt_ref, blame->commit->id);
5173 string_copy(opt_file, blame->commit->filename);
5174 if (blame->lineno)
5175 view->lineno = blame->lineno;
5176 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5178 break;
5180 case REQ_PARENT:
5181 if (check_blame_commit(blame, TRUE) &&
5182 select_commit_parent(blame->commit->id, opt_ref,
5183 blame->commit->filename)) {
5184 string_copy(opt_file, blame->commit->filename);
5185 setup_blame_parent_line(view, blame);
5186 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5188 break;
5190 case REQ_ENTER:
5191 if (!check_blame_commit(blame, FALSE))
5192 break;
5194 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5195 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5196 break;
5198 if (!strcmp(blame->commit->id, NULL_ID)) {
5199 struct view *diff = VIEW(REQ_VIEW_DIFF);
5200 const char *diff_index_argv[] = {
5201 "git", "diff-index", "--root", "--patch-with-stat",
5202 "-C", "-M", "HEAD", "--", view->vid, NULL
5205 if (!blame->commit->has_previous) {
5206 diff_index_argv[1] = "diff";
5207 diff_index_argv[2] = "--no-color";
5208 diff_index_argv[6] = "--";
5209 diff_index_argv[7] = "/dev/null";
5212 if (!prepare_update(diff, diff_index_argv, NULL)) {
5213 report("Failed to allocate diff command");
5214 break;
5216 flags |= OPEN_PREPARED;
5219 open_view(view, REQ_VIEW_DIFF, flags);
5220 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5221 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5222 break;
5224 default:
5225 return request;
5228 return REQ_NONE;
5231 static bool
5232 blame_grep(struct view *view, struct line *line)
5234 struct blame *blame = line->data;
5235 struct blame_commit *commit = blame->commit;
5236 const char *text[] = {
5237 blame->text,
5238 commit ? commit->title : "",
5239 commit ? commit->id : "",
5240 commit && opt_author ? commit->author : "",
5241 commit ? mkdate(&commit->time, opt_date) : "",
5242 NULL
5245 return grep_text(view, text);
5248 static void
5249 blame_select(struct view *view, struct line *line)
5251 struct blame *blame = line->data;
5252 struct blame_commit *commit = blame->commit;
5254 if (!commit)
5255 return;
5257 if (!strcmp(commit->id, NULL_ID))
5258 string_ncopy(ref_commit, "HEAD", 4);
5259 else
5260 string_copy_rev(ref_commit, commit->id);
5263 static struct view_ops blame_ops = {
5264 "line",
5265 NULL,
5266 blame_open,
5267 blame_read,
5268 blame_draw,
5269 blame_request,
5270 blame_grep,
5271 blame_select,
5275 * Branch backend
5278 struct branch {
5279 const char *author; /* Author of the last commit. */
5280 struct time time; /* Date of the last activity. */
5281 const struct ref *ref; /* Name and commit ID information. */
5284 static const struct ref branch_all;
5286 static const enum sort_field branch_sort_fields[] = {
5287 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5289 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5291 static int
5292 branch_compare(const void *l1, const void *l2)
5294 const struct branch *branch1 = ((const struct line *) l1)->data;
5295 const struct branch *branch2 = ((const struct line *) l2)->data;
5297 switch (get_sort_field(branch_sort_state)) {
5298 case ORDERBY_DATE:
5299 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5301 case ORDERBY_AUTHOR:
5302 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5304 case ORDERBY_NAME:
5305 default:
5306 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5310 static bool
5311 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5313 struct branch *branch = line->data;
5314 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5316 if (opt_date && draw_date(view, &branch->time))
5317 return TRUE;
5319 if (opt_author && draw_author(view, branch->author))
5320 return TRUE;
5322 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5323 return TRUE;
5326 static enum request
5327 branch_request(struct view *view, enum request request, struct line *line)
5329 struct branch *branch = line->data;
5331 switch (request) {
5332 case REQ_REFRESH:
5333 load_refs();
5334 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5335 return REQ_NONE;
5337 case REQ_TOGGLE_SORT_FIELD:
5338 case REQ_TOGGLE_SORT_ORDER:
5339 sort_view(view, request, &branch_sort_state, branch_compare);
5340 return REQ_NONE;
5342 case REQ_ENTER:
5343 if (branch->ref == &branch_all) {
5344 const char *all_branches_argv[] = {
5345 "git", "log", "--no-color", "--pretty=raw", "--parents",
5346 "--topo-order", "--all", NULL
5348 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5350 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5351 report("Failed to load view of all branches");
5352 return REQ_NONE;
5354 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5355 } else {
5356 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5358 return REQ_NONE;
5360 default:
5361 return request;
5365 static bool
5366 branch_read(struct view *view, char *line)
5368 static char id[SIZEOF_REV];
5369 struct branch *reference;
5370 size_t i;
5372 if (!line)
5373 return TRUE;
5375 switch (get_line_type(line)) {
5376 case LINE_COMMIT:
5377 string_copy_rev(id, line + STRING_SIZE("commit "));
5378 return TRUE;
5380 case LINE_AUTHOR:
5381 for (i = 0, reference = NULL; i < view->lines; i++) {
5382 struct branch *branch = view->line[i].data;
5384 if (strcmp(branch->ref->id, id))
5385 continue;
5387 view->line[i].dirty = TRUE;
5388 if (reference) {
5389 branch->author = reference->author;
5390 branch->time = reference->time;
5391 continue;
5394 parse_author_line(line + STRING_SIZE("author "),
5395 &branch->author, &branch->time);
5396 reference = branch;
5398 return TRUE;
5400 default:
5401 return TRUE;
5406 static bool
5407 branch_open_visitor(void *data, const struct ref *ref)
5409 struct view *view = data;
5410 struct branch *branch;
5412 if (ref->tag || ref->ltag || ref->remote)
5413 return TRUE;
5415 branch = calloc(1, sizeof(*branch));
5416 if (!branch)
5417 return FALSE;
5419 branch->ref = ref;
5420 return !!add_line_data(view, branch, LINE_DEFAULT);
5423 static bool
5424 branch_open(struct view *view)
5426 const char *branch_log[] = {
5427 "git", "log", "--no-color", "--pretty=raw",
5428 "--simplify-by-decoration", "--all", NULL
5431 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5432 report("Failed to load branch data");
5433 return TRUE;
5436 setup_update(view, view->id);
5437 branch_open_visitor(view, &branch_all);
5438 foreach_ref(branch_open_visitor, view);
5439 view->p_restore = TRUE;
5441 return TRUE;
5444 static bool
5445 branch_grep(struct view *view, struct line *line)
5447 struct branch *branch = line->data;
5448 const char *text[] = {
5449 branch->ref->name,
5450 branch->author,
5451 NULL
5454 return grep_text(view, text);
5457 static void
5458 branch_select(struct view *view, struct line *line)
5460 struct branch *branch = line->data;
5462 string_copy_rev(view->ref, branch->ref->id);
5463 string_copy_rev(ref_commit, branch->ref->id);
5464 string_copy_rev(ref_head, branch->ref->id);
5465 string_copy_rev(ref_branch, branch->ref->name);
5468 static struct view_ops branch_ops = {
5469 "branch",
5470 NULL,
5471 branch_open,
5472 branch_read,
5473 branch_draw,
5474 branch_request,
5475 branch_grep,
5476 branch_select,
5480 * Status backend
5483 struct status {
5484 char status;
5485 struct {
5486 mode_t mode;
5487 char rev[SIZEOF_REV];
5488 char name[SIZEOF_STR];
5489 } old;
5490 struct {
5491 mode_t mode;
5492 char rev[SIZEOF_REV];
5493 char name[SIZEOF_STR];
5494 } new;
5497 static char status_onbranch[SIZEOF_STR];
5498 static struct status stage_status;
5499 static enum line_type stage_line_type;
5500 static size_t stage_chunks;
5501 static int *stage_chunk;
5503 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5505 /* This should work even for the "On branch" line. */
5506 static inline bool
5507 status_has_none(struct view *view, struct line *line)
5509 return line < view->line + view->lines && !line[1].data;
5512 /* Get fields from the diff line:
5513 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5515 static inline bool
5516 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5518 const char *old_mode = buf + 1;
5519 const char *new_mode = buf + 8;
5520 const char *old_rev = buf + 15;
5521 const char *new_rev = buf + 56;
5522 const char *status = buf + 97;
5524 if (bufsize < 98 ||
5525 old_mode[-1] != ':' ||
5526 new_mode[-1] != ' ' ||
5527 old_rev[-1] != ' ' ||
5528 new_rev[-1] != ' ' ||
5529 status[-1] != ' ')
5530 return FALSE;
5532 file->status = *status;
5534 string_copy_rev(file->old.rev, old_rev);
5535 string_copy_rev(file->new.rev, new_rev);
5537 file->old.mode = strtoul(old_mode, NULL, 8);
5538 file->new.mode = strtoul(new_mode, NULL, 8);
5540 file->old.name[0] = file->new.name[0] = 0;
5542 return TRUE;
5545 static bool
5546 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5548 struct status *unmerged = NULL;
5549 char *buf;
5550 struct io io = {};
5552 if (!io_run(&io, argv, opt_cdup, IO_RD))
5553 return FALSE;
5555 add_line_data(view, NULL, type);
5557 while ((buf = io_get(&io, 0, TRUE))) {
5558 struct status *file = unmerged;
5560 if (!file) {
5561 file = calloc(1, sizeof(*file));
5562 if (!file || !add_line_data(view, file, type))
5563 goto error_out;
5566 /* Parse diff info part. */
5567 if (status) {
5568 file->status = status;
5569 if (status == 'A')
5570 string_copy(file->old.rev, NULL_ID);
5572 } else if (!file->status || file == unmerged) {
5573 if (!status_get_diff(file, buf, strlen(buf)))
5574 goto error_out;
5576 buf = io_get(&io, 0, TRUE);
5577 if (!buf)
5578 break;
5580 /* Collapse all modified entries that follow an
5581 * associated unmerged entry. */
5582 if (unmerged == file) {
5583 unmerged->status = 'U';
5584 unmerged = NULL;
5585 } else if (file->status == 'U') {
5586 unmerged = file;
5590 /* Grab the old name for rename/copy. */
5591 if (!*file->old.name &&
5592 (file->status == 'R' || file->status == 'C')) {
5593 string_ncopy(file->old.name, buf, strlen(buf));
5595 buf = io_get(&io, 0, TRUE);
5596 if (!buf)
5597 break;
5600 /* git-ls-files just delivers a NUL separated list of
5601 * file names similar to the second half of the
5602 * git-diff-* output. */
5603 string_ncopy(file->new.name, buf, strlen(buf));
5604 if (!*file->old.name)
5605 string_copy(file->old.name, file->new.name);
5606 file = NULL;
5609 if (io_error(&io)) {
5610 error_out:
5611 io_done(&io);
5612 return FALSE;
5615 if (!view->line[view->lines - 1].data)
5616 add_line_data(view, NULL, LINE_STAT_NONE);
5618 io_done(&io);
5619 return TRUE;
5622 /* Don't show unmerged entries in the staged section. */
5623 static const char *status_diff_index_argv[] = {
5624 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5625 "--cached", "-M", "HEAD", NULL
5628 static const char *status_diff_files_argv[] = {
5629 "git", "diff-files", "-z", NULL
5632 static const char *status_list_other_argv[] = {
5633 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5636 static const char *status_list_no_head_argv[] = {
5637 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5640 static const char *update_index_argv[] = {
5641 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5644 /* Restore the previous line number to stay in the context or select a
5645 * line with something that can be updated. */
5646 static void
5647 status_restore(struct view *view)
5649 if (view->p_lineno >= view->lines)
5650 view->p_lineno = view->lines - 1;
5651 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5652 view->p_lineno++;
5653 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5654 view->p_lineno--;
5656 /* If the above fails, always skip the "On branch" line. */
5657 if (view->p_lineno < view->lines)
5658 view->lineno = view->p_lineno;
5659 else
5660 view->lineno = 1;
5662 if (view->lineno < view->offset)
5663 view->offset = view->lineno;
5664 else if (view->offset + view->height <= view->lineno)
5665 view->offset = view->lineno - view->height + 1;
5667 view->p_restore = FALSE;
5670 static void
5671 status_update_onbranch(void)
5673 static const char *paths[][2] = {
5674 { "rebase-apply/rebasing", "Rebasing" },
5675 { "rebase-apply/applying", "Applying mailbox" },
5676 { "rebase-apply/", "Rebasing mailbox" },
5677 { "rebase-merge/interactive", "Interactive rebase" },
5678 { "rebase-merge/", "Rebase merge" },
5679 { "MERGE_HEAD", "Merging" },
5680 { "BISECT_LOG", "Bisecting" },
5681 { "HEAD", "On branch" },
5683 char buf[SIZEOF_STR];
5684 struct stat stat;
5685 int i;
5687 if (is_initial_commit()) {
5688 string_copy(status_onbranch, "Initial commit");
5689 return;
5692 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5693 char *head = opt_head;
5695 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5696 lstat(buf, &stat) < 0)
5697 continue;
5699 if (!*opt_head) {
5700 struct io io = {};
5702 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5703 io_read_buf(&io, buf, sizeof(buf))) {
5704 head = buf;
5705 if (!prefixcmp(head, "refs/heads/"))
5706 head += STRING_SIZE("refs/heads/");
5710 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5711 string_copy(status_onbranch, opt_head);
5712 return;
5715 string_copy(status_onbranch, "Not currently on any branch");
5718 /* First parse staged info using git-diff-index(1), then parse unstaged
5719 * info using git-diff-files(1), and finally untracked files using
5720 * git-ls-files(1). */
5721 static bool
5722 status_open(struct view *view)
5724 reset_view(view);
5726 add_line_data(view, NULL, LINE_STAT_HEAD);
5727 status_update_onbranch();
5729 io_run_bg(update_index_argv);
5731 if (is_initial_commit()) {
5732 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5733 return FALSE;
5734 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5735 return FALSE;
5738 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5739 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5740 return FALSE;
5742 /* Restore the exact position or use the specialized restore
5743 * mode? */
5744 if (!view->p_restore)
5745 status_restore(view);
5746 return TRUE;
5749 static bool
5750 status_draw(struct view *view, struct line *line, unsigned int lineno)
5752 struct status *status = line->data;
5753 enum line_type type;
5754 const char *text;
5756 if (!status) {
5757 switch (line->type) {
5758 case LINE_STAT_STAGED:
5759 type = LINE_STAT_SECTION;
5760 text = "Changes to be committed:";
5761 break;
5763 case LINE_STAT_UNSTAGED:
5764 type = LINE_STAT_SECTION;
5765 text = "Changed but not updated:";
5766 break;
5768 case LINE_STAT_UNTRACKED:
5769 type = LINE_STAT_SECTION;
5770 text = "Untracked files:";
5771 break;
5773 case LINE_STAT_NONE:
5774 type = LINE_DEFAULT;
5775 text = " (no files)";
5776 break;
5778 case LINE_STAT_HEAD:
5779 type = LINE_STAT_HEAD;
5780 text = status_onbranch;
5781 break;
5783 default:
5784 return FALSE;
5786 } else {
5787 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5789 buf[0] = status->status;
5790 if (draw_text(view, line->type, buf, TRUE))
5791 return TRUE;
5792 type = LINE_DEFAULT;
5793 text = status->new.name;
5796 draw_text(view, type, text, TRUE);
5797 return TRUE;
5800 static enum request
5801 status_load_error(struct view *view, struct view *stage, const char *path)
5803 if (displayed_views() == 2 || display[current_view] != view)
5804 maximize_view(view);
5805 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5806 return REQ_NONE;
5809 static enum request
5810 status_enter(struct view *view, struct line *line)
5812 struct status *status = line->data;
5813 const char *oldpath = status ? status->old.name : NULL;
5814 /* Diffs for unmerged entries are empty when passing the new
5815 * path, so leave it empty. */
5816 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5817 const char *info;
5818 enum open_flags split;
5819 struct view *stage = VIEW(REQ_VIEW_STAGE);
5821 if (line->type == LINE_STAT_NONE ||
5822 (!status && line[1].type == LINE_STAT_NONE)) {
5823 report("No file to diff");
5824 return REQ_NONE;
5827 switch (line->type) {
5828 case LINE_STAT_STAGED:
5829 if (is_initial_commit()) {
5830 const char *no_head_diff_argv[] = {
5831 "git", "diff", "--no-color", "--patch-with-stat",
5832 "--", "/dev/null", newpath, NULL
5835 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5836 return status_load_error(view, stage, newpath);
5837 } else {
5838 const char *index_show_argv[] = {
5839 "git", "diff-index", "--root", "--patch-with-stat",
5840 "-C", "-M", "--cached", "HEAD", "--",
5841 oldpath, newpath, NULL
5844 if (!prepare_update(stage, index_show_argv, opt_cdup))
5845 return status_load_error(view, stage, newpath);
5848 if (status)
5849 info = "Staged changes to %s";
5850 else
5851 info = "Staged changes";
5852 break;
5854 case LINE_STAT_UNSTAGED:
5856 const char *files_show_argv[] = {
5857 "git", "diff-files", "--root", "--patch-with-stat",
5858 "-C", "-M", "--", oldpath, newpath, NULL
5861 if (!prepare_update(stage, files_show_argv, opt_cdup))
5862 return status_load_error(view, stage, newpath);
5863 if (status)
5864 info = "Unstaged changes to %s";
5865 else
5866 info = "Unstaged changes";
5867 break;
5869 case LINE_STAT_UNTRACKED:
5870 if (!newpath) {
5871 report("No file to show");
5872 return REQ_NONE;
5875 if (!suffixcmp(status->new.name, -1, "/")) {
5876 report("Cannot display a directory");
5877 return REQ_NONE;
5880 if (!prepare_update_file(stage, newpath))
5881 return status_load_error(view, stage, newpath);
5882 info = "Untracked file %s";
5883 break;
5885 case LINE_STAT_HEAD:
5886 return REQ_NONE;
5888 default:
5889 die("line type %d not handled in switch", line->type);
5892 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5893 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5894 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5895 if (status) {
5896 stage_status = *status;
5897 } else {
5898 memset(&stage_status, 0, sizeof(stage_status));
5901 stage_line_type = line->type;
5902 stage_chunks = 0;
5903 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5906 return REQ_NONE;
5909 static bool
5910 status_exists(struct status *status, enum line_type type)
5912 struct view *view = VIEW(REQ_VIEW_STATUS);
5913 unsigned long lineno;
5915 for (lineno = 0; lineno < view->lines; lineno++) {
5916 struct line *line = &view->line[lineno];
5917 struct status *pos = line->data;
5919 if (line->type != type)
5920 continue;
5921 if (!pos && (!status || !status->status) && line[1].data) {
5922 select_view_line(view, lineno);
5923 return TRUE;
5925 if (pos && !strcmp(status->new.name, pos->new.name)) {
5926 select_view_line(view, lineno);
5927 return TRUE;
5931 return FALSE;
5935 static bool
5936 status_update_prepare(struct io *io, enum line_type type)
5938 const char *staged_argv[] = {
5939 "git", "update-index", "-z", "--index-info", NULL
5941 const char *others_argv[] = {
5942 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5945 switch (type) {
5946 case LINE_STAT_STAGED:
5947 return io_run(io, staged_argv, opt_cdup, IO_WR);
5949 case LINE_STAT_UNSTAGED:
5950 case LINE_STAT_UNTRACKED:
5951 return io_run(io, others_argv, opt_cdup, IO_WR);
5953 default:
5954 die("line type %d not handled in switch", type);
5955 return FALSE;
5959 static bool
5960 status_update_write(struct io *io, struct status *status, enum line_type type)
5962 char buf[SIZEOF_STR];
5963 size_t bufsize = 0;
5965 switch (type) {
5966 case LINE_STAT_STAGED:
5967 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5968 status->old.mode,
5969 status->old.rev,
5970 status->old.name, 0))
5971 return FALSE;
5972 break;
5974 case LINE_STAT_UNSTAGED:
5975 case LINE_STAT_UNTRACKED:
5976 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5977 return FALSE;
5978 break;
5980 default:
5981 die("line type %d not handled in switch", type);
5984 return io_write(io, buf, bufsize);
5987 static bool
5988 status_update_file(struct status *status, enum line_type type)
5990 struct io io = {};
5991 bool result;
5993 if (!status_update_prepare(&io, type))
5994 return FALSE;
5996 result = status_update_write(&io, status, type);
5997 return io_done(&io) && result;
6000 static bool
6001 status_update_files(struct view *view, struct line *line)
6003 char buf[sizeof(view->ref)];
6004 struct io io = {};
6005 bool result = TRUE;
6006 struct line *pos = view->line + view->lines;
6007 int files = 0;
6008 int file, done;
6009 int cursor_y = -1, cursor_x = -1;
6011 if (!status_update_prepare(&io, line->type))
6012 return FALSE;
6014 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6015 files++;
6017 string_copy(buf, view->ref);
6018 getsyx(cursor_y, cursor_x);
6019 for (file = 0, done = 5; result && file < files; line++, file++) {
6020 int almost_done = file * 100 / files;
6022 if (almost_done > done) {
6023 done = almost_done;
6024 string_format(view->ref, "updating file %u of %u (%d%% done)",
6025 file, files, done);
6026 update_view_title(view);
6027 setsyx(cursor_y, cursor_x);
6028 doupdate();
6030 result = status_update_write(&io, line->data, line->type);
6032 string_copy(view->ref, buf);
6034 return io_done(&io) && result;
6037 static bool
6038 status_update(struct view *view)
6040 struct line *line = &view->line[view->lineno];
6042 assert(view->lines);
6044 if (!line->data) {
6045 /* This should work even for the "On branch" line. */
6046 if (line < view->line + view->lines && !line[1].data) {
6047 report("Nothing to update");
6048 return FALSE;
6051 if (!status_update_files(view, line + 1)) {
6052 report("Failed to update file status");
6053 return FALSE;
6056 } else if (!status_update_file(line->data, line->type)) {
6057 report("Failed to update file status");
6058 return FALSE;
6061 return TRUE;
6064 static bool
6065 status_revert(struct status *status, enum line_type type, bool has_none)
6067 if (!status || type != LINE_STAT_UNSTAGED) {
6068 if (type == LINE_STAT_STAGED) {
6069 report("Cannot revert changes to staged files");
6070 } else if (type == LINE_STAT_UNTRACKED) {
6071 report("Cannot revert changes to untracked files");
6072 } else if (has_none) {
6073 report("Nothing to revert");
6074 } else {
6075 report("Cannot revert changes to multiple files");
6078 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6079 char mode[10] = "100644";
6080 const char *reset_argv[] = {
6081 "git", "update-index", "--cacheinfo", mode,
6082 status->old.rev, status->old.name, NULL
6084 const char *checkout_argv[] = {
6085 "git", "checkout", "--", status->old.name, NULL
6088 if (status->status == 'U') {
6089 string_format(mode, "%5o", status->old.mode);
6091 if (status->old.mode == 0 && status->new.mode == 0) {
6092 reset_argv[2] = "--force-remove";
6093 reset_argv[3] = status->old.name;
6094 reset_argv[4] = NULL;
6097 if (!io_run_fg(reset_argv, opt_cdup))
6098 return FALSE;
6099 if (status->old.mode == 0 && status->new.mode == 0)
6100 return TRUE;
6103 return io_run_fg(checkout_argv, opt_cdup);
6106 return FALSE;
6109 static enum request
6110 status_request(struct view *view, enum request request, struct line *line)
6112 struct status *status = line->data;
6114 switch (request) {
6115 case REQ_STATUS_UPDATE:
6116 if (!status_update(view))
6117 return REQ_NONE;
6118 break;
6120 case REQ_STATUS_REVERT:
6121 if (!status_revert(status, line->type, status_has_none(view, line)))
6122 return REQ_NONE;
6123 break;
6125 case REQ_STATUS_MERGE:
6126 if (!status || status->status != 'U') {
6127 report("Merging only possible for files with unmerged status ('U').");
6128 return REQ_NONE;
6130 open_mergetool(status->new.name);
6131 break;
6133 case REQ_EDIT:
6134 if (!status)
6135 return request;
6136 if (status->status == 'D') {
6137 report("File has been deleted.");
6138 return REQ_NONE;
6141 open_editor(status->new.name);
6142 break;
6144 case REQ_VIEW_BLAME:
6145 if (status)
6146 opt_ref[0] = 0;
6147 return request;
6149 case REQ_ENTER:
6150 /* After returning the status view has been split to
6151 * show the stage view. No further reloading is
6152 * necessary. */
6153 return status_enter(view, line);
6155 case REQ_REFRESH:
6156 /* Simply reload the view. */
6157 break;
6159 default:
6160 return request;
6163 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6165 return REQ_NONE;
6168 static void
6169 status_select(struct view *view, struct line *line)
6171 struct status *status = line->data;
6172 char file[SIZEOF_STR] = "all files";
6173 const char *text;
6174 const char *key;
6176 if (status && !string_format(file, "'%s'", status->new.name))
6177 return;
6179 if (!status && line[1].type == LINE_STAT_NONE)
6180 line++;
6182 switch (line->type) {
6183 case LINE_STAT_STAGED:
6184 text = "Press %s to unstage %s for commit";
6185 break;
6187 case LINE_STAT_UNSTAGED:
6188 text = "Press %s to stage %s for commit";
6189 break;
6191 case LINE_STAT_UNTRACKED:
6192 text = "Press %s to stage %s for addition";
6193 break;
6195 case LINE_STAT_HEAD:
6196 case LINE_STAT_NONE:
6197 text = "Nothing to update";
6198 break;
6200 default:
6201 die("line type %d not handled in switch", line->type);
6204 if (status && status->status == 'U') {
6205 text = "Press %s to resolve conflict in %s";
6206 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6208 } else {
6209 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6212 string_format(view->ref, text, key, file);
6213 if (status)
6214 string_copy(opt_file, status->new.name);
6217 static bool
6218 status_grep(struct view *view, struct line *line)
6220 struct status *status = line->data;
6222 if (status) {
6223 const char buf[2] = { status->status, 0 };
6224 const char *text[] = { status->new.name, buf, NULL };
6226 return grep_text(view, text);
6229 return FALSE;
6232 static struct view_ops status_ops = {
6233 "file",
6234 NULL,
6235 status_open,
6236 NULL,
6237 status_draw,
6238 status_request,
6239 status_grep,
6240 status_select,
6244 static bool
6245 stage_diff_write(struct io *io, struct line *line, struct line *end)
6247 while (line < end) {
6248 if (!io_write(io, line->data, strlen(line->data)) ||
6249 !io_write(io, "\n", 1))
6250 return FALSE;
6251 line++;
6252 if (line->type == LINE_DIFF_CHUNK ||
6253 line->type == LINE_DIFF_HEADER)
6254 break;
6257 return TRUE;
6260 static struct line *
6261 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6263 for (; view->line < line; line--)
6264 if (line->type == type)
6265 return line;
6267 return NULL;
6270 static bool
6271 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6273 const char *apply_argv[SIZEOF_ARG] = {
6274 "git", "apply", "--whitespace=nowarn", NULL
6276 struct line *diff_hdr;
6277 struct io io = {};
6278 int argc = 3;
6280 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6281 if (!diff_hdr)
6282 return FALSE;
6284 if (!revert)
6285 apply_argv[argc++] = "--cached";
6286 if (revert || stage_line_type == LINE_STAT_STAGED)
6287 apply_argv[argc++] = "-R";
6288 apply_argv[argc++] = "-";
6289 apply_argv[argc++] = NULL;
6290 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6291 return FALSE;
6293 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6294 !stage_diff_write(&io, chunk, view->line + view->lines))
6295 chunk = NULL;
6297 io_done(&io);
6298 io_run_bg(update_index_argv);
6300 return chunk ? TRUE : FALSE;
6303 static bool
6304 stage_update(struct view *view, struct line *line)
6306 struct line *chunk = NULL;
6308 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6309 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6311 if (chunk) {
6312 if (!stage_apply_chunk(view, chunk, FALSE)) {
6313 report("Failed to apply chunk");
6314 return FALSE;
6317 } else if (!stage_status.status) {
6318 view = VIEW(REQ_VIEW_STATUS);
6320 for (line = view->line; line < view->line + view->lines; line++)
6321 if (line->type == stage_line_type)
6322 break;
6324 if (!status_update_files(view, line + 1)) {
6325 report("Failed to update files");
6326 return FALSE;
6329 } else if (!status_update_file(&stage_status, stage_line_type)) {
6330 report("Failed to update file");
6331 return FALSE;
6334 return TRUE;
6337 static bool
6338 stage_revert(struct view *view, struct line *line)
6340 struct line *chunk = NULL;
6342 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6343 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6345 if (chunk) {
6346 if (!prompt_yesno("Are you sure you want to revert changes?"))
6347 return FALSE;
6349 if (!stage_apply_chunk(view, chunk, TRUE)) {
6350 report("Failed to revert chunk");
6351 return FALSE;
6353 return TRUE;
6355 } else {
6356 return status_revert(stage_status.status ? &stage_status : NULL,
6357 stage_line_type, FALSE);
6362 static void
6363 stage_next(struct view *view, struct line *line)
6365 int i;
6367 if (!stage_chunks) {
6368 for (line = view->line; line < view->line + view->lines; line++) {
6369 if (line->type != LINE_DIFF_CHUNK)
6370 continue;
6372 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6373 report("Allocation failure");
6374 return;
6377 stage_chunk[stage_chunks++] = line - view->line;
6381 for (i = 0; i < stage_chunks; i++) {
6382 if (stage_chunk[i] > view->lineno) {
6383 do_scroll_view(view, stage_chunk[i] - view->lineno);
6384 report("Chunk %d of %d", i + 1, stage_chunks);
6385 return;
6389 report("No next chunk found");
6392 static enum request
6393 stage_request(struct view *view, enum request request, struct line *line)
6395 switch (request) {
6396 case REQ_STATUS_UPDATE:
6397 if (!stage_update(view, line))
6398 return REQ_NONE;
6399 break;
6401 case REQ_STATUS_REVERT:
6402 if (!stage_revert(view, line))
6403 return REQ_NONE;
6404 break;
6406 case REQ_STAGE_NEXT:
6407 if (stage_line_type == LINE_STAT_UNTRACKED) {
6408 report("File is untracked; press %s to add",
6409 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6410 return REQ_NONE;
6412 stage_next(view, line);
6413 return REQ_NONE;
6415 case REQ_EDIT:
6416 if (!stage_status.new.name[0])
6417 return request;
6418 if (stage_status.status == 'D') {
6419 report("File has been deleted.");
6420 return REQ_NONE;
6423 open_editor(stage_status.new.name);
6424 break;
6426 case REQ_REFRESH:
6427 /* Reload everything ... */
6428 break;
6430 case REQ_VIEW_BLAME:
6431 if (stage_status.new.name[0]) {
6432 string_copy(opt_file, stage_status.new.name);
6433 opt_ref[0] = 0;
6435 return request;
6437 case REQ_ENTER:
6438 return pager_request(view, request, line);
6440 default:
6441 return request;
6444 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6445 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6447 /* Check whether the staged entry still exists, and close the
6448 * stage view if it doesn't. */
6449 if (!status_exists(&stage_status, stage_line_type)) {
6450 status_restore(VIEW(REQ_VIEW_STATUS));
6451 return REQ_VIEW_CLOSE;
6454 if (stage_line_type == LINE_STAT_UNTRACKED) {
6455 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6456 report("Cannot display a directory");
6457 return REQ_NONE;
6460 if (!prepare_update_file(view, stage_status.new.name)) {
6461 report("Failed to open file: %s", strerror(errno));
6462 return REQ_NONE;
6465 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6467 return REQ_NONE;
6470 static struct view_ops stage_ops = {
6471 "line",
6472 NULL,
6473 NULL,
6474 pager_read,
6475 pager_draw,
6476 stage_request,
6477 pager_grep,
6478 pager_select,
6483 * Revision graph
6486 struct commit {
6487 char id[SIZEOF_REV]; /* SHA1 ID. */
6488 char title[128]; /* First line of the commit message. */
6489 const char *author; /* Author of the commit. */
6490 struct time time; /* Date from the author ident. */
6491 struct ref_list *refs; /* Repository references. */
6492 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6493 size_t graph_size; /* The width of the graph array. */
6494 bool has_parents; /* Rewritten --parents seen. */
6497 /* Size of rev graph with no "padding" columns */
6498 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6500 struct rev_graph {
6501 struct rev_graph *prev, *next, *parents;
6502 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6503 size_t size;
6504 struct commit *commit;
6505 size_t pos;
6506 unsigned int boundary:1;
6509 /* Parents of the commit being visualized. */
6510 static struct rev_graph graph_parents[4];
6512 /* The current stack of revisions on the graph. */
6513 static struct rev_graph graph_stacks[4] = {
6514 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6515 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6516 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6517 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6520 static inline bool
6521 graph_parent_is_merge(struct rev_graph *graph)
6523 return graph->parents->size > 1;
6526 static inline void
6527 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6529 struct commit *commit = graph->commit;
6531 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6532 commit->graph[commit->graph_size++] = symbol;
6535 static void
6536 clear_rev_graph(struct rev_graph *graph)
6538 graph->boundary = 0;
6539 graph->size = graph->pos = 0;
6540 graph->commit = NULL;
6541 memset(graph->parents, 0, sizeof(*graph->parents));
6544 static void
6545 done_rev_graph(struct rev_graph *graph)
6547 if (graph_parent_is_merge(graph) &&
6548 graph->pos < graph->size - 1 &&
6549 graph->next->size == graph->size + graph->parents->size - 1) {
6550 size_t i = graph->pos + graph->parents->size - 1;
6552 graph->commit->graph_size = i * 2;
6553 while (i < graph->next->size - 1) {
6554 append_to_rev_graph(graph, ' ');
6555 append_to_rev_graph(graph, '\\');
6556 i++;
6560 clear_rev_graph(graph);
6563 static void
6564 push_rev_graph(struct rev_graph *graph, const char *parent)
6566 int i;
6568 /* "Collapse" duplicate parents lines.
6570 * FIXME: This needs to also update update the drawn graph but
6571 * for now it just serves as a method for pruning graph lines. */
6572 for (i = 0; i < graph->size; i++)
6573 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6574 return;
6576 if (graph->size < SIZEOF_REVITEMS) {
6577 string_copy_rev(graph->rev[graph->size++], parent);
6581 static chtype
6582 get_rev_graph_symbol(struct rev_graph *graph)
6584 chtype symbol;
6586 if (graph->boundary)
6587 symbol = REVGRAPH_BOUND;
6588 else if (graph->parents->size == 0)
6589 symbol = REVGRAPH_INIT;
6590 else if (graph_parent_is_merge(graph))
6591 symbol = REVGRAPH_MERGE;
6592 else if (graph->pos >= graph->size)
6593 symbol = REVGRAPH_BRANCH;
6594 else
6595 symbol = REVGRAPH_COMMIT;
6597 return symbol;
6600 static void
6601 draw_rev_graph(struct rev_graph *graph)
6603 struct rev_filler {
6604 chtype separator, line;
6606 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6607 static struct rev_filler fillers[] = {
6608 { ' ', '|' },
6609 { '`', '.' },
6610 { '\'', ' ' },
6611 { '/', ' ' },
6613 chtype symbol = get_rev_graph_symbol(graph);
6614 struct rev_filler *filler;
6615 size_t i;
6617 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6618 filler = &fillers[DEFAULT];
6620 for (i = 0; i < graph->pos; i++) {
6621 append_to_rev_graph(graph, filler->line);
6622 if (graph_parent_is_merge(graph->prev) &&
6623 graph->prev->pos == i)
6624 filler = &fillers[RSHARP];
6626 append_to_rev_graph(graph, filler->separator);
6629 /* Place the symbol for this revision. */
6630 append_to_rev_graph(graph, symbol);
6632 if (graph->prev->size > graph->size)
6633 filler = &fillers[RDIAG];
6634 else
6635 filler = &fillers[DEFAULT];
6637 i++;
6639 for (; i < graph->size; i++) {
6640 append_to_rev_graph(graph, filler->separator);
6641 append_to_rev_graph(graph, filler->line);
6642 if (graph_parent_is_merge(graph->prev) &&
6643 i < graph->prev->pos + graph->parents->size)
6644 filler = &fillers[RSHARP];
6645 if (graph->prev->size > graph->size)
6646 filler = &fillers[LDIAG];
6649 if (graph->prev->size > graph->size) {
6650 append_to_rev_graph(graph, filler->separator);
6651 if (filler->line != ' ')
6652 append_to_rev_graph(graph, filler->line);
6656 /* Prepare the next rev graph */
6657 static void
6658 prepare_rev_graph(struct rev_graph *graph)
6660 size_t i;
6662 /* First, traverse all lines of revisions up to the active one. */
6663 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6664 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6665 break;
6667 push_rev_graph(graph->next, graph->rev[graph->pos]);
6670 /* Interleave the new revision parent(s). */
6671 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6672 push_rev_graph(graph->next, graph->parents->rev[i]);
6674 /* Lastly, put any remaining revisions. */
6675 for (i = graph->pos + 1; i < graph->size; i++)
6676 push_rev_graph(graph->next, graph->rev[i]);
6679 static void
6680 update_rev_graph(struct view *view, struct rev_graph *graph)
6682 /* If this is the finalizing update ... */
6683 if (graph->commit)
6684 prepare_rev_graph(graph);
6686 /* Graph visualization needs a one rev look-ahead,
6687 * so the first update doesn't visualize anything. */
6688 if (!graph->prev->commit)
6689 return;
6691 if (view->lines > 2)
6692 view->line[view->lines - 3].dirty = 1;
6693 if (view->lines > 1)
6694 view->line[view->lines - 2].dirty = 1;
6695 draw_rev_graph(graph->prev);
6696 done_rev_graph(graph->prev->prev);
6701 * Main view backend
6704 static const char *main_argv[SIZEOF_ARG] = {
6705 "git", "log", "--no-color", "--pretty=raw", "--parents",
6706 "--topo-order", "%(head)", NULL
6709 static bool
6710 main_draw(struct view *view, struct line *line, unsigned int lineno)
6712 struct commit *commit = line->data;
6714 if (!commit->author)
6715 return FALSE;
6717 if (opt_date && draw_date(view, &commit->time))
6718 return TRUE;
6720 if (opt_author && draw_author(view, commit->author))
6721 return TRUE;
6723 if (opt_rev_graph && commit->graph_size &&
6724 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6725 return TRUE;
6727 if (opt_show_refs && commit->refs) {
6728 size_t i;
6730 for (i = 0; i < commit->refs->size; i++) {
6731 struct ref *ref = commit->refs->refs[i];
6732 enum line_type type;
6734 if (ref->head)
6735 type = LINE_MAIN_HEAD;
6736 else if (ref->ltag)
6737 type = LINE_MAIN_LOCAL_TAG;
6738 else if (ref->tag)
6739 type = LINE_MAIN_TAG;
6740 else if (ref->tracked)
6741 type = LINE_MAIN_TRACKED;
6742 else if (ref->remote)
6743 type = LINE_MAIN_REMOTE;
6744 else
6745 type = LINE_MAIN_REF;
6747 if (draw_text(view, type, "[", TRUE) ||
6748 draw_text(view, type, ref->name, TRUE) ||
6749 draw_text(view, type, "]", TRUE))
6750 return TRUE;
6752 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6753 return TRUE;
6757 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6758 return TRUE;
6761 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6762 static bool
6763 main_read(struct view *view, char *line)
6765 static struct rev_graph *graph = graph_stacks;
6766 enum line_type type;
6767 struct commit *commit;
6769 if (!line) {
6770 int i;
6772 if (!view->lines && !view->parent)
6773 die("No revisions match the given arguments.");
6774 if (view->lines > 0) {
6775 commit = view->line[view->lines - 1].data;
6776 view->line[view->lines - 1].dirty = 1;
6777 if (!commit->author) {
6778 view->lines--;
6779 free(commit);
6780 graph->commit = NULL;
6783 update_rev_graph(view, graph);
6785 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6786 clear_rev_graph(&graph_stacks[i]);
6787 return TRUE;
6790 type = get_line_type(line);
6791 if (type == LINE_COMMIT) {
6792 commit = calloc(1, sizeof(struct commit));
6793 if (!commit)
6794 return FALSE;
6796 line += STRING_SIZE("commit ");
6797 if (*line == '-') {
6798 graph->boundary = 1;
6799 line++;
6802 string_copy_rev(commit->id, line);
6803 commit->refs = get_ref_list(commit->id);
6804 graph->commit = commit;
6805 add_line_data(view, commit, LINE_MAIN_COMMIT);
6807 while ((line = strchr(line, ' '))) {
6808 line++;
6809 push_rev_graph(graph->parents, line);
6810 commit->has_parents = TRUE;
6812 return TRUE;
6815 if (!view->lines)
6816 return TRUE;
6817 commit = view->line[view->lines - 1].data;
6819 switch (type) {
6820 case LINE_PARENT:
6821 if (commit->has_parents)
6822 break;
6823 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6824 break;
6826 case LINE_AUTHOR:
6827 parse_author_line(line + STRING_SIZE("author "),
6828 &commit->author, &commit->time);
6829 update_rev_graph(view, graph);
6830 graph = graph->next;
6831 break;
6833 default:
6834 /* Fill in the commit title if it has not already been set. */
6835 if (commit->title[0])
6836 break;
6838 /* Require titles to start with a non-space character at the
6839 * offset used by git log. */
6840 if (strncmp(line, " ", 4))
6841 break;
6842 line += 4;
6843 /* Well, if the title starts with a whitespace character,
6844 * try to be forgiving. Otherwise we end up with no title. */
6845 while (isspace(*line))
6846 line++;
6847 if (*line == '\0')
6848 break;
6849 /* FIXME: More graceful handling of titles; append "..." to
6850 * shortened titles, etc. */
6852 string_expand(commit->title, sizeof(commit->title), line, 1);
6853 view->line[view->lines - 1].dirty = 1;
6856 return TRUE;
6859 static enum request
6860 main_request(struct view *view, enum request request, struct line *line)
6862 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6864 switch (request) {
6865 case REQ_ENTER:
6866 open_view(view, REQ_VIEW_DIFF, flags);
6867 break;
6868 case REQ_REFRESH:
6869 load_refs();
6870 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6871 break;
6872 default:
6873 return request;
6876 return REQ_NONE;
6879 static bool
6880 grep_refs(struct ref_list *list, regex_t *regex)
6882 regmatch_t pmatch;
6883 size_t i;
6885 if (!opt_show_refs || !list)
6886 return FALSE;
6888 for (i = 0; i < list->size; i++) {
6889 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6890 return TRUE;
6893 return FALSE;
6896 static bool
6897 main_grep(struct view *view, struct line *line)
6899 struct commit *commit = line->data;
6900 const char *text[] = {
6901 commit->title,
6902 opt_author ? commit->author : "",
6903 mkdate(&commit->time, opt_date),
6904 NULL
6907 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6910 static void
6911 main_select(struct view *view, struct line *line)
6913 struct commit *commit = line->data;
6915 string_copy_rev(view->ref, commit->id);
6916 string_copy_rev(ref_commit, view->ref);
6919 static struct view_ops main_ops = {
6920 "commit",
6921 main_argv,
6922 NULL,
6923 main_read,
6924 main_draw,
6925 main_request,
6926 main_grep,
6927 main_select,
6932 * Status management
6935 /* Whether or not the curses interface has been initialized. */
6936 static bool cursed = FALSE;
6938 /* Terminal hacks and workarounds. */
6939 static bool use_scroll_redrawwin;
6940 static bool use_scroll_status_wclear;
6942 /* The status window is used for polling keystrokes. */
6943 static WINDOW *status_win;
6945 /* Reading from the prompt? */
6946 static bool input_mode = FALSE;
6948 static bool status_empty = FALSE;
6950 /* Update status and title window. */
6951 static void
6952 report(const char *msg, ...)
6954 struct view *view = display[current_view];
6956 if (input_mode)
6957 return;
6959 if (!view) {
6960 char buf[SIZEOF_STR];
6961 va_list args;
6963 va_start(args, msg);
6964 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6965 buf[sizeof(buf) - 1] = 0;
6966 buf[sizeof(buf) - 2] = '.';
6967 buf[sizeof(buf) - 3] = '.';
6968 buf[sizeof(buf) - 4] = '.';
6970 va_end(args);
6971 die("%s", buf);
6974 if (!status_empty || *msg) {
6975 va_list args;
6977 va_start(args, msg);
6979 wmove(status_win, 0, 0);
6980 if (view->has_scrolled && use_scroll_status_wclear)
6981 wclear(status_win);
6982 if (*msg) {
6983 vwprintw(status_win, msg, args);
6984 status_empty = FALSE;
6985 } else {
6986 status_empty = TRUE;
6988 wclrtoeol(status_win);
6989 wnoutrefresh(status_win);
6991 va_end(args);
6994 update_view_title(view);
6997 static void
6998 init_display(void)
7000 const char *term;
7001 int x, y;
7003 /* Initialize the curses library */
7004 if (isatty(STDIN_FILENO)) {
7005 cursed = !!initscr();
7006 opt_tty = stdin;
7007 } else {
7008 /* Leave stdin and stdout alone when acting as a pager. */
7009 opt_tty = fopen("/dev/tty", "r+");
7010 if (!opt_tty)
7011 die("Failed to open /dev/tty");
7012 cursed = !!newterm(NULL, opt_tty, opt_tty);
7015 if (!cursed)
7016 die("Failed to initialize curses");
7018 nonl(); /* Disable conversion and detect newlines from input. */
7019 cbreak(); /* Take input chars one at a time, no wait for \n */
7020 noecho(); /* Don't echo input */
7021 leaveok(stdscr, FALSE);
7023 if (has_colors())
7024 init_colors();
7026 getmaxyx(stdscr, y, x);
7027 status_win = newwin(1, 0, y - 1, 0);
7028 if (!status_win)
7029 die("Failed to create status window");
7031 /* Enable keyboard mapping */
7032 keypad(status_win, TRUE);
7033 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7035 TABSIZE = opt_tab_size;
7037 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7038 if (term && !strcmp(term, "gnome-terminal")) {
7039 /* In the gnome-terminal-emulator, the message from
7040 * scrolling up one line when impossible followed by
7041 * scrolling down one line causes corruption of the
7042 * status line. This is fixed by calling wclear. */
7043 use_scroll_status_wclear = TRUE;
7044 use_scroll_redrawwin = FALSE;
7046 } else if (term && !strcmp(term, "xrvt-xpm")) {
7047 /* No problems with full optimizations in xrvt-(unicode)
7048 * and aterm. */
7049 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7051 } else {
7052 /* When scrolling in (u)xterm the last line in the
7053 * scrolling direction will update slowly. */
7054 use_scroll_redrawwin = TRUE;
7055 use_scroll_status_wclear = FALSE;
7059 static int
7060 get_input(int prompt_position)
7062 struct view *view;
7063 int i, key, cursor_y, cursor_x;
7064 bool loading = FALSE;
7066 if (prompt_position)
7067 input_mode = TRUE;
7069 while (TRUE) {
7070 foreach_view (view, i) {
7071 update_view(view);
7072 if (view_is_displayed(view) && view->has_scrolled &&
7073 use_scroll_redrawwin)
7074 redrawwin(view->win);
7075 view->has_scrolled = FALSE;
7076 if (view->pipe)
7077 loading = TRUE;
7080 /* Update the cursor position. */
7081 if (prompt_position) {
7082 getbegyx(status_win, cursor_y, cursor_x);
7083 cursor_x = prompt_position;
7084 } else {
7085 view = display[current_view];
7086 getbegyx(view->win, cursor_y, cursor_x);
7087 cursor_x = view->width - 1;
7088 cursor_y += view->lineno - view->offset;
7090 setsyx(cursor_y, cursor_x);
7092 /* Refresh, accept single keystroke of input */
7093 doupdate();
7094 nodelay(status_win, loading);
7095 key = wgetch(status_win);
7097 /* wgetch() with nodelay() enabled returns ERR when
7098 * there's no input. */
7099 if (key == ERR) {
7101 } else if (key == KEY_RESIZE) {
7102 int height, width;
7104 getmaxyx(stdscr, height, width);
7106 wresize(status_win, 1, width);
7107 mvwin(status_win, height - 1, 0);
7108 wnoutrefresh(status_win);
7109 resize_display();
7110 redraw_display(TRUE);
7112 } else {
7113 input_mode = FALSE;
7114 return key;
7119 static char *
7120 prompt_input(const char *prompt, input_handler handler, void *data)
7122 enum input_status status = INPUT_OK;
7123 static char buf[SIZEOF_STR];
7124 size_t pos = 0;
7126 buf[pos] = 0;
7128 while (status == INPUT_OK || status == INPUT_SKIP) {
7129 int key;
7131 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7132 wclrtoeol(status_win);
7134 key = get_input(pos + 1);
7135 switch (key) {
7136 case KEY_RETURN:
7137 case KEY_ENTER:
7138 case '\n':
7139 status = pos ? INPUT_STOP : INPUT_CANCEL;
7140 break;
7142 case KEY_BACKSPACE:
7143 if (pos > 0)
7144 buf[--pos] = 0;
7145 else
7146 status = INPUT_CANCEL;
7147 break;
7149 case KEY_ESC:
7150 status = INPUT_CANCEL;
7151 break;
7153 default:
7154 if (pos >= sizeof(buf)) {
7155 report("Input string too long");
7156 return NULL;
7159 status = handler(data, buf, key);
7160 if (status == INPUT_OK)
7161 buf[pos++] = (char) key;
7165 /* Clear the status window */
7166 status_empty = FALSE;
7167 report("");
7169 if (status == INPUT_CANCEL)
7170 return NULL;
7172 buf[pos++] = 0;
7174 return buf;
7177 static enum input_status
7178 prompt_yesno_handler(void *data, char *buf, int c)
7180 if (c == 'y' || c == 'Y')
7181 return INPUT_STOP;
7182 if (c == 'n' || c == 'N')
7183 return INPUT_CANCEL;
7184 return INPUT_SKIP;
7187 static bool
7188 prompt_yesno(const char *prompt)
7190 char prompt2[SIZEOF_STR];
7192 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7193 return FALSE;
7195 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7198 static enum input_status
7199 read_prompt_handler(void *data, char *buf, int c)
7201 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7204 static char *
7205 read_prompt(const char *prompt)
7207 return prompt_input(prompt, read_prompt_handler, NULL);
7210 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7212 enum input_status status = INPUT_OK;
7213 int size = 0;
7215 while (items[size].text)
7216 size++;
7218 while (status == INPUT_OK) {
7219 const struct menu_item *item = &items[*selected];
7220 int key;
7221 int i;
7223 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7224 prompt, *selected + 1, size);
7225 if (item->hotkey)
7226 wprintw(status_win, "[%c] ", (char) item->hotkey);
7227 wprintw(status_win, "%s", item->text);
7228 wclrtoeol(status_win);
7230 key = get_input(COLS - 1);
7231 switch (key) {
7232 case KEY_RETURN:
7233 case KEY_ENTER:
7234 case '\n':
7235 status = INPUT_STOP;
7236 break;
7238 case KEY_LEFT:
7239 case KEY_UP:
7240 *selected = *selected - 1;
7241 if (*selected < 0)
7242 *selected = size - 1;
7243 break;
7245 case KEY_RIGHT:
7246 case KEY_DOWN:
7247 *selected = (*selected + 1) % size;
7248 break;
7250 case KEY_ESC:
7251 status = INPUT_CANCEL;
7252 break;
7254 default:
7255 for (i = 0; items[i].text; i++)
7256 if (items[i].hotkey == key) {
7257 *selected = i;
7258 status = INPUT_STOP;
7259 break;
7264 /* Clear the status window */
7265 status_empty = FALSE;
7266 report("");
7268 return status != INPUT_CANCEL;
7272 * Repository properties
7275 static struct ref **refs = NULL;
7276 static size_t refs_size = 0;
7277 static struct ref *refs_head = NULL;
7279 static struct ref_list **ref_lists = NULL;
7280 static size_t ref_lists_size = 0;
7282 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7283 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7284 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7286 static int
7287 compare_refs(const void *ref1_, const void *ref2_)
7289 const struct ref *ref1 = *(const struct ref **)ref1_;
7290 const struct ref *ref2 = *(const struct ref **)ref2_;
7292 if (ref1->tag != ref2->tag)
7293 return ref2->tag - ref1->tag;
7294 if (ref1->ltag != ref2->ltag)
7295 return ref2->ltag - ref2->ltag;
7296 if (ref1->head != ref2->head)
7297 return ref2->head - ref1->head;
7298 if (ref1->tracked != ref2->tracked)
7299 return ref2->tracked - ref1->tracked;
7300 if (ref1->remote != ref2->remote)
7301 return ref2->remote - ref1->remote;
7302 return strcmp(ref1->name, ref2->name);
7305 static void
7306 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7308 size_t i;
7310 for (i = 0; i < refs_size; i++)
7311 if (!visitor(data, refs[i]))
7312 break;
7315 static struct ref *
7316 get_ref_head()
7318 return refs_head;
7321 static struct ref_list *
7322 get_ref_list(const char *id)
7324 struct ref_list *list;
7325 size_t i;
7327 for (i = 0; i < ref_lists_size; i++)
7328 if (!strcmp(id, ref_lists[i]->id))
7329 return ref_lists[i];
7331 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7332 return NULL;
7333 list = calloc(1, sizeof(*list));
7334 if (!list)
7335 return NULL;
7337 for (i = 0; i < refs_size; i++) {
7338 if (!strcmp(id, refs[i]->id) &&
7339 realloc_refs_list(&list->refs, list->size, 1))
7340 list->refs[list->size++] = refs[i];
7343 if (!list->refs) {
7344 free(list);
7345 return NULL;
7348 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7349 ref_lists[ref_lists_size++] = list;
7350 return list;
7353 static int
7354 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7356 struct ref *ref = NULL;
7357 bool tag = FALSE;
7358 bool ltag = FALSE;
7359 bool remote = FALSE;
7360 bool tracked = FALSE;
7361 bool head = FALSE;
7362 int from = 0, to = refs_size - 1;
7364 if (!prefixcmp(name, "refs/tags/")) {
7365 if (!suffixcmp(name, namelen, "^{}")) {
7366 namelen -= 3;
7367 name[namelen] = 0;
7368 } else {
7369 ltag = TRUE;
7372 tag = TRUE;
7373 namelen -= STRING_SIZE("refs/tags/");
7374 name += STRING_SIZE("refs/tags/");
7376 } else if (!prefixcmp(name, "refs/remotes/")) {
7377 remote = TRUE;
7378 namelen -= STRING_SIZE("refs/remotes/");
7379 name += STRING_SIZE("refs/remotes/");
7380 tracked = !strcmp(opt_remote, name);
7382 } else if (!prefixcmp(name, "refs/heads/")) {
7383 namelen -= STRING_SIZE("refs/heads/");
7384 name += STRING_SIZE("refs/heads/");
7385 if (!strncmp(opt_head, name, namelen))
7386 return OK;
7388 } else if (!strcmp(name, "HEAD")) {
7389 head = TRUE;
7390 if (*opt_head) {
7391 namelen = strlen(opt_head);
7392 name = opt_head;
7396 /* If we are reloading or it's an annotated tag, replace the
7397 * previous SHA1 with the resolved commit id; relies on the fact
7398 * git-ls-remote lists the commit id of an annotated tag right
7399 * before the commit id it points to. */
7400 while (from <= to) {
7401 size_t pos = (to + from) / 2;
7402 int cmp = strcmp(name, refs[pos]->name);
7404 if (!cmp) {
7405 ref = refs[pos];
7406 break;
7409 if (cmp < 0)
7410 to = pos - 1;
7411 else
7412 from = pos + 1;
7415 if (!ref) {
7416 if (!realloc_refs(&refs, refs_size, 1))
7417 return ERR;
7418 ref = calloc(1, sizeof(*ref) + namelen);
7419 if (!ref)
7420 return ERR;
7421 memmove(refs + from + 1, refs + from,
7422 (refs_size - from) * sizeof(*refs));
7423 refs[from] = ref;
7424 strncpy(ref->name, name, namelen);
7425 refs_size++;
7428 ref->head = head;
7429 ref->tag = tag;
7430 ref->ltag = ltag;
7431 ref->remote = remote;
7432 ref->tracked = tracked;
7433 string_copy_rev(ref->id, id);
7435 if (head)
7436 refs_head = ref;
7437 return OK;
7440 static int
7441 load_refs(void)
7443 const char *head_argv[] = {
7444 "git", "symbolic-ref", "HEAD", NULL
7446 static const char *ls_remote_argv[SIZEOF_ARG] = {
7447 "git", "ls-remote", opt_git_dir, NULL
7449 static bool init = FALSE;
7450 size_t i;
7452 if (!init) {
7453 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7454 die("TIG_LS_REMOTE contains too many arguments");
7455 init = TRUE;
7458 if (!*opt_git_dir)
7459 return OK;
7461 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7462 !prefixcmp(opt_head, "refs/heads/")) {
7463 char *offset = opt_head + STRING_SIZE("refs/heads/");
7465 memmove(opt_head, offset, strlen(offset) + 1);
7468 refs_head = NULL;
7469 for (i = 0; i < refs_size; i++)
7470 refs[i]->id[0] = 0;
7472 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7473 return ERR;
7475 /* Update the ref lists to reflect changes. */
7476 for (i = 0; i < ref_lists_size; i++) {
7477 struct ref_list *list = ref_lists[i];
7478 size_t old, new;
7480 for (old = new = 0; old < list->size; old++)
7481 if (!strcmp(list->id, list->refs[old]->id))
7482 list->refs[new++] = list->refs[old];
7483 list->size = new;
7486 return OK;
7489 static void
7490 set_remote_branch(const char *name, const char *value, size_t valuelen)
7492 if (!strcmp(name, ".remote")) {
7493 string_ncopy(opt_remote, value, valuelen);
7495 } else if (*opt_remote && !strcmp(name, ".merge")) {
7496 size_t from = strlen(opt_remote);
7498 if (!prefixcmp(value, "refs/heads/"))
7499 value += STRING_SIZE("refs/heads/");
7501 if (!string_format_from(opt_remote, &from, "/%s", value))
7502 opt_remote[0] = 0;
7506 static void
7507 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7509 const char *argv[SIZEOF_ARG] = { name, "=" };
7510 int argc = 1 + (cmd == option_set_command);
7511 int error = ERR;
7513 if (!argv_from_string(argv, &argc, value))
7514 config_msg = "Too many option arguments";
7515 else
7516 error = cmd(argc, argv);
7518 if (error == ERR)
7519 warn("Option 'tig.%s': %s", name, config_msg);
7522 static bool
7523 set_environment_variable(const char *name, const char *value)
7525 size_t len = strlen(name) + 1 + strlen(value) + 1;
7526 char *env = malloc(len);
7528 if (env &&
7529 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7530 putenv(env) == 0)
7531 return TRUE;
7532 free(env);
7533 return FALSE;
7536 static void
7537 set_work_tree(const char *value)
7539 char cwd[SIZEOF_STR];
7541 if (!getcwd(cwd, sizeof(cwd)))
7542 die("Failed to get cwd path: %s", strerror(errno));
7543 if (chdir(opt_git_dir) < 0)
7544 die("Failed to chdir(%s): %s", strerror(errno));
7545 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7546 die("Failed to get git path: %s", strerror(errno));
7547 if (chdir(cwd) < 0)
7548 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7549 if (chdir(value) < 0)
7550 die("Failed to chdir(%s): %s", value, strerror(errno));
7551 if (!getcwd(cwd, sizeof(cwd)))
7552 die("Failed to get cwd path: %s", strerror(errno));
7553 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7554 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7555 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7556 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7557 opt_is_inside_work_tree = TRUE;
7560 static int
7561 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7563 if (!strcmp(name, "i18n.commitencoding"))
7564 string_ncopy(opt_encoding, value, valuelen);
7566 else if (!strcmp(name, "core.editor"))
7567 string_ncopy(opt_editor, value, valuelen);
7569 else if (!strcmp(name, "core.worktree"))
7570 set_work_tree(value);
7572 else if (!prefixcmp(name, "tig.color."))
7573 set_repo_config_option(name + 10, value, option_color_command);
7575 else if (!prefixcmp(name, "tig.bind."))
7576 set_repo_config_option(name + 9, value, option_bind_command);
7578 else if (!prefixcmp(name, "tig."))
7579 set_repo_config_option(name + 4, value, option_set_command);
7581 else if (*opt_head && !prefixcmp(name, "branch.") &&
7582 !strncmp(name + 7, opt_head, strlen(opt_head)))
7583 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7585 return OK;
7588 static int
7589 load_git_config(void)
7591 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7593 return io_run_load(config_list_argv, "=", read_repo_config_option);
7596 static int
7597 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7599 if (!opt_git_dir[0]) {
7600 string_ncopy(opt_git_dir, name, namelen);
7602 } else if (opt_is_inside_work_tree == -1) {
7603 /* This can be 3 different values depending on the
7604 * version of git being used. If git-rev-parse does not
7605 * understand --is-inside-work-tree it will simply echo
7606 * the option else either "true" or "false" is printed.
7607 * Default to true for the unknown case. */
7608 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7610 } else if (*name == '.') {
7611 string_ncopy(opt_cdup, name, namelen);
7613 } else {
7614 string_ncopy(opt_prefix, name, namelen);
7617 return OK;
7620 static int
7621 load_repo_info(void)
7623 const char *rev_parse_argv[] = {
7624 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7625 "--show-cdup", "--show-prefix", NULL
7628 return io_run_load(rev_parse_argv, "=", read_repo_info);
7633 * Main
7636 static const char usage[] =
7637 "tig " TIG_VERSION " (" __DATE__ ")\n"
7638 "\n"
7639 "Usage: tig [options] [revs] [--] [paths]\n"
7640 " or: tig show [options] [revs] [--] [paths]\n"
7641 " or: tig blame [rev] path\n"
7642 " or: tig status\n"
7643 " or: tig < [git command output]\n"
7644 "\n"
7645 "Options:\n"
7646 " -v, --version Show version and exit\n"
7647 " -h, --help Show help message and exit";
7649 static void __NORETURN
7650 quit(int sig)
7652 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7653 if (cursed)
7654 endwin();
7655 exit(0);
7658 static void __NORETURN
7659 die(const char *err, ...)
7661 va_list args;
7663 endwin();
7665 va_start(args, err);
7666 fputs("tig: ", stderr);
7667 vfprintf(stderr, err, args);
7668 fputs("\n", stderr);
7669 va_end(args);
7671 exit(1);
7674 static void
7675 warn(const char *msg, ...)
7677 va_list args;
7679 va_start(args, msg);
7680 fputs("tig warning: ", stderr);
7681 vfprintf(stderr, msg, args);
7682 fputs("\n", stderr);
7683 va_end(args);
7686 static enum request
7687 parse_options(int argc, const char *argv[])
7689 enum request request = REQ_VIEW_MAIN;
7690 const char *subcommand;
7691 bool seen_dashdash = FALSE;
7692 /* XXX: This is vulnerable to the user overriding options
7693 * required for the main view parser. */
7694 const char *custom_argv[SIZEOF_ARG] = {
7695 "git", "log", "--no-color", "--pretty=raw", "--parents",
7696 "--topo-order", NULL
7698 int i, j = 6;
7700 if (!isatty(STDIN_FILENO)) {
7701 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7702 return REQ_VIEW_PAGER;
7705 if (argc <= 1)
7706 return REQ_NONE;
7708 subcommand = argv[1];
7709 if (!strcmp(subcommand, "status")) {
7710 if (argc > 2)
7711 warn("ignoring arguments after `%s'", subcommand);
7712 return REQ_VIEW_STATUS;
7714 } else if (!strcmp(subcommand, "blame")) {
7715 if (argc <= 2 || argc > 4)
7716 die("invalid number of options to blame\n\n%s", usage);
7718 i = 2;
7719 if (argc == 4) {
7720 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7721 i++;
7724 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7725 return REQ_VIEW_BLAME;
7727 } else if (!strcmp(subcommand, "show")) {
7728 request = REQ_VIEW_DIFF;
7730 } else {
7731 subcommand = NULL;
7734 if (subcommand) {
7735 custom_argv[1] = subcommand;
7736 j = 2;
7739 for (i = 1 + !!subcommand; i < argc; i++) {
7740 const char *opt = argv[i];
7742 if (seen_dashdash || !strcmp(opt, "--")) {
7743 seen_dashdash = TRUE;
7745 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7746 printf("tig version %s\n", TIG_VERSION);
7747 quit(0);
7749 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7750 printf("%s\n", usage);
7751 quit(0);
7754 custom_argv[j++] = opt;
7755 if (j >= ARRAY_SIZE(custom_argv))
7756 die("command too long");
7759 if (!prepare_update(VIEW(request), custom_argv, NULL))
7760 die("Failed to format arguments");
7762 return request;
7766 main(int argc, const char *argv[])
7768 const char *codeset = "UTF-8";
7769 enum request request = parse_options(argc, argv);
7770 struct view *view;
7771 size_t i;
7773 signal(SIGINT, quit);
7774 signal(SIGPIPE, SIG_IGN);
7776 if (setlocale(LC_ALL, "")) {
7777 codeset = nl_langinfo(CODESET);
7780 if (load_repo_info() == ERR)
7781 die("Failed to load repo info.");
7783 if (load_options() == ERR)
7784 die("Failed to load user config.");
7786 if (load_git_config() == ERR)
7787 die("Failed to load repo config.");
7789 /* Require a git repository unless when running in pager mode. */
7790 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7791 die("Not a git repository");
7793 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7794 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7795 if (opt_iconv_in == ICONV_NONE)
7796 die("Failed to initialize character set conversion");
7799 if (codeset && strcmp(codeset, "UTF-8")) {
7800 opt_iconv_out = iconv_open(codeset, "UTF-8");
7801 if (opt_iconv_out == ICONV_NONE)
7802 die("Failed to initialize character set conversion");
7805 if (load_refs() == ERR)
7806 die("Failed to load refs.");
7808 foreach_view (view, i)
7809 if (!argv_from_env(view->ops->argv, view->cmd_env))
7810 die("Too many arguments in the `%s` environment variable",
7811 view->cmd_env);
7813 init_display();
7815 if (request != REQ_NONE)
7816 open_view(NULL, request, OPEN_PREPARED);
7817 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7819 while (view_driver(display[current_view], request)) {
7820 int key = get_input(0);
7822 view = display[current_view];
7823 request = get_keybinding(view->keymap, key);
7825 /* Some low-level request handling. This keeps access to
7826 * status_win restricted. */
7827 switch (request) {
7828 case REQ_NONE:
7829 report("Unknown key, press %s for help",
7830 get_key(view->keymap, REQ_VIEW_HELP));
7831 break;
7832 case REQ_PROMPT:
7834 char *cmd = read_prompt(":");
7836 if (cmd && isdigit(*cmd)) {
7837 int lineno = view->lineno + 1;
7839 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7840 select_view_line(view, lineno - 1);
7841 report("");
7842 } else {
7843 report("Unable to parse '%s' as a line number", cmd);
7846 } else if (cmd) {
7847 struct view *next = VIEW(REQ_VIEW_PAGER);
7848 const char *argv[SIZEOF_ARG] = { "git" };
7849 int argc = 1;
7851 /* When running random commands, initially show the
7852 * command in the title. However, it maybe later be
7853 * overwritten if a commit line is selected. */
7854 string_ncopy(next->ref, cmd, strlen(cmd));
7856 if (!argv_from_string(argv, &argc, cmd)) {
7857 report("Too many arguments");
7858 } else if (!prepare_update(next, argv, NULL)) {
7859 report("Failed to format command");
7860 } else {
7861 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7865 request = REQ_NONE;
7866 break;
7868 case REQ_SEARCH:
7869 case REQ_SEARCH_BACK:
7871 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7872 char *search = read_prompt(prompt);
7874 if (search)
7875 string_ncopy(opt_search, search, strlen(search));
7876 else if (*opt_search)
7877 request = request == REQ_SEARCH ?
7878 REQ_FIND_NEXT :
7879 REQ_FIND_PREV;
7880 else
7881 request = REQ_NONE;
7882 break;
7884 default:
7885 break;
7889 quit(0);
7891 return 0;